Hibernate无法同时获取多个行李

blo*_*low 428 java hibernate jpa

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;

}
Run Code Online (Sandbox Code Playgroud)

Child.java

@Entity
public Child {

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

 @ManyToOne
 private Parent parent;

}
Run Code Online (Sandbox Code Playgroud)

这个问题怎么样?我能做什么?


编辑

好吧,我遇到的问题是另一个"父"实体在我父母的内部,我的真实行为是这样的:

Parent.java

@Entity
public Parent {

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

 @ManyToOne
 private AntoherParent anotherParent;

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

}
Run Code Online (Sandbox Code Playgroud)

AnotherParent.java

@Entity
public AntoherParent {

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

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<AnotherChild> anotherChildren;

}
Run Code Online (Sandbox Code Playgroud)

Hibernate不喜欢两个集合FetchType.EAGER,但这似乎是一个bug,我没有做不寻常的事情......

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

Boz*_*zho 513

我认为更新版本的hibernate(支持JPA 2.0)应该可以解决这个问题.但是否则你可以通过使用以下方法注释集合字段来解决它:

@LazyCollection(LazyCollectionOption.FALSE)
Run Code Online (Sandbox Code Playgroud)

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

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

  • 问题是解析JPA注释不允许超过2个急切加载的集合.但是特定于hibernate的注释允许它. (93认同)
  • 对超过1个EAGER的需求似乎是完全现实的.这种限制只是JPA的疏忽吗?拥有多个EAGER时我应该注意什么? (14认同)
  • 很好地解释为什么这可以解决问题. (7认同)
  • 问题是,hibernate无法通过一个查询获取两个集合.因此,当您查询父实体时,每个结果将需要2个额外查询,这通常是您不想要的. (6认同)
  • 奇怪,它对我有用.你从`@*ToMany`中删除了`fetchType`吗? (4认同)
  • 在我的案例中,从列表更改为集合有帮助.竖起大拇指! (3认同)

小智 279

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

  • 列表和集合不是一回事:集合不保留顺序 (41认同)
  • LinkedHashSet保留顺序 (17认同)
  • 我喜欢这个答案,但百万美元的问题是:为什么?为什么使用Set不显示异常?谢谢 (17认同)
  • 这是一个重要的区别,当你想到它时,完全正确.由DB中的外键实现的典型多对一实际上不是List,它是一个Set,因为订单不会被保留.所以Set真的更合适.我认为这在休眠方面有所不同,但我不知道为什么. (15认同)
  • 我有同样的**不能同时取多个袋**但不是因为注释.在我的情况下,我正在与两个`*ToMany`进行左连接和分离.将类型更改为"Set"也解决了我的问题.优秀而整洁的解决方案.这应该是官方的答案. (3认同)
  • @OndrejBozek 但是你保留什么顺序?我们正在谈论数据库中的外键关系。DB 可以(并且将)按照最方便的顺序返回项目。如果您真的想控制顺序,则需要向 fetch 查询添加一些排序规则(不确定是否/如何使用 JPA 完成),并且可能添加一些索引字段进行排序。 (3认同)
  • 我有一个完全有效的用例,在使用List时出现了同样的异常。我按照艾哈迈德(Ahmad)的建议更改了代码,现在我全都“设置”了。:) (2认同)

Dav*_*Rlz 124

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

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

这应该解决与Hibernate bug HHH-1718相关的问题

  • @DaveRlz为什么subSelect解决了这个问题.我尝试了你的解决方案和它的工作,但不知道这个问题是如何解决的? (5认同)
  • 这是最好的答案,除非“Set”确实有意义。使用“Set”建立单个“OneToMany”关系会导致“1+&lt;#relationships&gt;”查询,而使用“FetchMode.SUBSELECT”会导致“1+1”查询。此外,在接受的答案中使用注释(“LazyCollectionOption.FALSE”)会导致执行更多查询。 (4认同)
  • 有谁知道为什么SUBSELECT可以解决这个问题,而JOIN不能解决? (3认同)
  • 另外两个最重要的答案并不能解决我的问题。这个做了。谢谢! (2认同)

小智 28

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

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

@Fetch(value = FetchMode.SUBSELECT)
Run Code Online (Sandbox Code Playgroud)

这对我有用

  • 添加`@Fetch(value = FetchMode.SUBSELECT)就够了 (4认同)
  • 我确定您不是故意的,但是DaveRlz在3年前已经写过同样的东西 (2认同)

Pra*_*ngh 20

要修复它,只需Set代替List嵌套对象.

@OneToMany
Set<Your_object> objectList;
Run Code Online (Sandbox Code Playgroud)

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

它会工作.

CollectionId如果你只想坚持使用列表,那么Hibernate 还有一个概念.


Chr*_*ler 11

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

  • 欢迎来到Stack Overflow.但是,通常不鼓励仅链接答案.请参阅faq,特别是:http://stackoverflow.com/faq#deletion. (4认同)

Vla*_*cea 9

这个问题一直是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();
Run Code Online (Sandbox Code Playgroud)

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
]
Run Code Online (Sandbox Code Playgroud)

Hibernate不允许获取一个以上的包,因为这会生成笛卡尔积

最糟糕的“解决方案”

现在,您会发现很多答案,博客文章,视频或其他资源,它们告诉您对集合使用a Set代替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();
Run Code Online (Sandbox Code Playgroud)

在第一个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个查询问题,这也对性能不利。

  • 当然。查询更加明显,并且没有 IN 子句限制问题。 (3认同)
  • @JayD 我将来会在博客上讨论这一点。 (3认同)
  • 弗拉德,感谢您的帮助,我发现它非常有用。然而,这个问题与“hibernate.jdbc.fetch_size”有关(最终我将其设置为350)。顺便问一下,你知道如何优化嵌套关系吗?例如,实体1 -&gt; 实体2 -&gt; 实体3.1,实体3.2(其中实体3.1 / 3.2 是@OneToMany 关系) (2认同)
  • 你不能。从 SQL 的角度考虑一下。您无法在不生成笛卡尔积的情况下 JOIN 多个一对多关联。 (2认同)
  • 您应该始终在调用 Spring Data Jpa 存储库的服务方法上使用 @Transactional。不这样做是一个可怕的错误。 (2认同)
  • 我试图将其与“@NamedEntityGraph”带注释的实体类一起使用。除非我删除图形逻辑,否则它不起作用。实体图技术有什么优点吗? (2认同)
  • 如果您使用任何策略获取两个同级 Set 关联,您将获得笛卡尔积。如果关联是相关的,例如子孙,那么您将不会得到笛卡尔积。 (2认同)
  • 这取决于。对于可嵌入的,Set 更好。对于双向的一对多关联来说,这并不重要,但是List的用途更加广泛。对于多对多,集合更好。您可以在我的《高性能 Java 持久性》一书中找到详细的解释。 (2认同)

Mas*_*imo 6

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

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

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