Вопрос-ответ

Hibernate throws MultipleBagFetchException - cannot simultaneously fetch multiple bags

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:

enter image description here

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 the PASS_DISTINCT_THROUGH JPA query hint to false.


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

java hibernate jpa