如何在一个 JPQL 查询中使用多个 JOIN FETCH

Posted

技术标签:

【中文标题】如何在一个 JPQL 查询中使用多个 JOIN FETCH【英文标题】:How to use multiple JOIN FETCH in one JPQL query 【发布时间】:2015-07-17 07:01:58 【问题描述】:

我有以下实体:

public class Category 
   private Integer id;

   @OneToMany(mappedBy = "parent")
   private List<Topic> topics;


public class Topic 
   private Integer id;

   @OneToMany(mappedBy = "parent")
   private List<Posts> posts;

   @ManyToOne
   @JoinColumn(name = "id")
   private Category parent;


public class Post 
   private Integer id;

   @ManyToOne
   @JoinColumn(name = "id")
   private Topic parent;
   /* Post fields */

我想使用 JPQL 查询获取所有加入 topics 和加入 posts 的类别。我写了如下查询:

SELECT c FROM Category c
JOIN FETCH c.topics t
JOIN FETCH t.posts p WHERE 

但我得到了错误

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags

我找到了有关此错误的文章,但这些文章仅描述了在一个实体中有两个要加入的集合的情况。我的问题有点不同,我不知道如何解决。

可以在一个查询中完成吗?

【问题讨论】:

【参考方案1】:

考虑到我们有以下实体:

而且,您想要获取一些父 Post 实体以及所有关联的 commentstags 集合。

如果您使用多个JOIN FETCH 指令:

List<Post> posts = entityManager.createQuery("""
    select p
    from Post p
    left join fetch p.comments
    left join fetch p.tags
    where p.id between :minId and :maxId
    """, Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();

Hibernate 会抛出 MultipleBagFetchException:

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags [
  com.vladmihalcea.book.hpjp.hibernate.fetching.Post.comments,
  com.vladmihalcea.book.hpjp.hibernate.fetching.Post.tags
]

Hibernate 抛出此异常的原因是它不允许获取多个包,因为这会生成笛卡尔积。

其他人可能试图向您推销的最糟糕的“解决方案”

现在,您会发现很多答案、博客文章、视频或其他资源告诉您在收藏中使用 Set 而不是 List

这是一个糟糕的建议。不要那样做!

使用Sets 而不是Lists 将使MultipleBagFetchException 消失,但笛卡尔积仍然存在,这实际上更糟,因为您会在应用它很久之后发现性能问题“修复”。

正确的解决方案

您可以使用以下技巧:

List<Post> posts = entityManager.createQuery("""
    select distinct p
    from Post p
    left join fetch p.comments
    where p.id between :minId and :maxId
    """, Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

posts = entityManager.createQuery("""
    select distinct p
    from Post p
    left join fetch p.tags t
    where p in :posts
    """, Post.class)
.setParameter("posts", posts)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

在第一个 JPQL 查询中,distinct 不会转到 SQL 语句。这就是我们将PASS_DISTINCT_THROUGH JPA 查询提示设置为false 的原因。

DISTINCT 在 JPQL 中有两个含义,在这里,我们需要它在 Java 端而不是 SQL 端对 getResultList 返回的 Java 对象引用进行去重。

只要您使用JOIN FETCH 最多获取一个集合,就可以了。

通过使用多个查询,您将避免笛卡尔积,因为任何其他集合,但第一个是使用辅助查询获取的。

始终避免使用FetchType.EAGER 策略

如果您在为@OneToMany@ManyToMany 关联映射时使用FetchType.EAGER 策略,那么您很容易以MultipleBagFetchException 结束。

您最好从FetchType.EAGER 切换到Fetchype.LAZY,因为急切获取是一个糟糕的想法,可能会导致严重的应用程序性能问题。

结论

避免使用FetchType.EAGER 并且不要从List 切换到Set,因为这样做会使Hibernate 将MultipleBagFetchException 隐藏在地毯下。一次只获取一个集合,就可以了。

只要您使用与要初始化的集合相同数量的查询来执行此操作,就可以了。只是不要在循环中初始化集合,因为这会触发N+1 查询问题,这也对性能不利。

【讨论】:

在这篇文章的结尾你说:“尽管简单地使用索引列表或集合总是更好”。如何创建索引列表? 这个查询给了我以下错误,但仍然运行:JOIN FETCH expressions cannot be defined with an identification variable. 只有一个问题,您的查询返回一个帖子,没有类别。 @VladMihalcea 有点不相关——但是当你加入 fetch all 复合成员时,使用 join fetcheager 有什么优势?【参考方案2】:

这是一个复杂连接和多重条件的工作示例:

    String query_findByProductDepartmentHospital = "select location from ProductInstallLocation location "
            + " join location.product prod " + " join location.department dep "
            + " join location.department.hospital hos " + " where  prod.name = :product "
            + " and dep.name.name = :department " + " and hos.name = :hospital ";

    @Query(query_findByProductDepartmentHospital)
    ProductInstallLocation findByProductDepartmentHospital(@Param("product") String productName,@Param("department") String departName, @Param("hospital") String hospitalName);

【讨论】:

这没有获取,所以它不会初始化加入的集合。 此响应与此上下文无关。由于 fetch 子句而发生错误。【参考方案3】:

一种解决方法是同时使用@Query 和@EntityGraph,就像这里提到的use @Query and @EntityGraph together

【讨论】:

以上是关于如何在一个 JPQL 查询中使用多个 JOIN FETCH的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JPA 1.0 构建一个 JPQL 查询,从多个表中获取数据以克服延迟初始化?

JPQL 查询删除不接受声明的 JOIN?

JPQL 中的 LEFT JOIN ON()

JPQL JOIN FETCH 多个表也带有空集合

可以在一个 JPQL 查询中传递多个命名实体图?

JPQL JOIN 查询对应值的行数(多列)