Hibernate引发MultipleBagFetchException-无法同时获取多个包


471

Hibernate在创建SessionFactory时抛出此异常:

org.hibernate.loader.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,但这似乎是一个错误,我没有做异常的事情...

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


我想问一下,您希望生成哪种SQL查询来同时检索两个单独的集合?能够实现这些目标的SQL类型要么需要笛卡尔联接(可能效率很低),要么需要不连续列的UNION(也很丑陋)。大概无法以干净有效的方式在SQL中实现此目的影响了API设计。
Thomas W

@ThomasW这些是应生成的sql查询:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
nurettin

1
你可以得到一个呈三角错误,如果你有一个以上的List<child>fetchType用于定义一个以上 List<clield>
大捷思锐

Answers:


555

我认为较新版本的休眠(支持JPA 2.0)应该可以解决此问题。但是,否则,您可以通过以下方式对集合字段进行注释来解决:

@LazyCollection(LazyCollectionOption.FALSE)

请记住fetchType@*ToMany注释中删除该属性。

但是请注意,在大多数情况下,a Set<Child>比更为合适List<Child>,因此除非您确实需要List-Set

但是请记住,使用集不会消除Vlad Mihalcea在其答案中描述的底层笛卡尔积


4
奇怪,它对我有用。您fetchType从中删除了@*ToMany吗?
博若

101
问题在于,对JPA批注进行了解析,以允许不超过2个热切加载的集合。但是休眠特定的注释允许它。
博若

14
超过1种EAGER的需求似乎是完全现实的。这个限制仅仅是JPA的监督吗?有多个EAGER我应该寻找什么问题?
AR3Y35

6
事实是,休眠状态无法通过一个查询获取两个集合。因此,当您查询父实体时,每个结果将需要两个额外的查询,这通常是您不想要的。
博佐

7
解释为什么会解决问题,这真是太好了。
Webnet

290

只需在List类型之间进行更改Set

但是请注意,您不会消除Vlad Mihalcea在其答案中描述的底层笛卡尔乘积


42
列表和集合不是同一件事:集合不能保留顺序
Matteo 2012年

17
LinkedHashSet保留顺序
egallardo

15
这是一个重要的区别,当您考虑它时,这是完全正确的。由数据库中的外键实现的典型的多对一实际上不是列表,而是集合,因为不保留顺序。所以Set确实更合适。我认为这会使冬眠有所作为,尽管我不知道为什么。
fool4jesus 2012年

3
我当时无法同时获取多个包,但不是因为注释。就我而言,我正在与两者进行左连接和分离*ToMany。更改类型也Set解决了我的问题。出色而整洁的解决方案。这应该是官方的答案。
L. Holanda 2014年

20
我喜欢这个答案,但是百万美元的问题是:为什么?为什么用Set不显示异常?谢谢
Hinotori

140

在代码中添加特定于Hibernate的@Fetch批注:

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

这应该可以解决与Hibernate错误HHH-1718相关的问题


5
@DaveRlz为什么subSelect解决了此问题。我尝试了您的解决方案及其工作原理,但不知道如何使用此解决方案?
HakunaMatata

除非Set确实有道理,否则这是最佳答案。在查询中使用结果具有单一OneToMany关系,Set1+<# relationships>查询中使用FetchMode.SUBSELECT结果具有单一关系1+1。同样,在接受的答案(LazyCollectionOption.FALSE)中使用注释会导致执行更多查询。
mstrthealias 2015年

1
FetchType.EAGER不适用于此解决方案。需要继续进行Hibernate Fetch Profiles并需要解决它
Milinda Bandara

2
另外两个最重要的答案并不能解决我的问题。这个做了。谢谢!
Blindworks

3
有谁知道为什么SUBSELECT可以解决这个问题,而JOIN不能解决?
Innokenty

41

这个问题一直是StackOverflow或Hibernate论坛上反复出现的主题,因此我决定也将答案变成一篇文章

考虑到我们具有以下实体:

在此处输入图片说明

并且,您希望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不允许获取多个包,因为这会生成笛卡尔积

最糟糕的“解决方案”

现在,您会发现很多答案,博客文章,视频或其他资源,它们告诉您对集合使用a Set代替a List

那是可怕的建议。不要那样做!

使用Sets而不是ListsMultipleBagFetchException消失,但笛卡尔乘积仍将存在,实际上甚至更糟,因为在应用此“修复”后很长时间您会发现性能问题。

正确的解决方案

您可以执行以下技巧:

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_THROUGHJPA查询提示设置为false

DISTINCT在JPQL中有两个含义,在这里,我们需要它对getResultListJava端而不是SQL端返回的Java对象引用进行重复数据删除。请查看本文以获取更多详细信息。

