ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

我会问,您希望生成什么 SQL 查询来同时检索两个单独的集合?能够实现这些的 SQL 类型要么需要笛卡尔连接(可能非常低效),要么需要不相交列的 UNION(也很丑陋)。据推测,无法在 SQL 中以干净高效的方式实现这一点影响了 API 设计。
@ThomasW 这些是它应该生成的 sql 查询:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
如果您有多个 List<child> 并且为 多个 List<clield> 定义了 fetchType,您可能会收到类似的错误

b
bish

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

@LazyCollection(LazyCollectionOption.FALSE)

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

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

但请注意,使用集合您不会消除 Vlad Mihalcea in his answer 所述的底层笛卡尔积


奇怪,它对我有用。您是否从 @*ToMany 中删除了 fetchType
问题是 JPA 注释被解析为不允许超过 2 个急切加载的集合。但是特定于休眠的注释允许它。
需要超过 1 个 EAGER 似乎是完全现实的。这个限制只是 JPA 的疏忽吗?拥有多个 EAGER 时我应该注意哪些问题?
问题是,hibernate 无法通过一个查询获取两个集合。因此,当您查询父实体时,每个结果将需要 2 个额外的查询,这通常是您不想要的。
很高兴能解释为什么这可以解决问题。
b
bish

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

但请注意,您不会消除 Vlad Mihalcea in his answer 所述的底层笛卡尔积


List 和 Set 不是一回事:集合不保持顺序
LinkedHashSet 保留顺序
这是一个重要的区别,当您考虑它时,它是完全正确的。由数据库中的外键实现的典型多对一实际上不是一个列表,而是一个集合,因为没有保留顺序。所以Set确实更合适。我认为这在休眠中有所不同,尽管我不知道为什么。
我有相同的不能同时提取多个包,但不是因为注释。就我而言,我正在对两个 *ToMany 进行左连接和析取。将类型更改为 Set 也解决了我的问题。优秀而整洁的解决方案。这应该是官方的回答。
我喜欢这个答案,但百万美元的问题是:为什么?为什么 Set 不显示异常?谢谢
D
Dave Richardson

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

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

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


@DaveRlz 为什么 subSelect 解决了这个问题。我尝试了您的解决方案及其工作原理,但不知道使用此解决方案如何解决问题?
这是最好的答案,除非 Set 真的有意义。使用 Set 建立单个 OneToMany 关系会导致 1+<# relationships> 查询,而使用 FetchMode.SUBSELECT 会导致 1+1 查询。此外,在接受的答案 (LazyCollectionOption.FALSE) 中使用注释会导致执行更多查询。
FetchType.EAGER 不是一个合适的解决方案。需要继续使用 Hibernate Fetch Profiles 并需要解决它
另外两个最佳答案并没有解决我的问题。这个做到了。谢谢!
有谁知道为什么 SUBSELECT 修复它,但 JOIN 没有?
V
Vlad Mihalcea

考虑到我们有以下实体:

https://i.stack.imgur.com/fVIXG.png

而且,您想要获取一些父 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,因为 Eager fetch 是一个糟糕的想法,可能会导致严重的应用程序性能问题。

结论

避免使用 FetchType.EAGER 并且不要从 List 切换到 Set,因为这样做会使 Hibernate 将 MultipleBagFetchException 隐藏在地毯下。一次只取一个集合,你会没事的。

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


弗拉德,感谢您的帮助,我发现它非常有用。但是,该问题与 hibernate.jdbc.fetch_size 有关(最终我将其设置为 350)。碰巧,你知道如何优化嵌套关系吗?例如 entity1 ->实体2-> entity3.1,实体 3.2(其中 entity3.1 / 3.2 是 @OneToMany 关系)
你不能。从 SQL 的角度考虑它。如果不生成笛卡尔积,则无法加入多个一对多关联。
您应该始终在调用 Spring Data Jpa 存储库的服务方法上使用 @Transactional。不这样做是一个可怕的错误。
当然。查询更明显,不存在 IN 子句限制问题。
我试图将它与 @NamedEntityGraph 带注释的实体类一起使用。它不起作用,除非我删除图形逻辑。实体图技术有什么优点吗?
A
Abimaran Kugathasan

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

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

@Fetch(value = FetchMode.SUBSELECT)

这对我有用


添加 @Fetch(value = FetchMode.SUBSELECT) 就足够了
这是一个 Hibernate 唯一的解决方案。如果您使用的是共享 JPA 库怎么办?
我敢肯定你不是故意的,但是 DaveRlz 已经在 3 年前写过同样的东西
b
bish

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

@OneToMany
Set<Your_object> objectList;

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

它会起作用的。

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

但请注意,您不会消除 Vlad Mihalcea in his answer 所述的底层笛卡尔积


您提出的两个建议在性能方面都非常糟糕。
C
Christian Müller

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


欢迎来到堆栈溢出。但是,通常不鼓励仅链接的答案。请参阅常见问题解答,尤其是:stackoverflow.com/faq#deletion
M
Massimo

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

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

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


r
rimsky

我们尝试了 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;
}

您还可以阅读 this article,它正确解释了 JPA/Hibernate 的混乱程度。看完这篇,我想这是我这辈子最后一次使用 ORM 了。

我也遇到过领域驱动设计的人,他们基本上说 ORM 是一件可怕的事情。


F
Felipe Cadena

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


佚名

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

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

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

a
alxkudin

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


J
JAD

最后,这发生在我使用 FetchType.EAGER 有多个集合时,如下所示:

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

此外,这些集合在同一列中加入。

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

祝你好运! ~J


p
prisar

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

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

W
WesternGun

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

例如,Order 可能有一个 OrderGroup 列表(很短)和一个 Promotions 列表(也很短)。 @LazyCollection(LazyCollectionOption.FALSE) 可以同时用于两者,而不会导致 LazyInitializationExceptionMultipleBagFetchException

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


A
Antonio Petricca

我通过注释解决了:

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

K
Karen Hechter

好的,这是我的 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());


l
leee41

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

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

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


JPA3.0 不存在。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