简体   繁体   English

如何将@OneToMany 字段映射到列表中<DTO>什么时候使用 JPQL 或 HQL?

[英]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

Table relationships表关系

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.假设我们有以下postpost_comment表,它们通过post_comment表中的post_id外键列形成一对多关系。

用于 JPA DTO 投影的 post 和 post_comment 表

SQL Projection SQL 投影

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表中获取idtitle列,以及从post_comment表中获取idreview列,我们可以使用以下 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! |

DTO Projection DTO投影

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集合:

用于 DTO 投影的 PostDTO 和 PostCommentDTO

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类有一个构造函数,可以使用专用列别名设置idtitle属性:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM