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)
вместо FetchType
(спасибо Божо,,, за решение).
Переведено автоматически
Ответ 1
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:
@LazyCollection(LazyCollectionOption.FALSE)
Remember to remove the fetchType
attribute from the @*ToMany
annotation.
But note that in most cases a Set<Child>
is more appropriate than List<Child>
, so unless you really need a List
- go for Set
Use with caution
Remember that using a Set
won't eliminate the underlying Cartesian Product as described by Vlad Mihalcea in his answer!
Ответ 2
Simply change from List
type to Set
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!
Ответ 3
Considering we have the following entities:
And, you want to fetch some parent Post
entities along with all the comments
and tags
collections.
If you are using more than one JOIN FETCH
directives:
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 will throw the infamous:
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 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 a List
for your collections.
That's terrible advice. Don't do that!
Using Sets
instead of Lists
will make the MultipleBagFetchException
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:
List<Post> posts = entityManager.createQuery("""
select p
from Post p
left join fetch p.comments
where p.id between :minId and :maxId
""", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.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)
.getResultList();
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:
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();
In the first JPQL query,
distinct
DOES NOT go to the SQL statement. That's why we set thePASS_DISTINCT_THROUGH
JPA query hint tofalse
.DISTINCT has two meanings in JPQL, and here, we need it to deduplicate the Java object references returned by
getResultList
on the Java side, not the SQL side.
Используя несколько запросов, вы избежите декартова произведения, поскольку любая другая коллекция, но первая извлекается с помощью вторичного запроса.
Вы могли бы сделать еще кое-что
Если вы используете FetchType.EAGER
стратегию во время сопоставления для @OneToMany
или @ManyToMany
ассоциаций, то вы могли бы легко получить MultipleBagFetchException
.
Вам лучше переключиться с FetchType.EAGER
на Fetchype.LAZY
, поскольку быстрая выборка - ужасная идея, которая может привести к критическим проблемам с производительностью приложения.
Заключение
Избегайте FetchType.EAGER
и не переключайтесь с List
на Set
только потому, что это заставит Hibernate скрыть MultipleBagFetchException
под ковром. Извлекайте только одну коллекцию за раз, и все будет в порядке.
Пока вы делаете это с тем же количеством запросов, что и у вас есть коллекции для инициализации, все в порядке. Просто не инициализируйте коллекции в цикле, так как это вызовет проблемы с N + 1 запросом, что также плохо сказывается на производительности.
Ответ 4
Добавьте в свой код аннотацию @Fetch, специфичную для Hibernate:
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;
Это должно устранить проблему, связанную с ошибкой Hibernate HHH-1718