考虑到我们有以下实体:
并且,您希望获取一些父实体以及所有关联的和集合。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抛出这个异常的原因是它不允许获取多个袋子,因为这会生成笛卡尔积。
其他人可能试图卖给你的最糟糕的“解决方案”
现在,您会发现许多答案,博客文章,视频或其他资源,告诉您对集合使用a而不是a。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 查询中,不转到 SQL 语句。这就是我们将 JPA 查询提示设置为 .distinct
PASS_DISTINCT_THROUGH
false
DISTINCT在JPQL中有两个含义,在这里,我们需要它来重复 Java 端返回的 Java 对象引用,而不是 SQL 端。getResultList
只要您最多使用 获取一个集合,就可以了。JOIN FETCH
通过使用多个查询,您将避免笛卡尔积,因为任何其他集合,但第一个集合是使用辅助查询获取的。
始终避免策略FetchType.EAGER
如果您在映射或关联时使用该策略,则很容易得到 .FetchType.EAGER
@OneToMany
@ManyToMany
MultipleBagFetchException
您最好从 切换到 ,因为急切的获取是一个可怕的想法,可能会导致关键的应用程序性能问题。FetchType.EAGER
Fetchype.LAZY
结论
避免并且不要仅仅因为这样做会使Hibernate隐藏在地毯下而切换到。一次只获取一个集合,你就会没事的。FetchType.EAGER
List
Set
MultipleBagFetchException
只要使用与要初始化的集合数相同的查询数来执行此操作,就可以了。只是不要在循环中初始化集合,因为这会触发 N+1
个查询问题,这对性能也有影响。