Hibernate löst MultipleBagFetchException aus - kann nicht mehrere Taschen gleichzeitig abrufen

471

Hibernate löst diese Ausnahme während der SessionFactory-Erstellung aus:

org.hibernate.loader.MultipleBagFetchException: Es können nicht mehrere Taschen gleichzeitig abgerufen werden

Dies ist mein Testfall:

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;

}

Wie wäre es mit diesem Problem? Was kann ich machen?


BEARBEITEN

OK, das Problem, das ich habe, ist, dass sich eine andere "übergeordnete" Entität in meinem übergeordneten Element befindet. Mein wirkliches Verhalten ist folgendes:

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 mag keine zwei Sammlungen mit FetchType.EAGER, aber das scheint ein Fehler zu sein, ich mache keine ungewöhnlichen Dinge ...

Das Entfernen FetchType.EAGERvon Parentoder AnotherParentlöst das Problem, aber ich brauche es, so wirkliche Lösung zu verwenden ist @LazyCollection(LazyCollectionOption.FALSE)statt FetchType(dank Bozho für die Lösung).

Schlag
quelle
Ich würde fragen, welche SQL-Abfrage Sie generieren möchten, um zwei separate Sammlungen gleichzeitig abzurufen. Die Arten von SQL, die dies erreichen könnten, würden entweder einen kartesischen Join (möglicherweise sehr ineffizient) oder eine UNION disjunkter Spalten (ebenfalls hässlich) erfordern. Vermutlich hat die Unfähigkeit, dies in SQL auf saubere und effiziente Weise zu erreichen, das API-Design beeinflusst.
Thomas W
@ThomasW Dies sind die SQL-Abfragen, die es generieren sollte:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
Nurettin
1
Sie können einen simillar Fehlermeldung erhalten , wenn Sie mehr als eine haben List<child>mit fetchTypefür definierte mehr als eine List<clield>
Big Zed

Antworten:

555

Ich denke, eine neuere Version von Hibernate (unterstützt JPA 2.0) sollte dies handhaben. Andernfalls können Sie dies umgehen, indem Sie die Sammlungsfelder mit folgenden Anmerkungen versehen:

@LazyCollection(LazyCollectionOption.FALSE)

Denken Sie daran, das fetchTypeAttribut aus der @*ToManyAnmerkung zu entfernen .

Beachten Sie jedoch, dass a in den meisten Fällen Set<Child>besser geeignet ist als List<Child>, es sei denn, Sie benötigen wirklich a List- go forSet

Aber denken Sie daran, dass Sie mit der Verwendung von Sets das zugrunde liegende kartesische Produkt nicht beseitigen können, wie es von Vlad Mihalcea in seiner Antwort beschrieben wurde !

Bozho
quelle
4
seltsam, es hat bei mir funktioniert. Hast du das fetchTypeaus dem entfernt @*ToMany?
Bozho
101
Das Problem ist, dass die JPA-Anmerkungen analysiert werden, um nicht mehr als 2 eifrig geladene Sammlungen zuzulassen. Die für den Ruhezustand spezifischen Anmerkungen erlauben dies jedoch.
Bozho
14
Der Bedarf an mehr als 1 EAGER erscheint völlig realistisch. Ist diese Einschränkung nur ein JPA-Versehen? Worauf sollte ich achten, wenn ich mehrere EAGER habe?
AR3Y35
6
Die Sache ist, dass der Ruhezustand die beiden Sammlungen nicht mit einer Abfrage abrufen kann. Wenn Sie also nach der übergeordneten Entität fragen, werden 2 zusätzliche Abfragen pro Ergebnis benötigt, was normalerweise nicht gewünscht wird.
Bozho
7
Es wäre großartig, eine Erklärung zu haben, warum dies das Problem löst.
Webnet
290

Wechseln Sie einfach von ListTyp zu SetTyp.

Aber denken Sie daran, dass Sie das zugrunde liegende kartesische Produkt, wie es von Vlad Mihalcea in seiner Antwort beschrieben wurde, nicht beseitigen werden !

