615 Stimmen

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

Hibernate löst diese Ausnahme bei der Erstellung der SessionFactory aus:

org.hibernate.loader.MultipleBagFetchException: kann nicht mehrere Taschen gleichzeitig abrufen

Dies ist mein Testfall:

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

}

Kind.java

@Entity
public Child {

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

 @ManyToOne
 private Parent parent;

}

Was ist mit diesem Problem? Was kann ich tun?


EDITAR

OK, das Problem, das ich habe, ist, dass eine andere "übergeordnete" Entität innerhalb meiner übergeordneten ist, mein wirkliches Verhalten ist dies:

Elternteil.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...

Entfernen von FetchType.EAGER de Parent o AnotherParent löst das Problem, aber ich brauche es, also ist die wirkliche Lösung die Verwendung von @LazyCollection(LazyCollectionOption.FALSE) anstelle von FetchType (Dank an Bozho für die Lösung).

2 Stimmen

Ich würde fragen, welche SQL-Abfrage Sie zu erstellen hoffen, die zwei separate Sammlungen gleichzeitig abruft? Die Arten von SQL, mit denen dies möglich wäre, würden entweder eine kartesische Verknüpfung (potenziell höchst ineffizient) oder eine UNION disjunkter Spalten (ebenfalls hässlich) erfordern. Vermutlich hat die Unfähigkeit, dies in SQL auf eine saubere und effiziente Weise zu erreichen, das API-Design beeinflusst.

0 Stimmen

@ThomasW Dies sind die Sql-Abfragen, die das Programm generieren sollte: select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id

2 Stimmen

Ein ähnlicher Fehler kann auftreten, wenn Sie mehr als eine List<child> con fetchType definiert für mehr als eine List<clield>

691voto

Bozho Punkte 570413

Ich denke, eine neuere Version von Hibernate (mit Unterstützung von JPA 2.0) sollte dies handhaben. Aber ansonsten können Sie es umgehen, indem Sie die Sammlungsfelder mit annotieren:

@LazyCollection(LazyCollectionOption.FALSE)

Denken Sie daran, die fetchType Attribut aus dem @*ToMany Bemerkung.

Beachten Sie jedoch, dass in den meisten Fällen eine Set<Child> ist besser geeignet als List<Child> Wenn Sie also nicht wirklich eine List - anstreben Set

Aber denken Sie daran, dass Sie bei der Verwendung von Sets wird nicht die Unterlage zu beseitigen Kartesisches Produkt wie beschrieben von Vlad Mihalcea in seiner Antwort ¡!

1 Stimmen

Ich verwende hibernate 3.6, ich habe Ihren Vorschlag ausprobiert, aber das Problem bleibt.

6 Stimmen

Seltsam, bei mir hat es funktioniert. Haben Sie die fetchType von der @*ToMany ?

1 Stimmen

Oh, Entschuldigung, jetzt funktioniert es. Aber was ist der Grund für dieses Problem? Ich habe fetchtype EAGER schon oft in der gleichen Situation ohne Probleme benutzt, warum habe ich jetzt dieses Problem? Danke für die Antwort.

332voto

Ahmad Zyoud Punkte 3328

Wechseln Sie einfach von List Typ zu Set Typ.

Aber denken Sie daran, dass Sie wird nicht die Unterlage zu beseitigen Kartesisches Produkt wie beschrieben von Vlad Mihalcea in seiner Antwort ¡!

44 Stimmen

Eine Liste und eine Menge sind nicht dasselbe: Eine Menge bewahrt keine Ordnung.

19 Stimmen

LinkedHashSet bewahrt die Ordnung

18 Stimmen

Dies ist eine wichtige Unterscheidung und, wenn man darüber nachdenkt, völlig richtig. Das typische "many-to-one", das durch einen Fremdschlüssel in der DB implementiert wird, ist eigentlich keine Liste, sondern eine Menge, da die Reihenfolge nicht beibehalten wird. Set ist also wirklich besser geeignet. Ich denke, das macht den Unterschied in Hibernate aus, obwohl ich nicht weiß, warum.

212voto

Vlad Mihalcea Punkte 121171

Wir haben die folgenden Einrichtungen:

enter image description here

Und, Sie wollen ein Elternteil abholen Post Einheiten zusammen mit allen comments y tags Sammlungen.

Wenn Sie mehr als eine JOIN FETCH Richtlinien:

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 wirft die berüchtigte:

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 erlaubt es nicht, mehr als eine Tasche abzurufen, da dies ein kartesisches Produkt erzeugen würde.

Die schlechteste "Lösung"

Sie werden viele Antworten, Blogbeiträge, Videos oder andere Ressourcen finden, die Ihnen sagen, dass Sie eine Set anstelle einer List für Ihre Sammlungen.

Das ist ein schlechter Ratschlag. Tun Sie das nicht!

