Spring Data JPA відображає результат власного запиту до несуб’єктного POJO


92

У мене є метод сховища даних Spring з власним запитом

@Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
    GroupDetails getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

і я хотів би відобразити результат до несуб’єктного POJO GroupDetails.

Чи можливо це, і якщо так, чи можете ви навести приклад?

Відповіді:


65

Припускаючи GroupDetails, як у відповіді orid, ви пробували JPA 2.1 @ConstructorResult ?

@SqlResultSetMapping(
    name="groupDetailsMapping",
    classes={
        @ConstructorResult(
            targetClass=GroupDetails.class,
            columns={
                @ColumnResult(name="GROUP_ID"),
                @ColumnResult(name="USER_ID")
            }
        )
    }
)

@NamedNativeQuery(name="getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")

і використовувати в інтерфейсі сховища наступне:

GroupDetails getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

Згідно Spring Data JPA документації , весна буде спочатку спробувати знайти іменований запит відповідності вашого імені методи - це з допомогою @NamedNativeQuery, @SqlResultSetMappingі @ConstructorResultви повинні бути в змозі досягти такої поведінки


15
Щоб дані весни могли збігатися з NamedNativeQuery, перед іменем NameedNativeQuery потрібно вказати ім’я класу сутності домену, після якого крапка. Отже, ім'я повинно бути (якщо припустити, що суттю домену є Group) 'Group.getGroupDetails'.
Grant Lay

@GrantLay можете поглянути на це питання: stackoverflow.com/q/44871757/7491770 У мене точно така проблема.
таран

Як я поверну список таких Об'єктів?
Nikhil Sahu

1
Щоб змусити це працювати, слід GroupDetailsпозначити @Entity? Якщо можливо, можете сказати, до якого класу @NamedNativeQueryпотрібно застосовувати анотацію ?
Ману

3
@SqlResultSetMappingа також @NamedNativeQueryанотації повинні бути присутніми на сутності, яка використовується у вашому сховищі Spring Data (наприклад, для public interface CustomRepository extends CrudRepository<CustomEntity, Long>цього CustomEntityкласу)
Tomasz W

112

Я думаю, що найпростіший спосіб зробити це - використовувати так звану проекцію. Він може зіставити результати запитів з інтерфейсами. Використання SqlResultSetMappingє незручним і робить ваш код негарним :).

Приклад прямо з джерельних даних JPA вихідного коду:

public interface UserRepository extends JpaRepository<User, Integer> {

   @Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true)
   NameOnly findByNativeQuery(Integer id);

   public static interface NameOnly {

     String getFirstname();

     String getLastname();

  }
}

Ви також можете використовувати цей метод, щоб отримати список проекцій.

Ознайомтесь із цим весняним записом про документи JPA для отримання додаткової інформації про прогнози.

Примітка 1:

Не забудьте визначити свою Userсутність як звичайну - поля з проектованого інтерфейсу повинні збігатися з полями в цій сутності. В іншому випадку відображення полів може бути порушено ( getFirstname()може повернутися значення прізвища тощо).

Примітка 2:

Якщо ви використовуєте SELECT table.column ...нотацію, завжди визначайте псевдоніми, що відповідають іменам сутності. Наприклад, цей код не буде працювати належним чином (проекція поверне нулі для кожного геттера):

@Query(value = "SELECT user.firstname, user.lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);

Але це чудово працює:

@Query(value = "SELECT user.firstname AS firstname, user.lastname AS lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);

У випадку більш складних запитів я б скоріше використовував JdbcTemplateспеціальне сховище.


Це більш чисте рішення. Я перевірив, але продуктивність набагато гірша, ніж використання SqlResultSetMapping (це повільніше, приблизно 30-40% :()
kidnan1991

працює приємно! зробіть інтерфейс загальнодоступним, якщо ви хочете використовувати його деінде
tibi

Не працює, якщо потрібно витягти поле типу XML (clob). Будь-яка пропозиція?
Ashish

@Ashish я б скоріше використав JdbcTemplate( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) замість цього. Ви можете використовувати getClobметод on resultSet, щоб отримати клоб InputStream. Як приклад: rs.getClob("xml_column").getCharacterStream().
Міхал Стохмаль

Що робити, якщо я використовую SELECT * у запиті, а запит є рідним?
Салман Казмі

17

Я думаю, що підхід Міхала кращий. Але є ще один спосіб отримати результат із власного запиту.

@Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
String[][] getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

Тепер ви можете перетворити цей 2D-масив рядків у потрібну вам сутність.


2
просто і елегантно
Джон

9

Ви можете написати свій власний або неродний запит так, як вам заманеться, а результати обробки JPQL можна обернути екземплярами власних класів результатів. Створіть DTO з однаковими іменами стовпців, що повертаються в запиті, і створіть конструктор усіх аргументів з тією самою послідовністю та іменами, що повертаються запитом. Потім скористайтеся наступним способом для запиту бази даних.

@Query("SELECT NEW example.CountryAndCapital(c.name, c.capital.name) FROM Country AS c")

Створити DTO:

package example;

public class CountryAndCapital {
    public String countryName;
    public String capitalName;

    public CountryAndCapital(String countryName, String capitalName) {
        this.countryName = countryName;
        this.capitalName = capitalName;
    }
}

виправлення: однакові імена не є обов'язковими ... просто однакова послідовність параметрів у конструкторі та повернутий набір результатів.
Waqas Мемон

Це працює, лише якщо Country - це ваш клас сутності Java. Це не буде, якщо Country не є вашим класом сутності Java.
Yeshwant KAKAD

1
Ви кажете, що це також має працювати з власними запитами? Не могли б ви навести приклад цього?
Річард Тінгл,

OP запитує власний запит, але наведений приклад є
неродним

0

Ви можете зробити щось на зразок

@NamedQuery(name="IssueDescriptor.findByIssueDescriptorId" ,

    query=" select new com.test.live.dto.IssuesDto (idc.id, dep.department, iss.issueName, 
               cat.issueCategory, idc.issueDescriptor, idc.description) 
            from Department dep 
            inner join dep.issues iss 
            inner join iss.category cat 
            inner join cat.issueDescriptor idc 
            where idc.id in(?1)")

І там повинен бути конструктор типу

public IssuesDto(long id, String department, String issueName, String issueCategory, String issueDescriptor,
            String description) {
        super();
        this.id = id;
        this.department = department;
        this.issueName = issueName;
        this.issueCategory = issueCategory;
        this.issueDescriptor = issueDescriptor;
        this.description = description;
    }

13
Питання стосується власних запитів, а не запитів, написаних у HQL.
DBK

-5

На моєму комп’ютері я розумію, що цей код працює. Він трохи відрізняється від відповіді Даймона.

@SqlResultSetMapping(
    name="groupDetailsMapping",
    classes={
        @ConstructorResult(
            targetClass=GroupDetails.class,
            columns={
                @ColumnResult(name="GROUP_ID",type=Integer.class),
                @ColumnResult(name="USER_ID",type=Integer.class)
            }
        )
    }
)

@NamedNativeQuery(name="User.getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.