[英]How to map @OneToMany field into List<DTO> when using JPQL or HQL?
I have the following entities:我有以下实体:
@Entity
public class CityExpert {
@Id
private long id;
@OneToOne
private User user;
@OneToMany(mappedBy = "cityExpert")
private List<CityExpertDocument> documents;
// Lots of other fields...
}
@Entity
public class CityExpertDocument {
@Id
private long id;
@ManyToOne
private CityExpert cityExpert;
// Lots of other fields...
}
@Entity
public class User {
@Id
private long id;
private String name;
private String email;
// Lots of other fields...
}
I have the following HQL query, in which I select a subset of CityExpert
s:我有以下 HQL 查询,其中我选择了CityExpert
的一个子集:
"select " +
"e " +
"from " +
"CityExpert e " +
"where " +
"( (lower(e.user.name) like concat('%', lower(?1), '%') or e.user.name is null) or ?1 = '' ) " +
"and " +
"( (lower(e.user.phone) like concat('%', lower(?2), '%') or e.user.phone is null) or ?2 = '' ) "
However, since there are too many fields in CityExpert
, I don't want to select all fields.但是,由于CityExpert
字段CityExpert
,我不想选择所有字段。 Hence, I have changed the query as follows:因此,我已将查询更改如下:
"select " +
"e.user.name, " +
"e.user.email, " +
"e.documents " +
"from " +
"CityExpert e " +
"where " +
"( (lower(e.user.name) like concat('%', lower(?1), '%') or e.user.name is null) or ?1 = '' ) " +
"and " +
"( (lower(e.user.phone) like concat('%', lower(?2), '%') or e.user.phone is null) or ?2 = '' ) "
However, apparently we cannot select a one-to-many field in an entity like that, because I am getting a MySQLSyntaxErrorException
with the preceding query (refer to this question ).但是,显然我们不能在这样的实体中选择一对多字段,因为我在前面的查询中收到了MySQLSyntaxErrorException
(请参阅此问题)。 Hence, I have changed the query to following:因此,我已将查询更改为以下内容:
"select " +
"e.user.name, " +
"e.user.email, " +
"d " +
"from " +
"CityExpert e " +
"left join " +
"e.documents d" +
"where " +
"( (lower(e.user.name) like concat('%', lower(?1), '%') or e.user.name is null) or ?1 = '' ) " +
"and " +
"( (lower(e.user.phone) like concat('%', lower(?2), '%') or e.user.phone is null) or ?2 = '' ) "
However, this time the result becomes a List<Object[]>
, instead of List<CityExpert>
.但是,这次结果变成了List<Object[]>
,而不是List<CityExpert>
。
I have created the following DTO:我创建了以下 DTO:
public class CityExpertDTO {
private String name;
private String email;
private List<CityExpertDocument> documents;
}
However, I don't know how I should map the result returned by Hibernate to List<CityExpertDTO>
.但是,我不知道应该如何将 Hibernate 返回的结果映射到List<CityExpertDTO>
。 I mean, I can do this manually but surely there must be an automated solution provided by Hibernate.我的意思是,我可以手动执行此操作,但肯定必须有 Hibernate 提供的自动化解决方案。
I am using Spring Data JPA and using the HQL as follows:我正在使用 Spring Data JPA 并使用 HQL,如下所示:
public interface CityExpertRepository extends JpaRepository<CityExpert, Long> {
@Query(
"select " +
"e " +
"from " +
"CityExpert e " +
"where " +
"( (lower(e.user.name) like concat('%', lower(?1), '%') or e.user.name is null) or ?1 = '' ) " +
"and " +
"( (lower(e.user.phone) like concat('%', lower(?2), '%') or e.user.phone is null) or ?2 = '' ) "
)
Set<CityExpert> findUsingNameAndPhoneNumber(String name,
String phoneNumber);
}
How can I map the result to CityExpertDTO
?如何将结果映射到CityExpertDTO
?
Assuming we have the following post
and post_comment
tables, which form a one-to-many relationship via the post_id
Foreign Key column in the post_comment
table.假设我们有以下post
和post_comment
表,它们通过post_comment
表中的post_id
外键列形成一对多关系。
Considering we have a use case that only requires fetching the id
and title
columns from the post
table, as well as the id
and review
columns from the post_comment
tables, we could use the following JPQL query to fetch the required projection:考虑到我们有一个用例只需要从post
表中获取id
和title
列,以及从post_comment
表中获取id
和review
列,我们可以使用以下 JPQL 查询来获取所需的投影:
select p.id as p_id,
p.title as p_title,
pc.id as pc_id,
pc.review as pc_review
from PostComment pc
join pc.post p
order by pc.id
When running the projection query above, we get the following results:运行上面的投影查询时,我们得到以下结果:
| p.id | p.title | pc.id | pc.review |
|------|-----------------------------------|-------|---------------------------------------|
| 1 | High-Performance Java Persistence | 1 | Best book on JPA and Hibernate! |
| 1 | High-Performance Java Persistence | 2 | A must-read for every Java developer! |
| 2 | Hypersistence Optimizer | 3 | It's like pair programming with Vlad! |
However, we don't want to use a tabular-based ResultSet
or the default List<Object[]>
JPA or Hibernate query projection.但是,我们不想使用基于表格的ResultSet
或默认的List<Object[]>
JPA 或 Hibernate 查询投影。 We want to transform the aforementioned query result set to a List
of PostDTO
objects, each such object having a comments
collection containing all the associated PostCommentDTO
objects:我们想将上述查询结果集转换为PostDTO
对象List
,每个这样的对象都有一个包含所有关联PostCommentDTO
对象的comments
集合:
We can use a Hibernate ResultTransformer
, as illustrated by the following example:我们可以使用 Hibernate ResultTransformer
,如下例所示:
List<PostDTO> postDTOs = entityManager.createQuery("""
select p.id as p_id,
p.title as p_title,
pc.id as pc_id,
pc.review as pc_review
from PostComment pc
join pc.post p
order by pc.id
""")
.unwrap(org.hibernate.query.Query.class)
.setResultTransformer(new PostDTOResultTransformer())
.getResultList();
assertEquals(2, postDTOs.size());
assertEquals(2, postDTOs.get(0).getComments().size());
assertEquals(1, postDTOs.get(1).getComments().size());
The PostDTOResultTransformer
is going to define the mapping between the Object[]
projection and the PostDTO
object containing the PostCommentDTO
child DTO objects: PostDTOResultTransformer
将定义Object[]
投影和包含PostCommentDTO
子 DTO 对象的PostDTO
对象之间的映射:
public class PostDTOResultTransformer
implements ResultTransformer {
private Map<Long, PostDTO> postDTOMap = new LinkedHashMap<>();
@Override
public Object transformTuple(
Object[] tuple,
String[] aliases) {
Map<String, Integer> aliasToIndexMap = aliasToIndexMap(aliases);
Long postId = longValue(tuple[aliasToIndexMap.get(PostDTO.ID_ALIAS)]);
PostDTO postDTO = postDTOMap.computeIfAbsent(
postId,
id -> new PostDTO(tuple, aliasToIndexMap)
);
postDTO.getComments().add(
new PostCommentDTO(tuple, aliasToIndexMap)
);
return postDTO;
}
@Override
public List transformList(List collection) {
return new ArrayList<>(postDTOMap.values());
}
}
The aliasToIndexMap
is just a small utility that allows us to build a Map
structure that associates the column aliases and the index where the column value is located in the Object[]
tuple
array: aliasToIndexMap
只是一个小实用程序,它允许我们构建一个Map
结构,该结构将列别名和列值位于Object[]
tuple
数组中的索引相关联:
public Map<String, Integer> aliasToIndexMap(
String[] aliases) {
Map<String, Integer> aliasToIndexMap = new LinkedHashMap<>();
for (int i = 0; i < aliases.length; i++) {
aliasToIndexMap.put(aliases[i], i);
}
return aliasToIndexMap;
}
The postDTOMap
is where we are going to store all PostDTO
entities that, in the end, will be returned by the query execution. postDTOMap
是我们将要存储所有PostDTO
实体的地方, PostDTO
实体最终将由查询执行返回。 The reason we are using the postDTOMap
is that the parent rows are duplicated in the SQL query result set for each child record.我们使用postDTOMap
的原因是父行在 SQL 查询结果集中为每个子记录重复。
The computeIfAbsent
method allows us to create a PostDTO
object only if there is no existing PostDTO
reference already stored in the postDTOMap
.该computeIfAbsent
方法允许我们创建一个PostDTO
只有当不存在现有对象PostDTO
已经存储在参考postDTOMap
。
The PostDTO
class has a constructor that can set the id
and title
properties using the dedicated column aliases: PostDTO
类有一个构造函数,可以使用专用列别名设置id
和title
属性:
public class PostDTO {
public static final String ID_ALIAS = "p_id";
public static final String TITLE_ALIAS = "p_title";
private Long id;
private String title;
private List<PostCommentDTO> comments = new ArrayList<>();
public PostDTO(
Object[] tuples,
Map<String, Integer> aliasToIndexMap) {
this.id = longValue(tuples[aliasToIndexMap.get(ID_ALIAS)]);
this.title = stringValue(tuples[aliasToIndexMap.get(TITLE_ALIAS)]);
}
//Getters and setters omitted for brevity
}
The PostCommentDTO
is built in a similar fashion: PostCommentDTO
以类似的方式构建:
public class PostCommentDTO {
public static final String ID_ALIAS = "pc_id";
public static final String REVIEW_ALIAS = "pc_review";
private Long id;
private String review;
public PostCommentDTO(
Object[] tuples,
Map<String, Integer> aliasToIndexMap) {
this.id = longValue(tuples[aliasToIndexMap.get(ID_ALIAS)]);
this.review = stringValue(tuples[aliasToIndexMap.get(REVIEW_ALIAS)]);
}
//Getters and setters omitted for brevity
}
That's it!而已!
Using the PostDTOResultTransformer
, the SQL result set can be transformed into a hierarchical DTO projection, which is much convenient to work with, especially if it needs to be marshalled as a JSON response:使用PostDTOResultTransformer
,可以将 SQL 结果集转换为分层 DTO 投影,使用起来非常方便,尤其是在需要将其编组为 JSON 响应时:
postDTOs = {ArrayList}, size = 2
0 = {PostDTO}
id = 1L
title = "High-Performance Java Persistence"
comments = {ArrayList}, size = 2
0 = {PostCommentDTO}
id = 1L
review = "Best book on JPA and Hibernate!"
1 = {PostCommentDTO}
id = 2L
review = "A must read for every Java developer!"
1 = {PostDTO}
id = 2L
title = "Hypersistence Optimizer"
comments = {ArrayList}, size = 1
0 = {PostCommentDTO}
id = 3L
review = "It's like pair programming with Vlad!"
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.