Verwendung von Sets anstelle von Lists macht die MultipleBagFetchException verschwinden, aber das kartesische Produkt bleibt bestehen, was eigentlich noch schlimmer ist, da Sie das Leistungsproblem erst lange nach der Anwendung dieser "Lösung" bemerken werden.

Die richtige Lösung

Sie können den folgenden Trick anwenden:

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

In der ersten JPQL-Abfrage, distinct geht NICHT zur SQL-Anweisung. Deshalb setzen wir die PASS_DISTINCT_THROUGH JPA-Abfrage-Hinweis zu false .

DISTINCT hat in JPQL zwei Bedeutungen, und hier brauchen wir es, um die Java-Objektreferenzen zu deduplizieren, die von getResultList auf der Java-Seite, nicht auf der SQL-Seite.

Solange Sie höchstens eine Sammlung mit JOIN FETCH dann wird es Ihnen gut gehen.

Durch die Verwendung mehrerer Abfragen vermeiden Sie das kartesische Produkt, da jede andere Sammlung als die erste über eine zweite Abfrage abgerufen wird.

Sie könnten noch mehr tun

Wenn Sie die FetchType.EAGER Strategie zur Kartierungszeit für @OneToMany o @ManyToMany Assoziationen, dann kann es leicht zu einer MultipleBagFetchException .

Am besten wechseln Sie von FetchType.EAGER a Fetchype.LAZY da eager fetching eine schreckliche Idee ist, die zu kritischen Leistungsproblemen der Anwendung führen kann.

Schlussfolgerung

Vermeiden Sie FetchType.EAGER und wechseln Sie nicht von List a Set nur weil Hibernate dadurch die MultipleBagFetchException unter den Teppich gekehrt. Holen Sie immer nur eine Sammlung auf einmal, dann wird alles gut.

Solange Sie dies mit der gleichen Anzahl von Abfragen tun, wie Sie Sammlungen zu initialisieren haben, ist alles in Ordnung. Initialisieren Sie die Sammlungen nur nicht in einer Schleife, da dies zu N+1-Abfrageproblemen führt, die ebenfalls schlecht für die Leistung sind.

0 Stimmen

Danke für das geteilte Wissen. Wie auch immer, DISTINCT ist bei dieser Lösung ein Leistungskiller. Gibt es eine Möglichkeit, das Problem loszuwerden? distinct ? (versucht, zurückzukehren Set<...> stattdessen, hat nicht viel geholfen)

2 Stimmen

Vlad, danke für die Hilfe, ich finde sie wirklich nützlich. Allerdings war das Problem im Zusammenhang mit hibernate.jdbc.fetch_size (schließlich habe ich ihn auf 350 eingestellt). Wissen Sie zufällig, wie man verschachtelte Beziehungen optimieren kann? Z.B. entity1 -> entity2 -> entity3.1, entity 3.2 (wobei entity3.1 / 3.2 @OneToMany Beziehungen sind)

0 Stimmen

Können wir das kartesische Produkt nicht vermeiden, indem wir DISTINCT in einer einzigen jpql-Abfrage mit Set ?

186voto

Dave Richardson Punkte 4631

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

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

Dies sollte das Problem beheben, das mit dem Hibernate-Fehler zusammenhängt HHH-1718

6 Stimmen

@DaveRlz warum subSelect dieses Problem löst. Ich habe versucht, Ihre Lösung und seine Arbeit, aber nicht wissen, wie das Problem gelöst wurde, indem Sie diese?

4 Stimmen

Dies ist die beste Antwort, es sei denn, ein Set wirklich Sinn macht. Mit einer einzigen OneToMany Beziehung über eine Set führt zu 1+<# relationships> Abfragen, während bei der Verwendung von FetchMode.SUBSELECT führt zu 1+1 Abfragen. Auch die Verwendung des Vermerks in der akzeptierten Antwort ( LazyCollectionOption.FALSE ) führt dazu, dass noch mehr Abfragen ausgeführt werden.

1 Stimmen

FetchType.EAGER ist keine geeignete Lösung für dieses Problem. Müssen mit Hibernate Fetch Profiles fortfahren und müssen es lösen

49voto

Javier Punkte 557

Nach dem Ausprobieren jeder einzelnen Option, die in diesem und anderen Beiträgen beschrieben wird, bin ich zu dem Schluss gekommen, dass die Lösung die folgende ist.

In jeder XToMany-Stelle @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) und unmittelbar danach

@Fetch(value = FetchMode.SUBSELECT)

Das hat bei mir funktioniert

7 Stimmen

Hinzufügen von @Fetch(value = FetchMode.SUBSELECT) war genug

1 Stimmen

Dies ist eine reine Hibernate-Lösung. Was ist, wenn Sie eine gemeinsam genutzte JPA-Bibliothek verwenden?

5 Stimmen

Ich bin mir sicher, dass du das nicht wolltest, aber DaveRlz hat das Gleiche schon 3 Jahre zuvor geschrieben

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X