如何在一个 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
实体以及所有关联的 comments
和 tags
集合。
如果您使用多个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 fetch
与 eager
有什么优势?【参考方案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的主要内容,如果未能解决你的问题,请参考以下文章