当前位置: 首页 > 知识库问答 >
问题:

当使用JPQL或HQL时,如何将@OneToMany字段映射到列表中?

俞学
2023-03-14

我有以下实体:

@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...
}

我有以下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 = '' ) "

但是,由于CityExpert中的字段太多,我不想选择所有字段。因此,我将查询更改如下:

"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 = '' ) "

然而,显然,我们无法在这样的实体中选择一对多字段,因为我在前面的查询中得到了一个MySQLSyntaxErrorException(参考这个问题)。因此,我将查询更改为:

"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 = '' ) "

但是,这次结果变成了List

我已创建以下DTO:

public class CityExpertDTO {

    private String name;
    private String email;
    private List<CityExpertDocument> documents;

}

但是,我不知道应该如何将Hibernate返回的结果映射到List

我正在使用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);

}

如何将结果映射到CityExpertDTO?


共有1个答案

解念
2023-03-14

假设我们有以下postpost_comment表,它们通过post_comment表中的post_id外键列形成一对多关系。

考虑到我们有一个用例,只需要从post表中获取id和title列,以及从post\u 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

运行上面的投影查询时,我们得到以下结果:

| 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! |

但是,我们不想使用基于表格的结果集或默认列表

我们可以使用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());

PostDTOResultTransformer将定义对象[]投影与包含子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());
    }
}

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;
}

我们将在postDTOMap中存储所有实体,这些实体最终将由查询执行返回。我们使用postDTOMap的原因是,每个子记录的SQL查询结果集中的父行都是重复的。

只有在postDTOMap中没有存储现有的PostDTO引用时,computeIfAbsent方法才允许我们创建PostDTO对象。

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
}

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
}

那就是了!

使用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!"
 类似资料:
  • 我想询问一个人,并得到以下回应: 但在需要时,我还想包含字段,并获得如下内容: 但是,我得到的只是手机ID,就像这样: 这是两个有问题的领域:

  • 问题内容: 在JPQL中,我可以通过以下方式检索实体: 但是,如果我想检索Category实体的id和name字段(仅),则需要像对象这样的东西,通过它我可以说:和。如何在不检索整个实体的情况下做到这一点? 基本上,对于如何检索像查询的信息:? 问题答案: 在HQL中,可以使用list()函数获取包含结果行的Object []数组的列表: 返回数组中的第一个元素将是id,第二个-名称。 如果要使用

  • 实体类如下: 是否有任何方法可以将实体中的Transstatus列映射到枚举的整数值,即状态代码,例如,如果我有枚举值作为NOT_FOUND实体中的Transstatus值应该是12, 编辑请注意,我有两个枚举状态码的值,状态码字符串,我们想要的只是状态码,我们已经用“Ordinal”尝试过了,它打印枚举的整数值,而不是我示例中的状态码

  • 嗨,我尝试将以下Source类映射到以下Destation类。我使用了以下映射以将字符串值映射到列表字符串。它没有正确映射。我需要知道如何使用Dozer将2个字符串值映射到一个目标字符串列表中。

  • 我在寻找更“改进的方法”来实现这一点,我有一种方法,从一个id中获取所有记录,然后“映射”到一个类中,我有大约200个“标记”要映射,这将是一个非常长的if链。。。例子:

  • 当我试图将从表t_comment_photo查询的userPhoto映射到java类ToyComment中的列表元素时,我得到了错误。 我试图修复的resultMap是: 我尝试了和,但都不起作用。 怎么把它修好?