Hibernacja zgłasza wyjątek MultipleBagFetchException - nie można jednocześnie pobrać wielu toreb


471

Hibernacja zgłasza ten wyjątek podczas tworzenia SessionFactory:

org.hibernate.loader.MultipleBagFetchException: nie można jednocześnie pobrać wielu torebek

To mój przypadek testowy:

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;

}

Co powiesz na ten problem? Co mogę zrobić?


EDYTOWAĆ

OK, mam problem z tym, że inny „rodzic” jest wewnątrz mojego rodzica, moje prawdziwe zachowanie jest takie:

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;

}

Hibernacja nie lubi dwóch kolekcji FetchType.EAGER, ale wydaje się, że to błąd, nie robię niezwykłych rzeczy ...

Usuwanie FetchType.EAGERz Parentlub AnotherParentrozwiązuje problem, ale muszę, więc prawdziwym rozwiązaniem jest użycie @LazyCollection(LazyCollectionOption.FALSE)zamiast FetchType(dzięki Bozho do roztworu).


Chciałbym zapytać, jakie zapytanie SQL chcesz wygenerować, aby pobrać jednocześnie dwie osobne kolekcje? Rodzaje języka SQL, które byłyby w stanie to osiągnąć, wymagałyby połączenia kartezjańskiego (potencjalnie wysoce nieefektywnego) lub UNII rozłącznych kolumn (również brzydkich). Prawdopodobnie niemożność osiągnięcia tego w SQL w czysty i wydajny sposób wpłynęła na projekt API.
— Thomas W

@ThomasW Są to zapytania SQL, które powinien wygenerować:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
— nurettin

1
Możesz otrzymać błąd symulacyjny, jeśli masz więcej niż jeden List<child>ze fetchTypezdefiniowanym dla więcej niż jednego List<clield>
— Big Zed

Odpowiedzi:


555

Myślę, że nowsza wersja hibernacji (obsługująca JPA 2.0) powinna sobie z tym poradzić. Ale w przeciwnym razie możesz to obejść, dodając adnotacje do pól kolekcji:

@LazyCollection(LazyCollectionOption.FALSE)

Pamiętaj, aby usunąć fetchTypeatrybut z @*ToManyadnotacji.

Ale zauważ, że w większości przypadków a Set<Child>jest bardziej odpowiednie niż List<Child>, więc chyba, że ​​naprawdę potrzebujesz List- idź doSet

Przypomnij jednak, że używając zestawów nie wyeliminujesz podkładającego się produktu kartezjańskiego, jak opisał Vlad Mihalcea w swojej odpowiedzi !


4
dziwne, to zadziałało dla mnie. Czy usunąłeś fetchTypez @*ToMany?
— Bozho,

101
problem polega na tym, że adnotacje JPA są analizowane, aby nie pozwolić na więcej niż 2 niecierpliwie ładowanych kolekcji. Ale pozwalają na to adnotacje dotyczące hibernacji.
— Bozho

14
Potrzeba więcej niż 1 EAGER wydaje się całkowicie realistyczna. Czy to ograniczenie jest tylko nadzorem WZP? Jakie są obawy, na które powinienem zwrócić uwagę, mając wiele EAGERÓW?
— AR3Y35,

6
Chodzi o to, że hibernacja nie może pobrać dwóch kolekcji za pomocą jednego zapytania. Kiedy więc zapytasz o jednostkę nadrzędną, będzie ona potrzebować 2 dodatkowych zapytań na wynik, co zwykle jest czymś, czego nie chcesz.
— Bozho

7
Byłoby wspaniale mieć wyjaśnienie, dlaczego to rozwiązuje problem.
— Webnet

290

Wystarczy zmienić Listtyp na Settyp.

Przypomnij jednak, że nie wyeliminujesz podkładającego się produktu kartezjańskiego, jak opisał Vlad Mihalcea w swojej odpowiedzi !


