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;

}

Як щодо цієї проблеми? Що я можу зробити?


EDIT

Гаразд, проблема, що я маю, полягає в тому, що ще одна "батьківська" сутність знаходиться всередині мого батька, моя реальна поведінка така:

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.
Thomas W

@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>
Big Zed

Відповіді:


555

Я думаю, що з цим повинна впоратися нова версія сплячого режиму (підтримує JPA 2.0). Але в іншому випадку можна обійти це, анотувавши поля колекції за допомогою:

@LazyCollection(LazyCollectionOption.FALSE)

Не забудьте видалити fetchTypeатрибут із @*ToManyпримітки.

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

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


4
дивно, це спрацювало на мене. Ви видалили fetchTypeз @*ToMany?
Божо

101
Проблема полягає в тому, що анотації JPA розбираються, щоб не допускати більше 2 охоче завантажених колекцій. Але оголошення, специфічні для сплячки, це дозволяють.
Божо

14
Потреба у більш ніж 1 EAGER видається цілком реальною. Це обмеження є лише наглядом у рамках Спільної спільної діяльності? На які проблеми я повинен звернути увагу, коли маю багатогласих EAGER?
AR3Y35

6
річ у тому, що сплячий не може отримати дві колекції одним запитом. Отже, коли ви запитуєте про материнську сутність, йому знадобляться 2 додаткові запити на результат, які зазвичай не потрібні.
Божо

7
Було б чудово мати пояснення, чому це вирішує проблему.
Вебнет

290

Просто змініть Listтип на Setтип.

Але нагадайте, що ви не усунете нижній декартовий продукт, як описав Влад Міхалча у своїй відповіді !


42
Список і набір - це не одне і те ж: набір не зберігає порядок
Маттео

17
LinkedHashSet зберігає порядок
egallardo

15
Це важлива відмінність, і коли ви думаєте про це, цілком правильно. Типовий багатокористувацький ключ, реалізований іноземним ключем у БД, насправді не є списком, це набір, оскільки порядок не збережений. Тож Набір дійсно доречніше. Я думаю, що це має значення в сплячому режимі, хоча я не знаю, чому.
crazy4jesus

3
У мене те саме не могло одночасно отримати кілька мішків, але не через анотації. У моєму випадку я робив ліві приєднання та роз'єднання з двома *ToMany. Зміна типу Setвирішила і мою проблему. Відмінне і акуратне рішення. Це має бути офіційною відповіддю.
Л. Голландія

20
Відповідь мені сподобалась, але питання мільйона доларів: Чому? Чому в програмі Set не показуються винятки? Спасибі
Hinotori

140

Додайте до свого коду примітку @Fetch, специфічну для спячки:

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

Це має виправити проблему, пов’язану із сплячкою помилки HHH-1718


5
@DaveRlz, чому subSelect вирішує цю проблему. Я спробував ваше рішення та його роботу, але не знаєте, як проблема була вирішена за допомогою цього?
HakunaMatata

Це найкраща відповідь, якщо Setнасправді немає сенсу. Наявність єдиного OneToManyзв’язку з використанням Setрезультатів у 1+<# relationships>запитах, де як використання FetchMode.SUBSELECTрезультатів у 1+1запитах. Також використання примітки у прийнятій відповіді ( LazyCollectionOption.FALSE) спричиняє ще більше запитів.
mstrthealias

1
FetchType.EAGER не є правильним рішенням для цього. Потрібно продовжувати снувати зібрані профілі та потребувати вирішення
Milinda Bandara

2
Два інших найкращих відповіді не вирішили моєї проблеми. Цей і зробив. Дякую!
Blindworks

3
Хтось знає, чому SUBSELECT це виправляє, але ПРИЄДНАЙТЕ це не так?
Інокентій

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();

Спящий режим кине сумнозвісний:

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
]

Зимова сплячка не дозволяє отримати більше одного пакетика, оскільки це призведе до отримання декартового продукту .