Ahmad Zyoud
quelle
42
Eine Liste und ein Set sind nicht dasselbe: Ein Set bewahrt nicht die Ordnung
Matteo
17
LinkedHashSet behält Ordnung bei
egallardo
15
Dies ist eine wichtige Unterscheidung und, wenn Sie darüber nachdenken, völlig richtig. Das typische Viele-zu-Eins, das von einem Fremdschlüssel in der Datenbank implementiert wird, ist wirklich keine Liste, sondern ein Satz, da die Reihenfolge nicht beibehalten wird. Set ist also wirklich passender. Ich denke, das macht den Unterschied im Winterschlaf, obwohl ich nicht weiß warum.
narr4jesus
3
Ich hatte das gleiche kann nicht mehrere Taschen gleichzeitig holen, aber nicht wegen Anmerkungen. In meinem Fall machte ich Linksverbindungen und Disjunktionen mit den beiden *ToMany. Das Ändern des Typs Setlöste auch mein Problem. Hervorragende und saubere Lösung. Dies sollte die offizielle Antwort sein.
L. Holanda
20
Ich mochte die Antwort, aber die Millionen-Dollar-Frage lautet: Warum? Warum mit Set keine Ausnahmen anzeigen? Vielen Dank
Hinotori
140

Fügen Sie Ihrem Code eine Hibernate-spezifische @ Fetch-Annotation hinzu:

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

Dies sollte das Problem im Zusammenhang mit dem Hibernate-Fehler HHH-1718 beheben

Dave Richardson
quelle
5
@ DaveRlz warum subSelect dieses Problem löst. Ich habe Ihre Lösung und ihre Funktionsweise ausprobiert, weiß aber nicht, wie das Problem damit gelöst wurde?
HakunaMatata
Dies ist die beste Antwort, es sei denn, eine Setmacht wirklich Sinn. Eine einzelne OneToManyBeziehung unter Verwendung eines SetErgebnisses führt zu 1+<# relationships>Abfragen, während die Verwendung von FetchMode.SUBSELECTErgebnissen zu 1+1Abfragen führt. Wenn Sie die Anmerkung in der akzeptierten Antwort ( LazyCollectionOption.FALSE) verwenden, werden noch mehr Abfragen ausgeführt.
mstrthealias
1
FetchType.EAGER ist hierfür keine geeignete Lösung. Müssen mit Hibernate Fetch Profiles fortfahren und müssen es lösen
Milinda Bandara
2
Die beiden anderen Top-Antworten haben mein Problem nicht gelöst. Dieser tat es. Vielen Dank!
Blindworks
3
Weiß jemand, warum SUBSELECT das Problem behebt, JOIN jedoch nicht?
Innokenty
42

Diese Frage war sowohl in StackOverflow als auch im Hibernate-Forum ein wiederkehrendes Thema, daher habe ich beschlossen, die Antwort auch in einen Artikel umzuwandeln.

In Anbetracht dessen haben wir die folgenden Entitäten:

Geben Sie hier die Bildbeschreibung ein

Und Sie möchten einige übergeordnete PostEntitäten zusammen mit allen commentsund abrufentags Sammlungen abrufen.

Wenn Sie mehr als eine JOIN FETCHDirektive verwenden:

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

Der Winterschlaf wird das berüchtigte werfen:

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
]

Im Ruhezustand können nicht mehr als eine Tasche abgerufen werden, da dies zu einem kartesischen Produkt führen würde .

Die schlechteste "Lösung"

Jetzt finden Sie viele Antworten, Blog-Beiträge, Videos oder andere Ressourcen, in denen Sie aufgefordert werden, für Ihre Sammlungen ein Setanstelle eines Listzu verwenden.

Das ist ein schrecklicher Rat. Tu das nicht!

Wenn Sie Setsanstelle von verwenden, Listswird das MultipleBagFetchExceptionProblem behoben, aber das kartesische Produkt bleibt erhalten, was sogar noch schlimmer ist, da Sie das Leistungsproblem lange nach dem Anwenden dieses "Fixes" herausfinden werden.

Die richtige Lösung

Sie können den folgenden Trick ausführen:

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

distinctGehen Sie in der ersten JPQL-Abfrage NICHT zur SQL-Anweisung. Aus diesem Grund setzen wir den PASS_DISTINCT_THROUGHJPA-Abfragehinweis auf false.