42
Lista i zestaw to nie to samo: zestaw nie zachowuje porządku
— Matteo

17
LinkedHashSet zachowuje porządek
— egallardo

15
To ważne rozróżnienie i, jeśli się nad tym zastanowić, całkowicie poprawne. Typowa funkcja wiele do jednego zaimplementowana przez klucz obcy w DB tak naprawdę nie jest Listą, to Zestaw, ponieważ kolejność nie jest zachowana. Więc Set jest naprawdę bardziej odpowiedni. Myślę, że to robi różnicę w hibernacji, chociaż nie wiem dlaczego.
— fool4jesus

3
Miałem to samo, że nie mogę jednocześnie pobrać wielu torebek, ale nie z powodu adnotacji. W moim przypadku robiłem lewe połączenia i rozłączenia z tymi dwoma *ToMany. Zmieniłem też typ, aby Setrozwiązać mój problem. Doskonałe i schludne rozwiązanie. To powinna być oficjalna odpowiedź.
— L. Holanda,

20
Podobała mi się odpowiedź, ale pytanie za milion dolarów brzmi: dlaczego? Dlaczego w programie Set nie wyświetla się wyjątków? Dzięki
— Hinotori,

140

Dodaj adnotację @Fetch do swojego kodu:

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

To powinno rozwiązać problem związany z błędem Hibernacji HHH-1718


5
@DaveRlz dlaczego subSelect rozwiązuje ten problem. Wypróbowałem Twoje rozwiązanie i jego działanie, ale nie wiesz, jak rozwiązać ten problem?
— HakunaMatata,

To najlepsza odpowiedź, chyba że Setnaprawdę ma sens. Posiadanie pojedynczej OneToManyrelacji przy użyciu Setwyników w 1+<# relationships>zapytaniach, gdzie jak przy użyciu FetchMode.SUBSELECTwyników w 1+1zapytaniach. Ponadto użycie adnotacji w zaakceptowanej odpowiedzi ( LazyCollectionOption.FALSE) powoduje wykonanie jeszcze większej liczby zapytań.
— mstrthealias

1
FetchType.EAGER nie jest do tego właściwym rozwiązaniem. Musisz przejść do Hibernacji i pobrać profile
— Milinda Bandara

2
Dwie pozostałe najważniejsze odpowiedzi nie rozwiązały mojego problemu. Ten zrobił. Dziękuję Ci!
— Blindworks

3
Czy ktoś wie, dlaczego SUBSELECT to naprawia, ale JOIN nie?
— Innokenty,

41

To pytanie było powtarzającym się motywem zarówno na StackOverflow, jak i na forum Hibernacji, więc postanowiłem zamienić odpowiedź również w artykuł .

Biorąc pod uwagę, że mamy następujące podmioty:

wprowadź opis zdjęcia tutaj

I chcesz pobrać niektóre Postelementy nadrzędne wraz ze wszystkimi zbiorami commentsi tags.

Jeśli używasz więcej niż jednej JOIN FETCHdyrektywy:

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

Hibernacja rzuci niesławny:

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
]

Hibernacja nie pozwala na pobranie więcej niż jednej torby, ponieważ wygenerowałoby to produkt kartezjański .

Najgorsze „rozwiązanie”

Teraz znajdziesz wiele odpowiedzi, postów na blogu, filmów lub innych zasobów, w których możesz użyć Setzamiast Listswoich kolekcji.

To okropna rada. Nie rób tego!

Użycie Setszamiast Listsspowoduje, że MultipleBagFetchExceptionzniknie, ale produkt kartezjański nadal tam będzie, co jest nawet gorsze, ponieważ problem z wydajnością dowiesz się długo po zastosowaniu tej „poprawki”.

Właściwe rozwiązanie

Możesz wykonać następującą sztuczkę:

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

