549

Hibernate 在 SessionFactory 创建期间抛出这个异常:

org.hibernate.loader.MultipleBagFetchException:不能同时获取多个包

这是我的测试用例:

父类.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;

}

子.java

@Entity
public Child {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private 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;

}

另一个父母.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,但我需要它,所以真正的解决方案是使用而不是(感谢Bozho的解决方案)。ParentAnotherParent@LazyCollection(LazyCollectionOption.FALSE)FetchType

4

18 回答 18

632

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

@LazyCollection(LazyCollectionOption.FALSE)

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

但请注意,在大多数情况下 aSet<Child>比 , 更合适List<Child>,所以除非你真的需要 a List-go forSet

但请注意,使用集合不会消除Vlad Mihalcea 在他的回答中描述的底层笛卡尔积

于 2010-12-02T13:30:30.943 回答
307

只需从List类型更改为Set类型。

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

于 2011-05-03T05:55:07.050 回答
176

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

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

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

于 2011-11-29T10:46:02.507 回答
140

考虑到我们有以下实体:

在此处输入图像描述

而且,您想获取一些父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 不允许获取多个包,因为这会生成笛卡尔积。

最糟糕的“解决方案”

现在,您会发现很多答案、博客文章、视频或其他资源告诉您在收藏中使用 aSet而不是 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 中有两个含义,在这里,我们需要它对 Java 端返回的 Java 对象引用进行去重getResultList,而不是 SQL 端。

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

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

你可以做的还有更多

如果您在为或关联FetchType.EAGER映射时使用该策略,那么您很容易以.@OneToMany@ManyToManyMultipleBagFetchException

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

结论

避免FetchType.EAGER并且不要仅仅因为这样ListSet会使 Hibernate 隐藏MultipleBagFetchException在地毯下。一次只取一个集合,你会没事的。

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

于 2018-06-27T06:01:16.863 回答
44

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

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

@Fetch(value = FetchMode.SUBSELECT)

这对我有用

于 2014-02-27T14:03:16.327 回答
18

要修复它,只需Set替换List嵌套对象即可。

@OneToMany
Set<Your_object> objectList;

不要忘记使用fetch=FetchType.EAGER

它会起作用的。

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

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

于 2014-09-04T20:29:58.870 回答
9

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

于 2013-01-11T15:52:34.270 回答
6

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

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

在 Children 那么你应该添加 orderIndex 字段

于 2017-04-25T08:53:08.170 回答
2

我们尝试了Set而不是List,这是一场噩梦:当你添加两个新对象时,equals () 和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 是一件可怕的事情。

于 2018-01-10T13:52:56.167 回答
1

当你有过于复杂的对象和 saveral 集合可能不是一个好主意,让所有这些对象都使用 EAGER fetchType,最好使用 LAZY 并且当你真的需要加载集合时使用:Hibernate.initialize(parent.child)来获取数据。

于 2015-10-15T13:38:21.040 回答
1

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

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

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());
于 2019-01-12T18:09:55.103 回答
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

于 2020-01-11T21:14:35.337 回答
0

评论两者FetchLazyCollection有时有助于运行项目。

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)
于 2020-02-27T05:47:43.570 回答
0

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

例如,一个Order可能有一个OrderGroup(短的)列表以及一个Promotions(也很短)的列表。@LazyCollection(LazyCollectionOption.FALSE)可以在两者上都使用而不会造成LazyInitializationException任何一个MultipleBagFetchException

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

于 2020-04-14T15:43:40.187 回答
0

我通过注释解决了:

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
于 2021-01-29T10:12:42.240 回答
0

好的,这是我的 2 美分。我在我的实体中有 Fetch Lazy 注释,但我还在会话 bean 中复制了 fetch lazy,从而导致了多包问题。所以我只是删除了 SessionBean 中的行

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

在调用 List parent = criteria.list 后,我​​在要检索的列表上使用了 Hibernate.initialize。Hibernate.initialize(parent.getChildList());

于 2021-08-26T12:11:41.270 回答
0

您也可以尝试制作 fetch=FetchType.LAZY 并将 @Transactional(readOnly = true) 添加到您获取孩子的方法中

于 2021-10-22T10:49:01.893 回答
-5

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

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

事实上,fetch 的默认值也是 FetchType.LAZY。

于 2017-06-14T03:21:07.783 回答