DISTINCT hat in JPQL zwei Bedeutungen, und hier benötigen wir es, um die von getResultListder Java-Seite zurückgegebenen Java-Objektreferenzen zu deduplizieren , nicht die SQL-Seite. Weitere Informationen finden Sie in diesem Artikel .

Solange Sie höchstens eine Sammlung mit abrufen JOIN FETCH, ist alles in Ordnung.

Wenn Sie mehrere Abfragen verwenden, vermeiden Sie das kartesische Produkt, da jede andere Sammlung als die erste mithilfe einer sekundären Abfrage abgerufen wird.

Sie können noch mehr tun

Wenn Sie die FetchType.EAGERStrategie zum Mapping-Zeitpunkt für @OneToManyoder für @ManyToManyAssoziationen verwenden, kann dies leicht zu einem Ergebnis führenMultipleBagFetchException .

Sie sollten besser von zu wechseln FetchType.EAGER, Fetchype.LAZYda eifriges Abrufen eine schreckliche Idee ist, die zu kritischen Problemen mit der Anwendungsleistung führen kann .

Fazit

Vermeiden Sie FetchType.EAGERund wechseln Sie nicht von Listzu, Setnur weil der Ruhezustand das MultipleBagFetchExceptionunter dem Teppich versteckt . Holen Sie sich jeweils nur eine Sammlung, und alles wird gut.

Solange Sie dies mit der gleichen Anzahl von Abfragen tun, wie Sie Sammlungen initialisieren müssen, ist alles in Ordnung. Initialisieren Sie die Sammlungen nur nicht in einer Schleife, da dies N + 1-Abfrageprobleme auslöst , die auch die Leistung beeinträchtigen.

Vlad Mihalcea
quelle
Danke für das geteilte Wissen. Ist DISTINCTjedoch Leistungskiller in dieser Lösung. Gibt es eine Möglichkeit, loszuwerden distinct? (versuchte Set<...>stattdessen zurückzukehren, half nicht viel)
Leonid Dashko
1
DISTINCT wechselt nicht zur SQL-Anweisung. Deshalb PASS_DISTINCT_THROUGHist eingestellt auf false. DISTINCT hat in JPQL zwei Bedeutungen, und hier müssen wir es auf der Java-Seite deduplizieren, nicht auf der SQL-Seite. Weitere Informationen finden Sie in diesem Artikel .
Vlad Mihalcea
Vlad, danke für die Hilfe, die ich wirklich nützlich finde. Das Problem hing jedoch damit zusammen hibernate.jdbc.fetch_size(schließlich stellte ich es auf 350 ein). Wissen Sie zufällig, wie Sie verschachtelte Beziehungen optimieren können? ZB Entität1 -> Entität2 -> Entität3.1, Entität 3.2 (wobei Entität3.1 / 3.2 @ OneToMany-Beziehungen sind)
Leonid Dashko
1
@LeonidDashko Im Kapitel Abrufen in meinem Hochleistungs-Java-Persistenzbuch finden Sie viele Tipps zum Abrufen von Daten.
Vlad Mihalcea
1
Nein, du kannst nicht. Denken Sie in Bezug auf SQL darüber nach. Sie können nicht mehreren Eins-zu-Viele-Assoziationen beitreten, ohne ein kartesisches Produkt zu generieren.
Vlad Mihalcea
31

Nachdem ich es mit jeder einzelnen Option versucht hatte, die in diesen und anderen Beiträgen beschrieben wurde, kam ich zu dem Schluss, dass das Update wie folgt ist.

In jedem XToMany Ort @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) und dazwischen

@Fetch(value = FetchMode.SUBSELECT)

Das hat bei mir funktioniert

Javier
quelle
5
Hinzufügen @Fetch(value = FetchMode.SUBSELECT)war genug
user2601995
1
Dies ist eine Lösung nur für den Ruhezustand. Was ist, wenn Sie eine gemeinsam genutzte JPA-Bibliothek verwenden?
Michel
3
Ich bin mir sicher, dass Sie das nicht wollten, aber DaveRlz hat das schon 3 Jahre zuvor geschrieben
phil294
21