W pierwszym zapytaniu JPQL distinctNIE przechodzi do instrukcji SQL. Dlatego ustawiliśmy PASS_DISTINCT_THROUGHwskazówkę dotyczącą zapytania JPA na false.

DISTINCT ma dwa znaczenia w języku JPQL i tutaj potrzebujemy go do deduplikacji odwołań do obiektów Java zwróconych getResultListpo stronie Java, a nie po stronie SQL. Sprawdź ten artykuł, aby uzyskać więcej informacji.

Tak długo, jak JOIN FETCHpobierzesz co najwyżej jedną kolekcję , nic ci nie będzie.

Używając wielu zapytań, unikniesz produktu kartezjańskiego, ponieważ jakakolwiek inna kolekcja jest pobierana przy użyciu zapytania wtórnego.

Możesz zrobić więcej

Jeśli używasz FetchType.EAGERstrategii w czasie mapowania dla stowarzyszeń @OneToManylub @ManyToManystowarzyszeń, możesz łatwo skończyć z MultipleBagFetchException.

Lepiej jest przełączać się z FetchType.EAGERna, Fetchype.LAZYponieważ chętne pobieranie jest okropnym pomysłem, który może prowadzić do krytycznych problemów z wydajnością aplikacji .

Wniosek

Unikaj FetchType.EAGERi nie zmieniaj opcji z Listna Settylko dlatego, że spowoduje to, że Hibernacja ukryje MultipleBagFetchExceptionpod dywan. Pobieraj tylko jedną kolekcję na raz, a wszystko będzie dobrze.

Tak długo, jak robisz to z taką samą liczbą zapytań, jak masz kolekcje do zainicjowania, nic ci nie jest. Po prostu nie inicjuj kolekcji w pętli, ponieważ spowoduje to problemy z zapytaniami N + 1 , które również mają negatywny wpływ na wydajność.


Dzięki za wspólną wiedzę. Jednak DISTINCTw tym rozwiązaniu zabija wydajność. Czy jest sposób na pozbycie się distinct? (próbowała wrócić Set<...>zamiast, nie pomogło)
— Leonid Dashko

1
DISTINCT nie przechodzi do instrukcji SQL. Dlatego PASS_DISTINCT_THROUGHjest ustawiony na false. DISTINCT ma 2 znaczenia w języku JPQL, a tutaj potrzebujemy go do deduplikacji po stronie Java, a nie po stronie SQL. Sprawdź ten artykuł, aby uzyskać więcej informacji.
— Vlad Mihalcea,

Vlad, dzięki za pomoc, którą uważam za naprawdę przydatną. Problem był jednak związany hibernate.jdbc.fetch_size(ostatecznie ustawiłem go na 350). Czy wiesz, jak zoptymalizować relacje zagnieżdżone? Np. Byt1 -> byt2 -> byt3.1, byt 3.2 (gdzie byt3.1 / 3.2 są relacjami @OneToMany)
— Leonid Dashko

1
@LeonidDashko Zapoznaj się z rozdziałem Pobieranie w mojej książce o wysokiej wydajności Java Persistence, aby uzyskać wiele wskazówek związanych z pobieraniem danych.
— Vlad Mihalcea

1
Nie, nie możesz. Pomyśl o tym w kategoriach SQL. Nie można ŁĄCZYĆ wielu powiązań jeden do wielu bez generowania produktu kartezjańskiego.
— Vlad Mihalcea

31

Po wypróbowaniu każdej opcji opisanej w tych postach i innych, doszedłem do wniosku, że poprawka jest następująca.

W każdym miejscu XToMany @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) i bezpośrednio po nim

@Fetch(value = FetchMode.SUBSELECT)

To zadziałało dla mnie


5
dodanie @Fetch(value = FetchMode.SUBSELECT)było wystarczające
— user2601995

1
Jest to rozwiązanie tylko hibernacyjne. Co się stanie, jeśli korzystasz ze wspólnej biblioteki JPA?
— Michel

