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

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 AntoherParent anotherParent;

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

}

AnotherParent.java

@Entity
public AntoherParent {

 @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 за решение).

19 ответов

Решение

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

@LazyCollection(LazyCollectionOption.FALSE)

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

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

Просто изменить из List введите в Set тип.

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

Теперь, хотя вы могли бы "исправить" проблему с помощью Set вместо ListВы не должны этого делать, потому что декартово произведение все еще будет включено в базовые операторы SQL.

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

Если вам нужно выбрать дочерние объекты по многоуровневой иерархии, лучше выбрать от самого внутреннего потомка до родителей, как описано в этой статье.

В качестве альтернативы добавьте аннотацию @Fetch, специфичную для Hibernate, к своему коду:

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

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

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

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

@Fetch(value = FetchMode.SUBSELECT)

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

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

@OneToMany
Set<Your_object> objectList;

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

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

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

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

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

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

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

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

Мы попробовали 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 - ужасная вещь.

Для меня проблема заключалась в том, чтобы иметь вложенные 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

Я решил, аннотируя:

      @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)

Комментируя оба 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 ошибка.

Итак, вот мои 2 цента. У меня были аннотации Fetch Lazy в моем Entity, но я также продублировал ленивую выборку в сессионном компоненте, что вызвало проблему с несколькими пакетами. Поэтому я просто удалил строки в своем SessionBean

критериев.createAlias ​​(«ПОЛЕ», «НИКНЕЙМЫ», JoinType.LEFT_OUTER_JOIN); //УДАЛЕННЫЙ

И я использовал Hibernate.initialize в списке, который хотел получить после вызова List parent = criterion.list. Hibernate.initialize(parent.getChildList());

TLDR: Не используйте EAGER. Всегда привозите вещи только тогда, когда они вам действительно нужны!


Здесь много упоминаний об этом, и я хотел бы более подробно остановиться на том, почему это плохая идея и что можно использовать в качестве альтернативы. Надеюсь, прочитав это, вы поймете, что на самом деле вам НИКОГДА не следует использоватьFetchType.EAGER.

Для этого просто нет веских причин! Это ловушка мэра, которая может укусить вас (или, что еще хуже, кого-то еще) в будущем. Представьте, что вы выбрали FetchType.EAGER для связи Студент -> Учитель, потому что вы думаете, что каждый раз, когда вам нужно получить Студента, вам также нужны его Учителя. Теперь даже если вы на 100% уверены, что ученики без преподавателей вам сейчас никогда не понадобятся , вы просто не можете предугадать, как изменятся требования. FetchType.EAGER нарушает принцип открытости-закрытости! Ваш код больше не открыт для расширения - если позже возникнет необходимость загрузки Студентов без Учителей, то это сложно сделать без переделки (а возможно и нарушения) существующего кода!

Еще более серьезная проблема заключается в том, что вы, по сути, создали проблему выбора n+1 для возможных будущих запросов, которая (по праву) уже получила другую сумку в базовом запросе. Допустим, в будущем кто-то захочет загрузить всех учеников с их оценками, а их 30 000. Поскольку вы указали Hibernate, что EAGER должен получить всех учителей, он должен это сделать. Но поскольку вы уже получили еще один «пакет» (оценки) в рамках того же запроса, это фактически приводит к 30001 запросу — для данных, которые даже не нужны в этом сценарии! Первый запрос для загрузки всех учеников+оценок, а затем отдельный запрос для каждого ученика для получения его учителей. Излишне говорить, что это ужасно с точки зрения производительности. По моему мнению, это единственная причина, по которой люди считают, что «Hibernate работает медленно» - они просто не осознают, насколько невероятно неэффективно он может запрашивать информацию в некоторых ситуациях. Вы действительно должны быть осторожны с отношениями 1:n.

3 альтернативы (при необходимости извлекайте данные вручную):

  1. Используйте JOIN FETCH для получения коллекции. Чтобы придерживаться примераSELECT stud FROM STUDENT stud JOIN FETCH stud.teachersсделал бы свое дело. Это всегда будет запускать один запрос для получения студентов И учителей. Просто помните, что таким способом можно получить только одну коллекцию (объяснение для этого ответа зашло бы слишком далеко).
  2. Если вы используете Spring-JPA, вы можете использовать @EntityGraph(attributePaths = {"teachers"}), чтобы сделать то же самое.
  3. Вы можете вызвать Hibernate.initialize(oneStudent.getTeachers()) с помощью прокси-сервера Hibernate, чтобы получить отношение вручную. Это всегда будет создавать отдельный запрос, поэтому не делайте этого в цикле, иначе вы сами создали проблему выбора n+1.

И еще один общий совет: включите ведение журнала для org.hibernate.SQL в режиме DEBUG, чтобы увидеть, когда и какие запросы запускаются, и убедитесь, что ваша локальная установка разработки имеет достаточный объем данных. Когда вы тестируете свой код только с двумя студентами и не проверяете выполняемые запросы, вы можете пропустить что-то подобное и в конечном итоге столкнуться с проблемами в реальных системах с большим количеством данных.

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

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

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

Другие вопросы по тегам