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
Причина, по которой вы получаете это исключение, заключается в том, что 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 альтернативы (при необходимости извлекайте данные вручную):
- Используйте JOIN FETCH для получения коллекции. Чтобы придерживаться примера
SELECT stud FROM STUDENT stud JOIN FETCH stud.teachers
сделал бы свое дело. Это всегда будет запускать один запрос для получения студентов И учителей. Просто помните, что таким способом можно получить только одну коллекцию (объяснение для этого ответа зашло бы слишком далеко). - Если вы используете Spring-JPA, вы можете использовать @EntityGraph(attributePaths = {"teachers"}), чтобы сделать то же самое.
- Вы можете вызвать Hibernate.initialize(oneStudent.getTeachers()) с помощью прокси-сервера Hibernate, чтобы получить отношение вручную. Это всегда будет создавать отдельный запрос, поэтому не делайте этого в цикле, иначе вы сами создали проблему выбора n+1.
И еще один общий совет: включите ведение журнала для org.hibernate.SQL в режиме DEBUG, чтобы увидеть, когда и какие запросы запускаются, и убедитесь, что ваша локальная установка разработки имеет достаточный объем данных. Когда вы тестируете свой код только с двумя студентами и не проверяете выполняемые запросы, вы можете пропустить что-то подобное и в конечном итоге столкнуться с проблемами в реальных системах с большим количеством данных.
Вы можете использовать новую аннотацию, чтобы решить это:
@XXXToXXX(targetEntity = XXXX.class, fetch = FetchType.LAZY)
Фактически, значением по умолчанию для fetch также является FetchType.LAZY.