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
的两个集合,但这似乎是一个错误,我没有做不寻常的事情......
从 Parent
或 AnotherParent
中删除 FetchType.EAGER
可以解决问题,但我需要它,所以真正的解决方案是使用 @LazyCollection(LazyCollectionOption.FALSE)
而不是 FetchType
(感谢 Bozho 的解决方案)。
select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
List<child>
并且为 多个 List<clield>
定义了 fetchType
,您可能会收到类似的错误
我认为更新版本的休眠(支持 JPA 2.0)应该处理这个问题。但除此之外,您可以通过以下方式注释集合字段来解决它:
@LazyCollection(LazyCollectionOption.FALSE)
请记住从 @*ToMany
注释中删除 fetchType
属性。
但请注意,在大多数情况下,Set<Child>
比 List<Child>
更合适,所以除非您真的需要 List
- 选择 Set
但请注意,使用集合您不会消除 Vlad Mihalcea in his answer 所述的底层笛卡尔积!
只需从 List
类型更改为 Set
类型。
但请注意,您不会消除 Vlad Mihalcea in his answer 所述的底层笛卡尔积!
*ToMany
进行左连接和析取。将类型更改为 Set
也解决了我的问题。优秀而整洁的解决方案。这应该是官方的回答。
在代码中添加特定于 Hibernate 的 @Fetch 注释:
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;
这应该可以解决与 Hibernate 错误 HHH-1718 相关的问题
Set
真的有意义。使用 Set
建立单个 OneToMany
关系会导致 1+<# relationships>
查询,而使用 FetchMode.SUBSELECT
会导致 1+1
查询。此外,在接受的答案 (LazyCollectionOption.FALSE
) 中使用注释会导致执行更多查询。
考虑到我们有以下实体:
https://i.stack.imgur.com/fVIXG.png
而且,您想要获取一些父 Post
实体以及所有 comments
和 tags
集合。
如果您使用多个 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 关系)
@NamedEntityGraph
带注释的实体类一起使用。它不起作用,除非我删除图形逻辑。实体图技术有什么优点吗?
在尝试了这篇文章和其他文章中描述的每一个选项之后,我得出的结论是,修复如下。
在每个 XToMany 地方 @XXXToMany(mappedBy="parent", fetch=FetchType.EAGER)
和中间之后
@Fetch(value = FetchMode.SUBSELECT)
这对我有用
@Fetch(value = FetchMode.SUBSELECT)
就足够了
要修复它,只需将 Set
替换为嵌套对象的 List
。
@OneToMany
Set<Your_object> objectList;
并且不要忘记使用 fetch=FetchType.EAGER
它会起作用的。
如果您只想坚持使用列表,Hibernate 中还有一个概念 CollectionId
。
但请注意,您不会消除 Vlad Mihalcea in his answer 所述的底层笛卡尔积!
我发现了一篇关于 Hibernate 在这种对象映射中的行为的好博客文章:http://blog.eyallupu.com/2010/06/hibernate-exception-simultaneously.html
您可以在 JPA 中保留展位 EAGER 列表,并在其中至少一个中添加 JPA 注释 @OrderColumn(显然是要订购的字段的名称)。不需要特定的休眠注释。但请记住,如果所选字段没有从 0 开始的值,它可能会在列表中创建空元素
[...]
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@OrderColumn(name="orderIndex")
private List<Child> children;
[...]
在 Children 那么你应该添加 orderIndex 字段
我们尝试了 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 是一件可怕的事情。
当您有过于复杂的对象时,使用 saveral 集合可能不是一个好主意,让所有对象都使用 EAGER fetchType,最好使用 LAZY,当您确实需要加载集合时,请使用:Hibernate.initialize(parent.child)
来获取数据。
对我来说,问题在于嵌套的 EAGER 提取。
一种解决方案是将嵌套字段设置为 LAZY 并使用 Hibernate.initialize() 加载嵌套字段:
x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());
您也可以尝试制作 fetch=FetchType.LAZY 并将 @Transactional(readOnly = true) 添加到您获取孩子的方法中
最后,这发生在我使用 FetchType.EAGER 有多个集合时,如下所示:
@ManyToMany(fetch = FetchType.EAGER, targetEntity = className.class)
@JoinColumn(name = "myClass_id")
@JsonView(SerializationView.Summary.class)
private Collection<Model> ModelObjects;
此外,这些集合在同一列中加入。
为了解决这个问题,我将其中一个集合更改为 FetchType.LAZY,因为它适合我的用例。
祝你好运! ~J
评论 Fetch
和 LazyCollection
有时有助于运行项目。
@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)
@LazyCollection(LazyCollectionOption.FALSE)
的一个好处是带有此注释的多个字段可以共存,而 FetchType.EAGER
则不能,即使在这种共存是合法的情况下也是如此。
例如,Order
可能有一个 OrderGroup
列表(很短)和一个 Promotions
列表(也很短)。 @LazyCollection(LazyCollectionOption.FALSE)
可以同时用于两者,而不会导致 LazyInitializationException
和 MultipleBagFetchException
。
在我的情况下,@Fetch
确实解决了我的 MultipleBacFetchException
问题,但随后导致了 LazyInitializationException
,即臭名昭著的 no Session
错误。
我通过注释解决了:
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
好的,这是我的 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());
您可以使用新注释来解决此问题:
@XXXToXXX(targetEntity = XXXX.class, fetch = FetchType.LAZY)
事实上,fetch 的默认值也是 FetchType.LAZY。
不定期副业成功案例分享
@*ToMany
中删除了fetchType
?