簡體   English   中英

如何在一個 JPQL 查詢中使用多個 JOIN FETCH

[英]How to use multiple JOIN FETCH in one JPQL query

我有以下實體:

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

我找到了有關此錯誤的文章,但這些文章僅描述了在一個實體中有兩個要加入的集合的情況。 我的問題有點不同,我不知道如何解決。

可以在一個查詢中完成嗎?

考慮到我們有以下實體:

JPA實體域模型

並且,您想要獲取一些父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使用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並且不要僅僅因為這樣做會使 Hibernate 將MultipleBagFetchException隱藏在地毯下而從List切換到Set 一次只取一個集合,你會沒事的。

只要您使用與要初始化的集合相同數量的查詢來執行此操作,就可以了。 只是不要在循環中初始化集合,因為這會觸發N+1查詢問題,這也對性能不利。

這是復雜連接和多重條件的工作示例:

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

一種解決方法是將@Query 和@EntityGraph 一起使用,就像這里提到的那樣將@Query 和@EntityGraph 一起使用

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM