【问题标题】:How to use multiple JOIN FETCH in one JPQL query如何在一个 JPQL 查询中使用多个 JOIN FETCH
【发布时间】: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

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

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

【问题讨论】:

    标签: java hibernate jpa join jpql


    【解决方案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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-17
      • 2013-01-11
      • 1970-01-01
      • 2012-06-25
      • 2013-09-25
      • 2016-10-07
      • 2017-10-23
      • 2021-12-31
      相关资源
      最近更新 更多