考虑到我们有以下实体:
而且,你想找一些家长Post
实体以及所有相关的comments
and 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 https://vladmihalcea.com/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
为您的收藏。
这是个糟糕的建议。不要那样做!
Using 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 对象引用的重复数据getResultList
在 Java 端,而不是 SQL 端。
只要您使用最多获取一个集合JOIN FETCH
, 你会好起来的。
通过使用多个查询,您将避免使用笛卡尔积,因为除了第一个集合之外的任何其他集合都是使用辅助查询获取的。
始终避免FetchType.EAGER
战略
如果您正在使用FetchType.EAGER
映射时的策略@OneToMany
or @ManyToMany
关联,那么你很容易得到一个MultipleBagFetchException
.
你最好从FetchType.EAGER
to Fetchype.LAZY
因为急切的获取是一个糟糕的想法,可能会导致严重的应用程序性能问题。
结论
Avoid FetchType.EAGER
并且不要从List
to Set
只是因为这样做会让 Hibernate 隐藏MultipleBagFetchException
在地毯下。一次只获取一个集合,就可以了。
只要您使用与要初始化的集合相同数量的查询来执行此操作,就可以了。只是不要在循环中初始化集合,因为这会触发N+1 https://vladmihalcea.com/how-to-detect-the-n-plus-one-query-problem-during-testing/查询问题,这也会影响性能。