Hibernate在创建SessionFactory时抛出这个异常:
multiplebagfetchexception:不能同时获取多个包
这是我的测试用例:
Parent.java
@Entity
public Parent {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
// @IndexColumn(name="INDEX_COL") if I had this the problem solve but I retrieve more children than I have, one child is null.
private List<Child> children;
}
Child.java
@Entity
public Child {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Parent parent;
}
这个问题怎么样?我该怎么办?
EDIT
好的,我的问题是,另一个“父”实体是在我的父,我的真实行为是这样的:
Parent.java
@Entity
public Parent {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@ManyToOne
private AnotherParent anotherParent;
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
private List<Child> children;
}
AnotherParent.java
@Entity
public AnotherParent {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
private List<AnotherChild> anotherChildren;
}
Hibernate不喜欢FetchType有两个集合。EAGER,但这似乎是一个bug,我没有做不寻常的事情…
删除FetchType。来自Parent或AnotherParent的渴望解决了这个问题,但我需要它,所以真正的解决方案是使用@LazyCollection(LazyCollectionOption.FALSE)而不是FetchType(感谢Bozho的解决方案)。
考虑到我们有以下实体:
并且,您希望获取一些父Post实体以及所有注释和标记集合。
如果你使用了多个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将抛出臭名昭著的:
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来收集。
这是个糟糕的建议。不要那样做!
使用set而不是list将使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 DOES NOT转到SQL语句。这就是为什么我们将PASS_DISTINCT_THROUGH JPA查询提示设置为false。
DISTINCT在JPQL中有两个含义,在这里,我们需要它在Java端(而不是SQL端)对getResultList返回的Java对象引用进行重复删除。
只要使用JOIN fetch最多获取一个集合,就没问题。
通过使用多个查询,您将避免使用笛卡尔积,因为任何其他集合都是如此,但第一个集合是使用辅助查询获取的。
你能做的还有很多
如果你在使用FetchType。对于@OneToMany或@ManyToMany关联,在映射时使用EAGER策略,那么您很容易得到一个MultipleBagFetchException。
你最好从FetchType切换。渴望获取信息。LAZY,因为急切抓取是一个可怕的想法,可能导致严重的应用程序性能问题。
结论
避免FetchType。不要从List切换到Set,因为这样做会让Hibernate把MultipleBagFetchException隐藏起来。每次只获取一个集合,就不会有问题。
只要使用的查询数量与需要初始化的集合数量相同,就没问题。只是不要在循环中初始化集合,因为这会触发N+1个查询问题,这对性能也很不利。