Найгірше "рішення"

Тепер ви знайдете безліч відповідей, дописів у блогах, відео чи інших ресурсів, які дозволять використовувати 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асоціацій, ви можете легко закінчити програму a MultipleBagFetchException.

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

Висновок

Уникайте FetchType.EAGERта не перемикайтесь Listна Setлише тому, що це зробить сплячку сховатися MultipleBagFetchExceptionпід килимом. Отримайте відразу одну колекцію, і ви будете добре.

Поки ви робите це з тією ж кількістю запитів, що і колекції для ініціалізації, ви все в порядку. Просто не ініціалізуйте колекції в циклі, оскільки це спричинить проблеми запитів N + 1 , які також погані для продуктивності.


Дякую за спільні знання. Однак DISTINCTв цьому рішенні є вбивця продуктивності. Чи є спосіб позбутися distinct? (намагався повернутися Set<...>замість цього, не дуже допомогло)
Леонід Дашко

1
DISTINCT не переходить до оператора SQL. Ось чому PASS_DISTINCT_THROUGHналаштовано на false. DISTINCT має 2 значення у JPQL, і тут нам потрібно це для дедуплікації на стороні Java, а не на стороні SQL. Перегляньте цю статтю для отримання більш детальної інформації.
Влад

Влад, дякую за допомогу, яку я вважаю дійсно корисною. Однак це питання було пов'язане hibernate.jdbc.fetch_size(зрештою, я встановив його до 350). Випадково ви знаєте, як оптимізувати вкладені відносини? Наприклад, entit1 -> сутність2 -> суб'єкт3.1, сутність 3.2 (де сутність3.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
Це єдине рішення зі сплячки. Що робити, якщо ви використовуєте спільну бібліотеку JPA?
Мішель

3
Я впевнений, що ти цього не мав на увазі, але DaveRlz вже написав те саме на 3 роки раніше
phil294

21

Щоб виправити це, просто Setзамість Listвашого вкладеного об'єкта.

@OneToMany
Set<Your_object> objectList;

і не забудьте використовувати fetch=FetchType.EAGER

це спрацює.

Є ще одна концепція CollectionIdв сплячому режимі, якщо ви хочете дотримуватися лише списку.

Але нагадайте, що ви не усунете нижній декартовий продукт, як описав Влад Міхалча у своїй відповіді !


11

Я знайшов хорошу публікацію в блозі про поведінку Hibernate у подібних об’єктах відображення: http://blog.eyallupu.com/2010/06/hibernate-exception-sim istovremenoly.html


4
Ласкаво просимо до переповнення стека. Однак відповіді, що стосуються лише посилань, як правило, не рекомендуються. Див. Faq, зокрема: stackoverflow.com/faq#deletion .
femtoRgon

6

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

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

у дітей тоді слід додати поле orderIndex


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 - жахлива річ.


1

Якщо у вас занадто складні об'єкти з колекцією saveral не може бути гарною ідеєю мати їх усіма EAGER fetchType, краще скористайтеся LAZY і коли вам дійсно потрібно завантажити колекції, використовуйте: Hibernate.initialize(parent.child)для отримання даних.


0

Для мене проблема полягала у вкладенні EAGER витягів .

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

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());

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


0

Коментуючи Fetchі LazyCollectionінколи, і іноді допомагає запустити проект.

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)

0

Хороша річ у @LazyCollection(LazyCollectionOption.FALSE)тому, що кілька полів із цією анотацією можуть співіснувати, але FetchType.EAGERне можуть, навіть у ситуаціях, коли таке співіснування є законним.

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

У моєму випадку я @Fetchвирішив мою проблему, MultipleBacFetchExceptionале потім викликає LazyInitializationExceptionсумнозвісну no Sessionпомилку.


-5

Ви можете використати нову примітку для вирішення цього питання:

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

Насправді значенням за замовчуванням fetch є також FetchType.LAZY.


5
JPA3.0 не існує.
holmis83
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.