Um es zu beheben nehmen Sie einfach Setanstelle der Listfür verschachteltes Objekt.

@OneToMany
Set<Your_object> objectList;

und vergessen Sie nicht zu verwenden fetch=FetchType.EAGER

es wird klappen.

CollectionIdIn Hibernate gibt es noch ein weiteres Konzept, wenn Sie sich nur an die Liste halten möchten.

Aber denken Sie daran, dass Sie das zugrunde liegende kartesische Produkt, wie es von Vlad Mihalcea in seiner Antwort beschrieben wurde, nicht beseitigen werden !

Prateek Singh
quelle
6

Sie können Stand-EAGER-Listen in JPA behalten und mindestens einer von ihnen die JPA-Anmerkung @OrderColumn hinzufügen (mit offensichtlich dem Namen eines zu bestellenden Felds). Keine spezifischen Anmerkungen zum Ruhezustand erforderlich. Beachten Sie jedoch, dass leere Elemente in der Liste erstellt werden können, wenn das ausgewählte Feld keine Werte ab 0 enthält

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

In Children sollten Sie dann das Feld orderIndex hinzufügen

Massimo
quelle
2

Wir haben Set anstelle von List ausprobiert und es ist ein Albtraum: Wenn Sie zwei neue Objekte hinzufügen, können equals () und hashCode () beide nicht unterscheiden! Weil sie keinen Ausweis haben.

Typische Tools wie Eclipse generieren diese Art von Code aus Datenbanktabellen:

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

Sie können auch diesen Artikel lesen , der richtig erklärt, wie durcheinander JPA / Hibernate ist. Nachdem ich dies gelesen habe, denke ich, dass dies das letzte Mal ist, dass ich ORM in meinem Leben benutze.

Ich habe auch Leute von Domain Driven Design getroffen, die im Grunde sagen, ORM sei eine schreckliche Sache.

Mike Dudley
quelle
1

Wenn Sie zu komplexe Objekte mit mehreren Sammlungen haben, ist es möglicherweise keine gute Idee, alle Objekte mit EAGER fetchType zu verwenden. Verwenden Sie besser LAZY, und wenn Sie die Sammlungen wirklich laden müssen, verwenden Sie:, Hibernate.initialize(parent.child)um die Daten abzurufen.

Felipe Cadena
quelle
0

Für mich bestand das Problem darin, verschachtelte EAGER- Abrufe zu haben.

Eine Lösung besteht darin, die verschachtelten Felder auf LAZY zu setzen und Hibernate.initialize () zu verwenden , um die verschachtelten Felder zu laden :

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

Am Ende geschah dies, als ich mehrere Sammlungen mit FetchType.EAGER hatte, wie folgt:

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

Außerdem wurden die Sammlungen in derselben Spalte zusammengefasst.

Um dieses Problem zu lösen, habe ich eine der Sammlungen in FetchType.LAZY geändert, da dies für meinen Anwendungsfall in Ordnung war.

Viel Glück! ~ J.

JAD
quelle
0

Beides zu kommentieren Fetchund LazyCollectionmanchmal hilft es, ein Projekt auszuführen.

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)
Prisar
quelle
0

Eine gute Sache @LazyCollection(LazyCollectionOption.FALSE)ist, dass mehrere Felder mit dieser Anmerkung koexistieren FetchType.EAGERkönnen, während dies nicht möglich ist, selbst in Situationen, in denen eine solche Koexistenz legitim ist.

Zum Beispiel kann ein Ordereine Liste von OrderGroup(eine kurze) sowie eine Liste von Promotions(auch kurz) haben. @LazyCollection(LazyCollectionOption.FALSE)kann , ohne dass auf beide verwendet werden , LazyInitializationExceptionweder MultipleBagFetchException.

In meinem Fall @Fetchhabe ich mein Problem gelöst, MultipleBacFetchExceptionaber dann LazyInitializationExceptionden berüchtigten no SessionFehler verursacht.

WesternGun
quelle
-5

Sie können eine neue Anmerkung verwenden, um dies zu lösen:

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

Tatsächlich ist der Standardwert von fetch auch FetchType.LAZY.

leee41
quelle
5
JPA3.0 existiert nicht.
holmis83