Hibernate rzuca MultipleBagFetchException - nie może jednocześnie pobrać wielu worków

Hibernate wyrzuca ten wyjątek podczas tworzenia SessionFactory:

Org.hibernacja.ładowacz.MultipleBagFetchException: nie można pobrać jednocześnie wielu toreb]}

To mój testowy przypadek:

Rodzic.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;

}

Dziecko.java

@Entity
public Child {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private Parent parent;

}

A co z tym problemem? Co mogę zrobić?


EDIT

OK, problem jest taki, że inny" rodzic " jest wewnątrz mój rodzic, moje prawdziwe zachowanie jest takie:

Rodzic.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;

}

Kolejny rodzic.java

@Entity
public AnotherParent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

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

}

Hibernate nie lubi dwóch kolekcji z FetchType.EAGER, ale to chyba błąd, nie robię niezwykłych rzeczy...

Usunięcie FetchType.EAGER z Parent lub AnotherParent rozwiązuje problem, ale potrzebuję go, więc prawdziwym rozwiązaniem jest użycie @LazyCollection(LazyCollectionOption.FALSE) zamiast FetchType (dzięki Bozho za rozwiązanie).

Author: Jan Nielsen, 2010-12-02

16 answers

Myślę, że nowsza wersja hibernate (obsługująca JPA 2.0) powinna sobie z tym poradzić. Ale poza tym możesz to obejść, adnotując pola kolekcji za pomocą:

@LazyCollection(LazyCollectionOption.FALSE)

Pamiętaj, aby usunąć atrybut fetchType z adnotacji @*ToMany.

Ale zauważ, że w większości przypadków {[3] } jest bardziej odpowiedni niż List<Child>, więc jeśli naprawdę nie potrzebujesz List - idź do Set

Ale przypomnij, że używając zbiorów nie wyeliminujesz iloczynu kartezjańskiego Jak to opisał Vlad Mihalcea w swojej odpowiedzi !

 584
Author: Bozho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-08 12:14:44

Po prostu zmień typ z List Na typ Set.

Ale przypomnij, że nie wyeliminujesz iloczynu kartezjańskiego opisanego przez Vlada Mihalcea w jego odpowiedzi!

 289
Author: Ahmad Zyoud,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-08 12:14:06

Dodaj adnotację @ Fetch specyficzną dla Hibernate do kodu:

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

Powinno to rozwiązać problem związany z błędem Hibernate HHH-1718

 159
Author: Dave Richardson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-04-18 14:39:31

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

Tutaj wpisz opis obrazka

I, chcesz pobrać element nadrzędny Post wraz ze wszystkimi kolekcjami comments i tags.

Jeśli używasz więcej niż jednej dyrektywy 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 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
]

Hibernate nie pozwala na pobieranie więcej niż jednego worka, ponieważ generowałoby to iloczyn kartezjański.

Najgorsze "rozwiązanie"

Teraz znajdziesz wiele odpowiedzi, posty na blogu, filmy lub inne zasoby mówiące o używaniu Set zamiast List do swoich kolekcji.

To straszna Rada. Nie rób tego!

Użycie Sets zamiast Lists sprawi, że MultipleBagFetchException zniknie, ale produkt kartezjański nadal tam będzie, co jest jeszcze gorsze, ponieważ problem z wydajnością dowiesz się długo po zastosowaniu tej "poprawki".

Właściwe rozwiązanie

Możesz wykonać następujące czynności trick:

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, distinct nie przechodzi do instrukcji SQL. Dlatego ustawiamy podpowiedź PASS_DISTINCT_THROUGH JPA query na false.

DISTINCT ma dwa znaczenia w JPQL, a tutaj potrzebujemy go do deduplikacji odwołań do obiektów Java zwracanych przez getResultList Po stronie Javy, a nie po stronie SQL.

Tak długo, jak pobierasz co najwyżej jedną kolekcję używając JOIN FETCH, wszystko będzie dobrze.

Używając wielu zapytań, unikniesz iloczynu kartezjańskiego, ponieważ każdy inna kolekcja, ale pierwsza jest pobierana za pomocą zapytania wtórnego.

There ' s more you could do

Jeśli używasz FetchType.EAGER strategii w czasie mapowania dla @OneToMany lub @ManyToMany asocjacji, możesz łatwo skończyć z MultipleBagFetchException.

Lepiej jest przełączyć się z FetchType.EAGER na Fetchype.LAZY, ponieważ eager fetching to okropny pomysł, który może prowadzić do krytycznych problemów z wydajnością aplikacji.

