Spring Data JPA отображает собственный результат запроса в не-Entity POJO
У меня есть метод репозитория 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);
и я хотел бы сопоставить результат с Non-Entity POJO GroupDetails
.
Возможно ли, и если да, не могли бы вы привести пример?
Ответы
Ответ 1
Предполагая 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 Документация JPA , Spring сначала попытается найти именованный запрос, соответствующий вашему имени метода, поэтому с помощью @NamedNativeQuery
, @SqlResultSetMapping
и @ConstructorResult
, вы сможете достичь этого поведения
Ответ 2
Я думаю, что самый простой способ сделать это - использовать так называемую проекцию. Он может сопоставить результаты запроса с интерфейсами. Использование 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
с собственным репозиторием.
Ответ 3
Я думаю, что Михал подход лучше. Но есть еще один способ получить результат из собственного запроса.
@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);
Теперь вы можете преобразовать этот массив двухмерных строк в нужный объект.
Ответ 4
Вы можете написать свой собственный или не собственный запрос так, как вы хотите, и вы можете обернуть результаты запроса 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;
}
}
Ответ 5
Если GroupDetails
что-то вроде:
public class GroupDetails {
int groupId;
int userId;
public GroupDetails(int groupId , int userId) {
this.groupId = groupId;
this.userId = userId;
}
// getters setters, etc.
}
Следующее должно работать:
@Query(value = "SELECT new GroupDetails(gm.group_id, gm.user_id) 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")
Ответ 6
Вы можете сделать что-то вроде
@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;
}
Ответ 7
На моем компьютере этот код работает. Он немного отличается от ответа Даймона.
@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")