Hibernate 抛出 MultipleBagFetchException - 无法同时获取多个包
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
来自 Parent
或 AnotherParent
解决了问题,但我需要它,所以真正的解决方案是使用 @LazyCollection(LazyCollectionOption.FALSE)
而不是 < code>FetchType (感谢 Bozho 提供解决方案)。
Hibernate throws this exception during SessionFactory creation:
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
This is my test case:
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;
}
How about this problem? What can I do?
EDIT
OK, the problem I have is that another "parent" entity is inside my parent, my real behavior is this:
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 doesn't like two collections with FetchType.EAGER
, but this seems to be a bug, I'm not doing unusual things...
Removing FetchType.EAGER
from Parent
or AnotherParent
solves the problem, but I need it, so real solution is to use @LazyCollection(LazyCollectionOption.FALSE)
instead of FetchType
(thanks to Bozho for the solution).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(18)
我认为较新版本的 hibernate(支持 JPA 2.0)应该可以处理这个问题。但除此之外,您可以通过使用以下方式注释集合字段来解决此问题:
请记住从
@*ToMany
注释中删除fetchType
属性。但请注意,在大多数情况下,
Set
比List
更合适,因此除非您确实需要List
- 请使用对于Set
请谨慎使用
请记住,使用
Set
不会消除底层笛卡尔积,如< a href="https://stackoverflow.com/a/51055523/1657465">Vlad Mihalcea 在他的回答中,被称为“最糟糕的解决方案”!I think a newer version of hibernate (supporting JPA 2.0) should handle this. But otherwise you can work it around by annotating the collection fields with:
Remember to remove the
fetchType
attribute from the@*ToMany
annotation.But note that in most cases a
Set<Child>
is more appropriate thanList<Child>
, so unless you really need aList
- go forSet
Use with caution
Remember that using a
Set
won't eliminate the underlying Cartesian Product as described by Vlad Mihalcea in his answer, featured as "The worst solution"!只需从
List
类型更改为Set
类型即可。谨慎使用
不建议使用此解决方案,因为它不会消除底层笛卡尔积,如 Vlad Mihalcea 在他的回答中,被列为“最糟糕的解决方案”!
Simply change from
List
type toSet
type.Use with caution
This solution is not recommended as it won't eliminate the underlying Cartesian Product as described by Vlad Mihalcea in his answer, featured as "The worst solution"!
考虑到我们有以下实体:
并且,您想要获取一些父
Post
实体以及所有comments
和tag集合。
如果您使用多个
JOIN FETCH
指令:Hibernate 将抛出臭名昭著的错误:
Hibernate 不允许获取多个包,因为这会生成笛卡尔积。
最糟糕的“解决方案”
现在,您会发现很多答案、博客文章、视频或其他资源告诉您在集合中使用
Set
而不是List
。这是个糟糕的建议。不要这样做!
使用
Sets
而不是Lists
将使MultipleBagFetchException
消失,但笛卡尔积仍将是在那里,这实际上更糟糕,因为在应用此“修复”很久之后您就会发现性能问题。正确的 Hibernate 6 解决方案
如果您使用 Hibernate 6,那么您可以像这样解决此问题:
只要您在每个查询中使用
JOIN FETCH
最多获取一个集合,就可以了。通过使用多个查询,您将避免使用笛卡尔积,因为任何其他集合,但第一个查询是使用辅助查询获取的。
正确的 Hibernate 5 解决方案
您可以执行以下技巧:
通过使用多个查询,您将避免使用笛卡尔积,因为任何其他集合,但第一个查询是使用辅助查询获取的。
您还可以做更多事情
如果您在映射
@OneToMany
或@ManyToMany
关联时使用FetchType.EAGER
策略,那么您可以很容易以MultipleBagFetchException
结束。您最好从
FetchType.EAGER
切换到Fetchype.LAZY
,因为急切获取是一个糟糕的想法,可能会导致严重的应用程序性能问题。结论
避免
FetchType.EAGER
并且不要从List
切换到Set
仅仅因为这样做会让 Hibernate 隐藏MultipleBagFetchException在地毯下。一次只获取一个集合,就可以了。
只要您使用与要初始化的集合相同数量的查询来执行此操作,就可以了。只是不要在循环中初始化集合,因为这会触发 N+1 查询问题,这也会影响性能。
Considering we have the following entities:
And, you want to fetch some parent
Post
entities along with all thecomments
andtags
collections.If you are using more than one
JOIN FETCH
directives:Hibernate will throw the infamous:
Hibernate doesn't allow fetching more than one bag because that would generate a Cartesian product.
The worst "solution"
Now, you will find lots of answers, blog posts, videos, or other resources telling you to use a
Set
instead of aList
for your collections.That's terrible advice. Don't do that!
Using
Sets
instead ofLists
will make theMultipleBagFetchException
go away, but the Cartesian Product will still be there, which is actually even worse, as you'll find out the performance issue long after you applied this "fix".The proper Hibernate 6 solution
If you're using Hibernate 6, then you can fix this issue like this:
As long as you fetch at most one collection using
JOIN FETCH
per query, you will be fine.By using multiple queries, you will avoid the Cartesian Product since any other collection, but the first one is fetched using a secondary query.
The proper Hibernate 5 solution
You can do the following trick:
By using multiple queries, you will avoid the Cartesian Product since any other collection, but the first one is fetched using a secondary query.
There's more you could do
If you're using the
FetchType.EAGER
strategy at mapping time for@OneToMany
or@ManyToMany
associations, then you could easily end up with aMultipleBagFetchException
.You are better off switching from
FetchType.EAGER
toFetchype.LAZY
since eager fetching is a terrible idea that can lead to critical application performance issues.Conclusion
Avoid
FetchType.EAGER
and don't switch fromList
toSet
just because doing so will make Hibernate hide theMultipleBagFetchException
under the carpet. Fetch just one collection at a time, and you'll be fine.As long as you do it with the same number of queries as you have collections to initialize, you are fine. Just don't initialize the collections in a loop, as that will trigger N+1 query issues, which are also bad for performance.
在代码中添加 Hibernate 特定的 @Fetch 注释:
这应该可以解决与 Hibernate bug HHH 相关的问题-1718
Add a Hibernate-specific @Fetch annotation to your code:
This should fix the issue, related to Hibernate bug HHH-1718
在尝试了这篇文章和其他文章中描述的每个选项之后,我得出的结论是修复如下。
在每个 XToMany 位置 @
XXXToMany(mappedBy="parent", fetch=FetchType.EAGER)
之后
这对我有用
After trying with every single option describe in this posts and others, I came to the conclusion that the the fix is a follows.
In every XToMany place @
XXXToMany(mappedBy="parent", fetch=FetchType.EAGER)
and intermediately after
This worked for me
要修复此问题,只需将嵌套对象的
Set
替换为List
即可。并且不要忘记使用
fetch=FetchType.EAGER
,它会起作用的。如果您只想坚持使用列表,Hibernate 中还有一个概念
CollectionId
。谨慎使用
请记住,您不会消除底层笛卡尔积,如Vlad Mihalcea 在他的回答中,被称为“最糟糕的解决方案”!
To fix it simply take
Set
in place ofList
for your nested object.And don't forget to use
fetch=FetchType.EAGER
, It'll work.There is one more concept
CollectionId
in Hibernate if you want to stick with list only.Use with caution
Remember that you won't eliminate the underlying Cartesian Product as described by Vlad Mihalcea in his answer, featured as "The worst solution"!
您可以在 JPA 中保留展位 EAGER 列表,并向其中至少一个添加 JPA 注释 @OrderColumn (显然带有要订购的字段的名称)。不需要特定的休眠注释。
但请记住,如果所选字段在 Children 中没有从 0 开始的值,则可能会在列表中创建空元素
,那么您应该添加 orderIndex 字段
you can keep booth EAGER lists in JPA and add to at least one of them the JPA annotation @OrderColumn (with obviously the name of a field to be ordered). No need of specific hibernate annotations.
But keep in mind it could create empty elements in the list if the chosen field does not have values starting from 0
in Children then you should add the orderIndex field
当您的保存集合的对象过于复杂时,使用 EAGER fetchType 并不是一个好主意,最好使用 LAZY,当您确实需要加载集合时,请使用:
Hibernate.initialize(parent.child)
Hibernate.initialize(parent.child) 来获取数据。When you have too complex objects with saveral collection could not be good idea to have all of them with EAGER fetchType, better use LAZY and when you really need to load the collections use:
Hibernate.initialize(parent.child)
to fetch the data.我们尝试使用 Set 而不是 List,这真是一场噩梦:当您添加两个新对象时,equals() 和 hashCode() 无法区分它们!因为他们没有身份证。
Eclipse 等典型工具会从数据库表生成此类代码:
您还可以阅读 这篇文章 正确地解释了 JPA/Hibernate 是如何混乱的。读完这篇文章后,我想这是我一生中最后一次使用 ORM。
我也遇到过领域驱动设计人员,他们基本上说 ORM 是一件可怕的事情。
We tried Set instead of List and it is a nightmare: when you add two new objects, equals() and hashCode() fail to distinguish both of them ! Because they don't have any id.
typical tools like Eclipse generate that kind of code from Database tables:
You may also read this article that explains properly how messed up JPA/Hibernate is. After reading this, I think this is the last time I use any ORM in my life.
I've also encounter Domain Driven Design guys that basically say ORM are a terrible thing.
对我来说,问题在于嵌套的EAGER获取。
一种解决方案是将嵌套字段设置为 LAZY 并使用 Hibernate.initialize() 加载嵌套字段:
For me, the problem was having nested EAGER fetches.
One solution is to set the nested fields to LAZY and use Hibernate.initialize() to load the nested field(s):
最后,当我使用 FetchType.EAGER 拥有多个集合时,就会发生这种情况,如下所示:
此外,这些集合连接在同一列上。
为了解决这个问题,我将其中一个集合更改为 FetchType.LAZY,因为它适合我的用例。
祝你好运!
~J
At my end, this happened when I had multiple collections with FetchType.EAGER, like this:
Additionally, the collections were joining on the same column.
To solve this issue, I changed one of the collections to FetchType.LAZY since it was okay for my use-case.
Goodluck!
~J
您还可以尝试使 fetch=FetchType.LAZY 并将 @Transactional(readOnly = true) 添加到获取子项的方法中
You can also try to make fetch=FetchType.LAZY and just add @Transactional(readOnly = true) to method where you get child
TLDR:不要使用 EAGER。始终仅在您真正需要时才取东西!
这里提到了很多关于 FetchType.EAGER 的内容,我想更详细地说明为什么这是一个坏主意以及应该使用什么。希望读完本文后您意识到您绝对不应该永远使用
FetchType.EAGER
。只是没有充分的理由!这是一个市长陷阱,可能会在未来咬住你(甚至更糟糕的是其他人)。假设您为学生选择了 FetchType.EAGER ->老师关系,因为你认为每次你需要去接学生时,你也需要他的老师。现在,即使您现在百分百确定自己永远不需要没有老师的学生,您也无法预见需求将如何变化。 FetchType.EAGER 违反了开闭原则!您的代码不再开放扩展 - 如果以后需要加载没有教师的学生,那么在不重新设计(并且可能破坏)现有代码的情况下很难做到这一点!
您遇到的一个更大的问题是,您基本上为将来可能的查询创建了一个 n+1 选择问题,(正确地)已经在基本查询中获取了另一个包。假设将来有人想要加载所有学生的成绩,其中有 30000 个。既然你告诉 Hibernate EAGER 获取所有教师,它就必须这样做。但由于您已经在同一查询中获取了另一个“包”(成绩),这实际上会导致 30001 次查询 - 对于该场景中甚至不需要的数据!第一个查询加载所有学生+成绩,然后为每个学生单独查询以获取他的老师。不用说,这对于性能来说是可怕的。在我看来,这是人们相信“Hibernate 很慢”的唯一原因 - 他们只是没有意识到在某些情况下它可能查询东西的效率有多低。你真的必须小心 1:n 关系。
3 个替代方案(需要时手动获取内容):
SELECT Stud FROM STUDENT Stud JOIN FETCH Stud.teachers
就可以了。这将始终触发一个查询来获取学生和教师。请注意,仅以这种方式获取一个集合(对于这个答案来说解释太过分了)。最后还有一个一般提示:将 org.hibernate.SQL 的日志记录设置为 DEBUG,以查看何时/触发哪些查询,并确保本地开发设置具有合理数量的数据。当您仅与 2 名学生测试代码并且不检查触发的查询时,您可能会错过类似的内容,并最终在具有更多数据的真实系统上出现问题。
TLDR: Don't use EAGER. Always fetch things only when you really need them!
A lot of mentions here about
FetchType.EAGER
and I wanted to go a bit more into detail why this is a bad idea and on what to use alternatively. Hopefully after reading this you realize that really you should absolutely NEVER useFetchType.EAGER
.There is just no good reason to! It's a mayor pitfall that can bite you (or even worse: someone else) down the road. Imagine you chose FetchType.EAGER for a Student -> Teacher relation, because you think every time you need to fetch a Student you also need his Teachers. Now even if you are 100% sure you never need students without teachers right now, you simply can't foresee how requirements change. FetchType.EAGER violates the Open-closed principle! Your code is no longer open for extension - if later the need for loading Students without Teachers arise it's difficult do to that without reworking (and possibly breaking) existing code!
An even bigger problem you have is that you basically created an n+1 select problem for possible future queries, that (rightfully) already fetched another bag in the basic query. Let's say in the future someone wants to load all Students with their grades and there are 30000 of them. Since you told Hibernate to EAGER fetch all Teachers it has to do that. But since you already fetched another "bag" (the grades) within the same query this actually results in 30001 queries - for data that wasn't even needed in that scenario! The first query for loading all Students+Grades and then a separate query for each Student to fetch his teachers. Needless to say that this is horrendous for performance. In my opinion this is the sole reason for people to believe that "Hibernate is slow" - they just don't realize how incredible inefficient it might query stuff in some situations. You really have to be careful with 1:n relations.
3 Alternatives (manually fetch stuff when needed):
SELECT stud FROM STUDENT stud JOIN FETCH stud.teachers
would do the trick. This will always fire a single query to fetch students AND teachers. Just be mindful to only fetch one collection that way (explanation would go too far for this answer).Also one final tip in general: turn the logging for org.hibernate.SQL to DEBUG to see when/what queries are fired and make sure your local dev setup has a reasonable amount of data. When you test your code with only 2 students and don't check the queries that are fired you might miss something like that and end up having issues on real systems with more data.
注释
Fetch
和LazyCollection
有时有助于运行项目。Commenting both
Fetch
andLazyCollection
sometimes helps to run project.@LazyCollection(LazyCollectionOption.FALSE)
的一个好处是,带有此注释的多个字段可以共存,而FetchType.EAGER
则不能共存,即使在这种共存是合法的情况下也是如此。例如,
Order
可能有一个OrderGroup
列表(很短)以及一个Promotions
列表(也很短)。@LazyCollection(LazyCollectionOption.FALSE)
可以在两者上使用,而不会导致LazyInitializationException
和MultipleBagFetchException
。就我而言,@Fetch 确实解决了我的 MultipleBacFetchException 问题,但随后导致了 LazyInitializationException,即臭名昭著的 no Session 错误。
One good thing about
@LazyCollection(LazyCollectionOption.FALSE)
is that several fields with this annotation can coexist whileFetchType.EAGER
cannot, even in the situations where such coexistence is legit.For example, an
Order
may have a list ofOrderGroup
(a short one) as well as a list ofPromotions
(also short).@LazyCollection(LazyCollectionOption.FALSE)
can be used on both without causingLazyInitializationException
neitherMultipleBagFetchException
.In my case
@Fetch
did solve my problem ofMultipleBacFetchException
but then causesLazyInitializationException
, the infamousno Session
error.我通过注释解决了:
I solved by annotating:
好的,这是我的 2 美分。我在实体中有 Fetch Lazy 注释,但我也在会话 bean 中复制了 fetch Lazy 注释,从而导致了多包问题。 中的行
所以我只是删除了 SessionBean criteria.createAlias("FIELD", "ALIAS", JoinType.LEFT_OUTER_JOIN); //REMOVED
我在调用 Listparent = criteria.list 后想要检索的列表上使用了 Hibernate.initialize 。
Hibernate.initialize(parent.getChildList());
Ok so here's my 2 cents. I had the Fetch Lazy annotations in my Entity but I also duplicated the fetch lazy in the session bean thus causing a multiple bag issue. So I just removed the lines in my SessionBean
criteria.createAlias("FIELD", "ALIAS", JoinType.LEFT_OUTER_JOIN); //REMOVED
And I used Hibernate.initialize on the list I wanted to retrieve after the List parent = criteria.list was called.
Hibernate.initialize(parent.getChildList());
您可以使用新的注释来解决这个问题:
事实上,fetch 的默认值也是 FetchType.LAZY。
You could use a new annotation to solve this:
In fact, fetch's default value is FetchType.LAZY too.