3
Jestem pewien, że nie chciałeś, ale DaveRlz napisał już to samo 3 lata wcześniej
— phil294,

21

Aby rozwiązać to po prostu wziąć Setw miejscu Listdla zagnieżdżonego obiektu.

@OneToMany
Set<Your_object> objectList;

i nie zapomnij użyć fetch=FetchType.EAGER

to będzie działać.

Jest jeszcze jedna koncepcja CollectionIdw Hibernacji, jeśli chcesz trzymać się tylko listy.

Przypomnij jednak, że nie wyeliminujesz podkładającego się produktu kartezjańskiego, jak opisał Vlad Mihalcea w swojej odpowiedzi !



6

możesz przechowywać listy EAGER na stoisku w JPA i dodać do co najmniej jednej z nich JPA adnotacja @OrderColumn (oczywiście nazwa pola do zamówienia). Nie potrzeba specjalnych adnotacji hibernacji. Pamiętaj jednak, że może tworzyć puste elementy na liście, jeśli wybrane pole nie ma wartości zaczynających się od 0

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

w Dzieci należy dodać pole orderIndex


2

Wypróbowaliśmy Set zamiast List i jest to koszmar: kiedy dodajesz dwa nowe obiekty, równa się () i hashCode () nie rozpoznają obu! Ponieważ nie mają żadnego identyfikatora.

typowe narzędzia, takie jak Eclipse, generują ten rodzaj kodu z tabel bazy danych:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;
}

Możesz również przeczytać ten artykuł który prawidłowo wyjaśnia, jak popsuty jest JPA / Hibernacja. Po przeczytaniu tego, myślę, że po raz ostatni używam ORM w moim życiu.

Spotkałem też facetów zajmujących się projektowaniem opartym na domenie, którzy twierdzą, że ORM to straszna rzecz.


1

Gdy masz zbyt skomplikowane obiekty z kolekcją oszczędzającą, nie byłoby dobrym pomysłem mieć je wszystkie z EAGER fetchType, lepiej użyj LAZY, a gdy naprawdę potrzebujesz załadować kolekcje, użyj: Hibernate.initialize(parent.child)aby pobrać dane.


0

Dla mnie problemem było zagnieżdżenie pobrań EAGER .

Jednym z rozwiązań jest ustawienie zagnieżdżonych pól na LAZY i użycie Hibernate.initialize () do załadowania zagnieżdżonych pól:

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

0

Na moim końcu stało się tak, gdy miałem wiele kolekcji z FetchType.EAGER, takich jak:

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

Ponadto kolekcje łączyły się w tej samej kolumnie.

Aby rozwiązać ten problem, zmieniłem jedną z kolekcji na FetchType.LAZY, ponieważ w moim przypadku użycia było to w porządku.

Powodzenia! ~ J


0

Komentowanie obu, Fetcha LazyCollectionczasem pomaga uruchomić projekt.

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

0

Jedną dobrą rzeczą @LazyCollection(LazyCollectionOption.FALSE)jest to, że kilka pól z tą adnotacją może koegzystować, podczas gdy FetchType.EAGERnie, nawet w sytuacjach, w których takie współistnienie jest uzasadnione.

Na przykład, Ordermoże mieć listę OrderGroup(krótką), a także listę Promotions(również krótką). @LazyCollection(LazyCollectionOption.FALSE)może być stosowany na obu bez powodowania LazyInitializationExceptionżadnego MultipleBagFetchException.

W moim przypadku @Fetchrozwiązałem problem, MultipleBacFetchExceptionale potem powoduje LazyInitializationExceptionniesławny no Sessionbłąd.


-5

Aby rozwiązać ten problem, możesz użyć nowej adnotacji:

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

W rzeczywistości domyślną wartością fetch jest również FetchType.LAZY.


5
JPA3.0 nie istnieje.
— holmis83,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.