Hibernate генерирует MultipleBagFetchException - не может одновременно получить несколько пакетов

471

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(спасибо Bozho для решения).

дуть
источник
Я хотел бы спросить, какой SQL-запрос вы надеетесь сгенерировать, чтобы одновременно получать две отдельные коллекции? Виды SQL, которые могли бы достичь этого, потребовали бы либо декартового объединения (потенциально весьма неэффективного), либо СОЮЗА непересекающихся столбцов (также некрасивых). Предположительно, невозможность добиться этого в SQL чистым и эффективным образом повлияла на дизайн API.
Томас В.
@ThomasW Это SQL-запросы, которые он должен генерировать:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
nurettin
1
Вы можете получить аналогичную ошибку, если у вас более одного List<child>с fetchTypeопределенным для более чем одного List<clield>
Большой Зед

Ответы:

555

Я думаю, что более новая версия hibernate (поддерживающая JPA 2.0) должна справиться с этим. Но в противном случае вы можете обойти это, пометив поля коллекции с помощью:

@LazyCollection(LazyCollectionOption.FALSE)

Не забудьте удалить fetchTypeатрибут из @*ToManyаннотации.

Но обратите внимание , что в большинстве случаев Set<Child>является более подходящим , чем List<Child>, так что если вы действительно не нужен List- дерзайтеSet

Но напомните, что при использовании наборов вы не будете устранять нижележащий декартов продукт, как описано Владом Михалчей в его ответе !

Bozho
источник
4
странно, это сработало для меня. Вы удалили fetchTypeиз @*ToMany?
Божо
102
проблема в том, что аннотации JPA анализируются, чтобы не допустить более двух загруженных коллекций. Но специфичные для спящего аннотации позволяют это.
Божо
14
Потребность в более чем 1 EAGER кажется абсолютно реалистичной. Является ли это ограничение просто упущением JPA? На какие проблемы я должен обращать внимание при наличии нескольких EAGER?
AR3Y35
6
Дело в том, что Hibernate не может получить две коллекции одним запросом. Поэтому, когда вы запрашиваете родительский объект, ему потребуется 2 дополнительных запроса для каждого результата, что обычно не требуется.
Божо
7
Было бы здорово получить объяснение, почему это решает проблему.
Веб-сеть
290

Просто измените от Listтипа к Setтипу.

Но напомните, что вы не будете устранять нижележащее декартово произведение, описанное Владом Михалчей в его ответе !

Ахмад Зюд
источник
42
Список и набор - это не одно и то же: набор не сохраняет порядок
Маттео
17
LinkedHashSet сохраняет порядок
egallardo
15
Это важное различие и, если подумать, совершенно правильно. Типичный много-к-одному, реализованный внешним ключом в БД, на самом деле это не список, а набор, потому что порядок не сохраняется. Так что Сет действительно более уместен. Я думаю, что это имеет значение в спящем режиме, хотя я не знаю почему.
fool4jesus
3
У меня было одно и то же, я не могу одновременно получить несколько сумок, но не из-за аннотаций. В моем случае я делал левые соединения и дизъюнкты с двумя *ToMany. Изменение типа, чтобы Setрешить мою проблему тоже. Отличное и аккуратное решение. Это должен быть официальный ответ.
Л. Холанда
20
Мне понравился ответ, но вопрос на миллион долларов: почему? Почему с Set не показывать исключения? Спасибо
Хинотори
140

Добавьте специфичную для Hibernate аннотацию @Fetch в свой код:

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

Это должно исправить проблему, связанную с ошибкой Hibernate HHH-1718

Дейв Ричардсон
источник
5
@DaveRlz, почему subSelect решает эту проблему. Я попробовал ваше решение и оно работает, но не знаете, как с помощью этого решить проблему?
HakunaMatata
Это лучший ответ, если только он Setне имеет смысла. Наличие единственной OneToManyсвязи с использованием Setрезультатов в 1+<# relationships>запросах, где использование FetchMode.SUBSELECTрезультатов в 1+1запросах. Кроме того, использование аннотации в принятом ответе ( LazyCollectionOption.FALSE) приводит к выполнению еще большего количества запросов.
mstrthealias
1
FetchType.EAGER не является правильным решением для этого. Необходимо продолжить работу с Hibernate Fetch Profiles и решить ее
Милинда Бандара
2
Два других главных ответа не решили мою проблему. Этот сделал. Спасибо!
Blindworks
3
Кто-нибудь знает, почему SUBSELECT это исправляет, а JOIN нет?
Иннокентий
42

Этот вопрос был постоянной темой как на StackOverflow, так и на форуме Hibernate, поэтому я решил превратить ответ и в статью .

Учитывая, что у нас есть следующие объекты:

введите описание изображения здесь

И, вы хотите загрузить некоторые родительские 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, возвращаемые getResultListна стороне Java, а не на стороне SQL. Проверьте эту статью для более подробной информации.

Пока вы выбираете не более одной коллекции JOIN FETCH, все будет в порядке.

Используя несколько запросов, вы избежите декартово произведение, поскольку любая другая коллекция, кроме первой, извлекается с использованием вторичного запроса.

Ты можешь сделать больше

Если вы используете FetchType.EAGERстратегию на отображение времени @OneToManyили @ManyToManyассоциаций, то вы могли бы легко закончить с MultipleBagFetchException.

Вам лучше перейти с FetchType.EAGERна, Fetchype.LAZYтак как активная выборка - ужасная идея, которая может привести к критическим проблемам производительности приложений .

Вывод

Избегайте FetchType.EAGERи не переходить от Listк Setтолько потому , что делать это будет сделать Hibernate скрыть MultipleBagFetchExceptionпод ковром. Принеси только одну коллекцию за раз, и все будет хорошо.

