Hibernate抛出MultipleBagFetchException——不能同时获取多个包

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;


}

这个问题怎么样?我该怎么办?


编辑

好的,我的问题是,另一个“父”实体是在我的父,我的真实行为是这样的:

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的两个集合,但这似乎是一个错误,我没有做不寻常的事情…

ParentAnotherParent中删除FetchType.EAGER可以解决问题,但我需要它,所以真正的解决方案是使用@LazyCollection(LazyCollectionOption.FALSE)而不是FetchType(感谢Bozho的解决方案)。

438041 次浏览

我认为hibernate的新版本(支持JPA 2.0)应该可以处理这个问题。但除此之外,你可以通过注释集合字段来解决它:

@LazyCollection(LazyCollectionOption.FALSE)

记住要从@*ToMany注释中删除fetchType属性。

但是请注意,在大多数情况下Set<Child>List<Child>更合适,所以除非你真的需要List,否则请选择Set

但是请注意,使用集合时,不会消除底层的笛卡儿积,正如弗拉德·米哈尔恰回答道所描述的那样!

简单地从List类型更改为Set类型。

但请提醒您不会消除了由弗拉德·米哈尔恰回答道所描述的底层笛卡儿积 !

在你的代码中添加一个hibernate特有的@Fetch注释:

@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;

这将修复与Hibernate bug 终极战士- 1718相关的问题

我发现了一篇关于Hibernate在这种对象映射中的行为的很好的博客文章:http://blog.eyallupu.com/2010/06/hibernate-exception-simultaneously.html

在尝试了这篇文章和其他文章中描述的每一个选项后,我得出的结论是,解决方案如下。

在每一个XToMany的地方@XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) 中间在

之后
@Fetch(value = FetchMode.SUBSELECT)

这对我很有效

要修复它,只需将嵌套对象的Set替换为List

@OneToMany
Set<Your_object> objectList;

不要忘记使用fetch=FetchType.EAGER

它会起作用的。

如果你想只使用list,在Hibernate中还有一个概念CollectionId

但请提醒您不会消除了由弗拉德·米哈尔恰回答道所描述的底层笛卡儿积 !

当你有太复杂的对象时,所有的对象都使用EAGER fetchType并不是一个好主意,最好使用LAZY,当你真的需要加载集合时,使用Hibernate.initialize(parent.child)来获取数据。

您可以在JPA中保留摊位EAGER列表,并向其中至少一个添加JPA注释@OrderColumn(显然是要排序的字段的名称)。不需要特定的hibernate注释。 但是请记住,如果所选字段没有从0

开始的值,则可能会在列表中创建空元素
 [...]
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@OrderColumn(name="orderIndex")
private List<Child> children;
[...]

在Children中,你应该添加orderIndex字段

你可以使用一个新的注释来解决这个问题:

@XXXToXXX(targetEntity = XXXX.class, fetch = FetchType.LAZY)

实际上,fetch的默认值是FetchType。懒惰。

我们尝试了而不是列表,这是一个噩梦:当你添加两个新对象时,=()和hashCode()无法区分它们!因为他们没有任何身份证明。

典型的工具如Eclipse从数据库表中生成这种代码:

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}

您还可以阅读这篇文章,它正确地解释了JPA/Hibernate是如何混乱的。读完这篇文章后,我想这是我一生中最后一次使用ORM了。

我也遇到过领域驱动设计的人,他们说ORM是一个可怕的东西。

考虑到我们有以下实体:

enter image description here

并且,您想要获取一些父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将抛出臭名昭著的:

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获取最多一个集合,就可以了。

通过使用多个查询,您将避免使用笛卡尔积,因为任何其他集合都是如此,但第一个集合是使用辅助查询获取的。

你能做的还有很多

如果您在映射时为@OneToMany@ManyToMany关联使用FetchType.EAGER策略,那么您可能很容易得到MultipleBagFetchException

最好从FetchType.EAGER切换到Fetchype.LAZY,因为即时抓取是一个糟糕的想法,可能会导致严重的应用程序性能问题。

结论

避免FetchType.EAGER,不要从List切换到Set,因为这样做会让Hibernate把MultipleBagFetchException隐藏在地毯下面。每次只获取一个集合,就不会有问题。

只要使用的查询数量与需要初始化的集合数量相同,就没问题。只是不要在循环中初始化集合,因为这会触发N+1个查询问题,这对性能也很不利。

对我来说,问题是嵌套了急切的获取。

一个解决方案是将嵌套字段设置为懒惰的,并使用Hibernate.initialize()来加载嵌套字段:

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());

在我这边,当我使用FetchType有多个集合时就发生了这种情况。热切的,像这样:

@ManyToMany(fetch = FetchType.EAGER, targetEntity = className.class)
@JoinColumn(name = "myClass_id")
@JsonView(SerializationView.Summary.class)
private Collection<Model> ModelObjects;

此外,集合连接在同一列上。

为了解决这个问题,我将其中一个集合更改为FetchType。LAZY,因为它适合我的用例。

< p >古德勒克! ~ J < / p >

同时注释FetchLazyCollection有时有助于运行项目。

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)

关于@LazyCollection(LazyCollectionOption.FALSE)的一个好处是,具有该注释的几个字段可以共存,而FetchType.EAGER则不能,即使在这种共存是合法的情况下也是如此。

例如,Order可能有一个OrderGroup(一个短列表)的列表,以及一个Promotions(也是短列表)的列表。@LazyCollection(LazyCollectionOption.FALSE)可以同时使用,而不会导致LazyInitializationExceptionMultipleBagFetchException

在我的情况下,@Fetch确实解决了我的MultipleBacFetchException问题,但随后导致LazyInitializationException,臭名昭著的no Session错误。

我通过注释来解决:

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)

好吧,这是我的2美分。我在我的实体中有Fetch Lazy注释,但我也在会话bean中复制了Fetch Lazy,从而导致了多个包问题。因此,我只是删除了SessionBean中的行

criteria.createAlias("FIELD";ALIAS", JoinType.LEFT_OUTER_JOIN);/ /删除

我使用Hibernate。在list parent = criteria之后,我想检索的列表上的初始化。List被调用。 Hibernate.initialize (parent.getChildList ()); < / p >

你也可以尝试使用fetch=FetchType。LAZY和只是添加@Transactional(readOnly = true)的方法,你得到的孩子