只要您使用最多获取一个集合JOIN FETCH,就可以了。

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

还有更多您可以做

如果您FetchType.EAGER在映射时间为@OneToMany@ManyToMany关联时使用该策略,那么您很容易以结束MultipleBagFetchException

最好从切换到FetchType.EAGERFetchype.LAZY因为急切的获取是一个可怕的想法,可能会导致严重的应用程序性能问题

结论

避免FetchType.EAGER并且不要从切换到ListSet仅仅是因为这样做会使Hibernate隐藏MultipleBagFetchException在地毯下面。一次只获取一个集合,就可以了。

只要您使用与要初始化的集合相同数量的查询来执行此操作,就可以了。只是不要在循环中初始化集合,否则会触发N + 1个查询问题,这也对性能不利。


感谢您的共享知识。但是, DISTINCT此解决方案是性能杀手。有办法摆脱distinct吗?(尝试返回Set<...>,但没有太大帮助)
Leonid Dashko

1
DISTINCT不会转到SQL语句。这PASS_DISTINCT_THROUGH就是设置为的原因false。DISTINCT在JPQL中有2个含义,在这里,我们需要它在Java方面而不是SQL方面进行重复数据删除。请查看本文以获取更多详细信息。
Vlad Mihalcea

弗拉德(Vlad),感谢您的帮助,我发现它真的很有用。但是,问题是有关的hibernate.jdbc.fetch_size(最终我将其设置为350)。碰巧,您知道如何优化嵌套关系吗?例如,实体1->实体2->实体3.1,实体3.2(其中,实体3.1 / 3.2是@OneToMany关系)
Leonid Dashko,

1
@LeonidDashko查看我的《高性能Java持久性》一书中的“获取”一章,以获取与获取数据有关的许多技巧。
Vlad Mihalcea,

1
你不能。用SQL来考虑。您不能加入多个一对多关联而不生成笛卡尔乘积。
Vlad Mihalcea

31

在尝试了本文和其他文章中介绍的每个选项之后,我得出的结论是,解决方法是遵循的。

在每个XToMany位置@ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) 以及之后

@Fetch(value = FetchMode.SUBSELECT)

这对我有用


5
添加@Fetch(value = FetchMode.SUBSELECT)就够了
user2601995

1
这是仅适用于Hibernate的解决方案。如果您使用共享的JPA库怎么办?
米歇尔

3
我确定您不是故意的,但是DaveRlz在3年前已经写过同样的东西
phil294 '18

21

要解决此问题,只需Set替换List您的嵌套对象即可。

@OneToMany
Set<Your_object> objectList;

而且不要忘记使用 fetch=FetchType.EAGER

它会工作。

CollectionId如果您只想使用列表,则Hibernate中还有一个概念。

但是请注意,您不会消除Vlad Mihalcea在其答案中描述的底层笛卡尔乘积



6

您可以将展位EAGER列表保留在JPA中,并向其中至少一个添加JPA批注@OrderColumn(显然是要订购的字段的名称)。无需特定的休眠注释。但是请记住,如果所选字段的值从0开始,则可能会在列表中创建空元素

 [...]
 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 @OrderColumn(name="orderIndex")
 private List<Child> children;
 [...]

在儿童中,您应该添加orderIndex字段


2

我们尝试用Set代替List,这是一场噩梦:添加两个新对象时,equals()和hashCode()无法区分它们!因为他们没有任何ID。

诸如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是一件可怕的事情。


1

如果您的保存对象集合中的对象太复杂,则最好不要将所有对象都使用EAGER fetchType,最好使用LAZY,而当您真正需要加载集合时,请使用:Hibernate.initialize(parent.child)来获取数据。


0

对我来说,问题在于嵌套了EAGER提取。

一种解决方案是将嵌套字段设置为LAZY并使用Hibernate.initialize()加载嵌套字段:

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

0

最后,当我使用FetchType.EAGER进行多个集合时,会发生这种情况,如下所示:

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

此外,这些集合正在同一列上。

为了解决此问题,我将其中一个集合更改为FetchType.LAZY,因为这对于我的用例来说还可以。

祝好运!〜J


0

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

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

0

一件好事@LazyCollection(LazyCollectionOption.FALSE)FetchType.EAGER,即使在这种共存合法的情况下,带有此批注的多个字段也可以共存而不能共存。

例如,一个Order可能有一个OrderGroup(简短的)列表,Promotions也有一个(也简短的)列表。@LazyCollection(LazyCollectionOption.FALSE)可以在两者上都使用,而不会导致LazyInitializationException两者都没有MultipleBagFetchException

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


-5

您可以使用新的注释来解决此问题:

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

实际上,提取的默认值也是FetchType.LAZY。


5
JPA3.0不存在。
holmis83 '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.