Пока вы делаете это с тем же количеством запросов, что и у вас для инициализации коллекций, у вас все в порядке. Только не инициализируйте коллекции в цикле, поскольку это вызовет проблемы с N + 1 запросами , которые также ухудшают производительность.

Влад Михалча
источник
Спасибо за обмен знаниями. Тем не менее, DISTINCTэто снижение производительности в этом решении. Есть ли способ избавиться от distinct? (пытался вернуться Set<...>вместо этого, не помогло)
Леонид Дашко
1
DISTINCT не идет в оператор SQL. Вот почему PASS_DISTINCT_THROUGHустановлено false. DISTINCT имеет два значения в JPQL, и здесь нам нужно его дедуплицировать на стороне Java, а не на стороне SQL. Проверьте эту статью для более подробной информации.
Влад Михалча
Влад, спасибо за помощь, я нахожу это действительно полезным. Тем не менее, проблема была связана с hibernate.jdbc.fetch_size(в конце концов я установил его на 350). Вы случайно не знаете, как оптимизировать вложенные отношения? Например, entity1 -> entity2 -> entity3.1, entity 3.2 (где entity3.1 / 3.2 - отношения @OneToMany)
Леонид Дашко
1
@LeonidDashko Изучите главу «Извлечение» в моей книге «Сохранение Java-производительности», где вы найдете множество советов, связанных с извлечением данных.
Влад Михальча
1
Нет, ты не можешь. Думайте об этом с точки зрения SQL. Вы не можете присоединиться к нескольким ассоциациям «один ко многим», не создав декартово произведение
Влад Михалча
31

После попыток описать каждую опцию в этом и других постах, я пришел к выводу, что исправление заключается в следующем.

В каждом месте XToMany @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) и после

@Fetch(value = FetchMode.SUBSELECT)

Это сработало для меня

Хавьер
источник
5
добавления @Fetch(value = FetchMode.SUBSELECT)было достаточно
user2601995
1
Это Hibernate только решение. Что если вы используете общую библиотеку JPA?
Мишель
3
Я уверен, что вы не имели в виду, но DaveRlz уже написал то же самое 3 годами ранее
phil294
21

Чтобы исправить это просто взять Setвместо Listдля вложенного объекта.

@OneToMany
Set<Your_object> objectList;

и не забудьте использовать fetch=FetchType.EAGER

это будет работать.

В CollectionIdHibernate есть еще одна концепция, если вы хотите придерживаться только списка.

Но напомните, что вы не будете устранять нижележащее декартово произведение, описанное Владом Михалчей в его ответе !

Пратик Сингх
источник
11

Я нашел хороший пост в блоге о поведении Hibernate в такого рода сопоставлениях объектов: http://blog.eyallupu.com/2010/06/hibernate-exception-simchronously.html

Кристиан Мюллер
источник
4
Добро пожаловать в стек переполнения. Тем не менее, ответы только на ссылки, как правило, не рекомендуется. Смотрите часто задаваемые вопросы, в частности: stackoverflow.com/faq#deletion .
femtoRgon
6

Вы можете сохранить списки EAGER в JPA и добавить по крайней мере к одному из них аннотацию JPA @OrderColumn (очевидно, с именем поля, которое нужно упорядочить). Нет необходимости в специальных спящих аннотациях. Но имейте в виду, что это может создать пустые элементы в списке, если выбранное поле не имеет значений, начинающихся с 0

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

в Children, тогда вы должны добавить поле orderIndex

Massimo
источник
2

Мы попробовали 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;
}

Вы также можете прочитать эту статью, которая правильно объясняет, как испортился JPA / Hibernate. Прочитав это, я думаю, что это последний раз, когда я использую ORM в своей жизни.

Я также встречал ребят из Domain Driven Design, которые в основном говорят, что ORM - ужасная вещь.

Майк Дадли
источник
1

Когда у вас есть слишком сложные объекты с коллекцией saveral, не может быть хорошей идеей иметь все их с EAGER fetchType, лучше использовать LAZY, а когда вам действительно нужно загрузить коллекции, используйте: Hibernate.initialize(parent.child)для получения данных.

Фелипе Кадена
источник
0

Для меня проблема заключалась в том, что у меня были вложенные EAGER .

Одно из решений - установить для вложенных полей значение LAZY и использовать Hibernate.initialize () для загрузки вложенных полей:

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());
butter_prune
источник
0

В конце концов, это случилось, когда у меня было несколько коллекций с FetchType.EAGER, например:

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

Кроме того, коллекции объединялись в одном столбце.

Чтобы решить эту проблему, я изменил одну из коллекций на FetchType.LAZY, так как она подходила для моего варианта использования.

Удачи! ~ J

JAD
источник
0

Комментирование Fetchи LazyCollectionиногда помогает запустить проект.

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)
prisar
источник
0

Одна хорошая вещь о @LazyCollection(LazyCollectionOption.FALSE)том, что несколько полей с этой аннотацией могут сосуществовать, но FetchType.EAGERне могут, даже в ситуациях, когда такое сосуществование является законным.

Например, Orderможет иметь список OrderGroup(короткий), а также список Promotions(также короткий). @LazyCollection(LazyCollectionOption.FALSE)может использоваться на обоих, не вызывая LazyInitializationExceptionни того, ни другого MultipleBagFetchException.

В моем случае @Fetchдействительно решить мою проблему, MultipleBacFetchExceptionно затем причины LazyInitializationException, печально известную no Sessionошибку.

WesternGun
источник
-5

Вы можете использовать новую аннотацию, чтобы решить это:

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

Фактически, значением по умолчанию для fetch также является FetchType.LAZY.

leee41
источник
5
JPA3.0 не существует.
holmis83