Podsumowanie

Unikaj FetchType.EAGER i nie przełącz się z List na Set tylko dlatego, że zrobi to Hibernate ukryje MultipleBagFetchException pod dywan. Przynieś tylko jedną kolekcję na raz, a będzie dobrze.

Tak długo, jak robisz to z taką samą liczbą zapytań, jak masz kolekcje do zainicjowania, jesteś w porządku. Po prostu nie inicjuj kolekcji w pętli, ponieważ spowoduje to N + 1 problemów z zapytaniami, które również są złe dla wydajności.

 78
Author: Vlad Mihalcea,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-08 21:05:51

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

In every xtomany place @XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) i pośrednio po

@Fetch(value = FetchMode.SUBSELECT)

To zadziałało dla mnie

 37
Author: Javier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-27 16:46:14

Aby to naprawić, po prostu weź Set w miejsce List dla zagnieżdżonego obiektu.

@OneToMany
Set<Your_object> objectList;

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

To zadziała.

Jest jeszcze jedna koncepcja CollectionId w Hibernate, jeśli chcesz trzymać się tylko listy.

Ale przypomnij, że nie wyeliminujesz iloczynu kartezjańskiego opisanego przez Vlada Mihalcea w jego odpowiedzi !

 18
Author: Prateek Singh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-08 12:13:50

Znalazłem dobry wpis na blogu o zachowaniu Hibernate w tego typu mapowaniach obiektów: http://blog.eyallupu.com/2010/06/hibernate-exception-simultaneously.html

 10
Author: Christian Müller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-11 15:52:34

Możesz zachować listy Bootha w JPA i dodać do co najmniej jednej z nich adnotację JPA @ OrderColumn (Oczywiście z nazwą pola do zamówienia). Nie ma potrzeby stosowania konkretnych adnotacji hibernate. Należy jednak pamiętać, ż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 przypadku dzieci należy dodać pole orderIndex

 6
Author: Massimo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-25 09:29:18

Próbowaliśmy ustawić zamiast List i jest to koszmar: po dodaniu dwóch nowych obiektów, równa się() i hashCode () nie rozróżnia obu z nich ! Bo nie mają dokumentów.

Typowe narzędzia takie jak Eclipse generują taki kod 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 wyjaśnia, jak pokręcone jest JPA/Hibernate. Po przeczytaniu tego, myślę, że to ostatni raz, Kiedy używam jakiegokolwiek ORM w moim życie.

Spotkałam również facetów z Domain Driven Design, którzy w zasadzie mówią, że ORM to straszna rzecz.

 2
Author: Mike Dudley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-06 19:13:21

Jeśli masz zbyt złożone obiekty z kolekcją saveral nie może być dobrym pomysłem, aby mieć wszystkie z nich z EAGER fetchType, lepiej Użyj LAZY i kiedy naprawdę trzeba załadować Kolekcje użyj: Hibernate.initialize(parent.child) aby pobrać dane.

 1
Author: Felipe Cadena,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-15 13:38:21

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

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

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());
 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-12 18:09:55

U mnie stało się tak, gdy miałem wiele kolekcji z FetchType.EAGER, like this:

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

Dodatkowo zbiory łączyły się w tej samej kolumnie.

Aby rozwiązać ten problem, zmieniłem jedną z kolekcji na FetchType.Leniwy, ponieważ był w porządku dla mojego przypadku użycia.

Goodluck! ~J
 0
Author: JAD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-11 21:14:35

Komentowanie zarówno Fetch jak i LazyCollection czasami pomaga w prowadzeniu projektu.

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)
 0
Author: prisar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-27 05:47:43

Jedną dobrą rzeczą w @LazyCollection(LazyCollectionOption.FALSE) jest to, że kilka pól z tą adnotacją może współistnieć, podczas gdy FetchType.EAGER nie może, nawet w sytuacjach, gdy takie współistnienie jest legalne.

Na przykład, {[2] } moż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 ani MultipleBagFetchException.

W moim przypadku @Fetch rozwiązał mój problem MultipleBacFetchException, ale potem powoduje LazyInitializationException, niesławny błąd no Session.

 0
Author: WesternGun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-14 15:43:40

Rozwiązałem przez adnotację:

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
 0
Author: Antonio Petricca,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-29 10:12:42

Możesz użyć nowej adnotacji, aby rozwiązać ten problem:

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

W rzeczywistości domyślną wartością fetch jest FetchType.Leniwy też.

 -5
Author: leee41,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-06 03:26:41