Hibernate genera MultipleBagFetchException - non è possibile recuperare contemporaneamente più sacchi


471

Hibernate genera questa eccezione durante la creazione di SessionFactory:

org.hibernate.loader.MultipleBagFetchException: impossibile recuperare contemporaneamente più sacchetti

Questo è il mio caso di prova:

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;

}

Che ne dici di questo problema? Cosa posso fare?


MODIFICARE

OK, il problema che ho è che un'altra entità "genitore" è dentro il mio genitore, il mio vero comportamento è questo:

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;

}

A Hibernate non piacciono due raccolte FetchType.EAGER, ma questo sembra essere un bug, non sto facendo cose insolite ...

Rimozione FetchType.EAGERdal Parento AnotherParentrisolve il problema, ma bisogno, così vera soluzione è quella di utilizzare @LazyCollection(LazyCollectionOption.FALSE)invece FetchType(grazie a Bozho per la soluzione).


Vorrei chiedere, quale query SQL speri di generare per recuperare contemporaneamente due raccolte separate? I tipi di SQL che sarebbero in grado di raggiungerli richiederebbero un join cartesiano (potenzialmente altamente inefficiente) o UNIONE di colonne disgiunte (anche brutte). Presumibilmente l'incapacità di raggiungere questo obiettivo in SQL in modo pulito ed efficiente ha influenzato la progettazione dell'API.
Thomas W,

@ThomasW Queste sono le query sql che dovrebbe generare:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
nurettin

1
È possibile ottenere un errore di simillar se si dispone di più di uno List<child>con fetchTypedefinito per più di una List<clield>
Big Zed

Risposte:


555

Penso che una nuova versione di ibernazione (che supporta JPA 2.0) dovrebbe gestirlo. Ma altrimenti puoi aggirare il problema annotando i campi della raccolta con:

@LazyCollection(LazyCollectionOption.FALSE)

Ricorda di rimuovere l' fetchTypeattributo dall'annotazione @*ToMany.

Ma nota che nella maggior parte dei casi a Set<Child>è più appropriato di List<Child>, quindi a meno che tu non abbia davvero bisogno di List- vai perSet

Ma ricorda che con l'utilizzo dei set non eliminerai il Prodotto cartesiano sottostante come descritto da Vlad Mihalcea nella sua risposta !


4
strano, ha funzionato per me. Hai rimosso il fetchTypeda @*ToMany?
Bozho,

101
il problema è che le annotazioni JPA vengono analizzate per non consentire più di 2 raccolte caricate con entusiasmo. Ma le annotazioni specifiche dell'ibernazione lo consentono.
Bozho,

14
La necessità di più di 1 EAGER sembra totalmente realistica. Questa limitazione è solo una svista dell'APP? Quali sono le preoccupazioni che dovrei cercare quando ho EAGER muliple?
AR3Y35,

6
il fatto è che l'ibernazione non può recuperare le due raccolte con una query. Pertanto, quando esegui una query per l'entità padre, saranno necessarie 2 query aggiuntive per risultato, che di solito è qualcosa che non desideri.
Bozho,

7
Sarebbe bello avere una spiegazione sul perché questo risolva il problema.
Webnet,

290

Passa semplicemente da un Listtipo all'altro Set.

Ma ricorda che non eliminerai il Prodotto cartesiano sottostante come descritto da Vlad Mihalcea nella sua risposta !


42
Una lista e un set non sono la stessa cosa: un set non conserva l'ordine
Matteo

17
LinkedHashSet conserva l'ordine
egallardo,

15
Questa è una distinzione importante e, quando ci pensi, del tutto corretta. Il multiplo tipico implementato da una chiave esterna nel DB non è in realtà un elenco, è un set perché l'ordine non viene conservato. Quindi Set è davvero più appropriato. Penso che questo faccia la differenza in letargo, anche se non so perché.
fool4jesus,

3
Stavo avendo lo stesso non è possibile recuperare contemporaneamente più borse ma non a causa delle annotazioni. Nel mio caso, stavo facendo join sinistro e disgiunzioni con i due *ToMany. Cambiare il tipo per Setrisolvere anche il mio problema. Soluzione eccellente e ordinata. Questa dovrebbe essere la risposta ufficiale.
L. Holanda,

20
Mi è piaciuta la risposta, ma la domanda da un milione di dollari è: perché? Perché con Set non mostrare eccezioni? Grazie
Hinotori,

140

Aggiungi un'annotazione @Fetch specifica di Hibernate al tuo codice:

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

Ciò dovrebbe risolvere il problema, correlato al bug di Hibernate HHH-1718


5
@DaveRlz perché subSelect risolve questo problema. Ho provato la tua soluzione e il suo funzionamento, ma non sai come è stato risolto il problema utilizzando questo?
Hakuna Matata,

Questa è la risposta migliore a meno che non abbia Setdavvero senso. Avere una singola OneToManyrelazione usando un Setrisultato in 1+<# relationships>query, dove come usando FetchMode.SUBSELECTrisultati in 1+1query. Inoltre, l'utilizzo dell'annotazione nella risposta accettata ( LazyCollectionOption.FALSE) comporta l'esecuzione di ancora più query.
mstrthealias,

1
FetchType.EAGER non è una soluzione adeguata per questo. Devo procedere con i profili Hibernate Fetch e devi risolverlo
Milinda Bandara,

2
Le altre due risposte principali non hanno risolto il mio problema. Questo ha fatto. Grazie!
Blindworks,

3
Qualcuno sa perché SUBSELECT lo risolve, ma JOIN no?
Innokenty,

42

Questa domanda è stata un tema ricorrente sia su StackOverflow che sul forum Hibernate, quindi ho deciso di trasformare anche la risposta in un articolo .

Considerando che abbiamo le seguenti entità:

inserisci qui la descrizione dell'immagine

E, vuoi recuperare alcune Postentità padre insieme a tutte le raccolte commentse tags.

Se stai usando più di una JOIN FETCHdirettiva:

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 lancerà il famigerato:

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 non consente di recuperare più di una borsa perché ciò genererebbe un prodotto cartesiano .

La peggiore "soluzione"

Ora troverai molte risposte, post di blog, video o altre risorse che ti dicono di utilizzare un Setinvece di un Listper le tue raccolte.

È un consiglio terribile. Non farlo!

Usando Setsinvece di Listsfarai MultipleBagFetchExceptionandare via, ma il Prodotto cartesiano sarà ancora lì, il che è in realtà anche peggio, poiché scoprirai il problema delle prestazioni molto tempo dopo aver applicato questa "correzione".

La soluzione corretta

Puoi fare il seguente trucco:

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

Nella prima query JPQL, distinctNON passa all'istruzione SQL. Ecco perché impostiamo il PASS_DISTINCT_THROUGHsuggerimento per la query JPA su false.

DISTINCT ha due significati in JPQL, e qui ne abbiamo bisogno per deduplicare i riferimenti agli oggetti Java restituiti dal getResultListlato Java, non dal lato SQL. Dai un'occhiata a questo articolo per maggiori dettagli.

Finché recupererai al massimo una raccolta usando JOIN FETCH, starai bene.

Utilizzando più query, eviterai il prodotto cartesiano da qualsiasi altra raccolta ma la prima viene recuperata utilizzando una query secondaria.

C'è dell'altro che potresti fare

Se stai usando la FetchType.EAGERstrategia al momento della mappatura per @OneToManyo @ManyToManyassociazioni, potresti facilmente finire con un MultipleBagFetchException.

È meglio passare da FetchType.EAGERa Fetchype.LAZYpoiché il recupero impaziente è un'idea terribile che può portare a problemi di prestazioni dell'applicazione critici .

Conclusione

Evita FetchType.EAGERe non passare da Lista Setsolo perché così facendo Hibernate nasconderà MultipleBagFetchExceptionil tappeto. Prendi solo una collezione alla volta e andrà tutto bene.

Fintanto che lo fai con lo stesso numero di query che hai raccolte da inizializzare, stai bene. Basta non inizializzare le raccolte in un ciclo, in quanto ciò causerà problemi di query N + 1 , che sono anche dannosi per le prestazioni.


Grazie per la conoscenza condivisa. Tuttavia, questo DISTINCTè un killer delle prestazioni in questa soluzione. C'è un modo per sbarazzarsi di distinct? (ha cercato di tornare Set<...>invece, non ha aiutato molto)
Leonid Dashko,

1
DISTINCT non passa all'istruzione SQL. Ecco perché PASS_DISTINCT_THROUGHè impostato su false. DISTINCT ha 2 significati in JPQL, e qui ne abbiamo bisogno per deduplicare sul lato Java, non sul lato SQL. Dai un'occhiata a questo articolo per maggiori dettagli.
Vlad Mihalcea,

Vlad, grazie per l'aiuto lo trovo davvero utile. Tuttavia, il problema era correlato hibernate.jdbc.fetch_size(alla fine l'ho impostato su 350). Per caso, sai come ottimizzare le relazioni nidificate? Ad esempio entità1 -> entità2 -> entità3.1, entità 3.2 (dove entità3.1 / 3.2 sono relazioni @OneToMany)
Leonid Dashko

1
@LeonidDashko Dai un'occhiata al capitolo Recupero nel mio libro Persistence Java ad alte prestazioni per molti suggerimenti relativi al recupero dei dati.
Vlad Mihalcea,

1
No, non puoi. Pensaci in termini di SQL. Non puoi ISCRIVITI a più associazioni uno-a-molte senza generare un prodotto cartesiano.
Vlad Mihalcea,

31

Dopo aver provato con ogni singola opzione descritta in questo post e in altri, sono giunto alla conclusione che la correzione è la seguente.

In ogni XToMany posto @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) e intermedi dopo

@Fetch(value = FetchMode.SUBSELECT)

Questo ha funzionato per me


5
l'aggiunta è @Fetch(value = FetchMode.SUBSELECT)stata sufficiente
user2601995

1
Questa è una soluzione unica ibernazione. Cosa succede se si utilizza una libreria JPA condivisa?
Michel,

3
Sono sicuro che non intendevi farlo, ma DaveRlz ha già scritto la stessa cosa 3 anni prima
phil294

21

Per risolvere il problema è sufficiente prendere Setin luogo di Listper il vostro oggetto nidificato.

@OneToMany
Set<Your_object> objectList;

e non dimenticare di usare fetch=FetchType.EAGER

Funzionerà.

C'è un altro concetto CollectionIdin Hibernate se vuoi restare solo con la lista.

Ma ricorda che non eliminerai il Prodotto cartesiano sottostante come descritto da Vlad Mihalcea nella sua risposta !



6

puoi tenere gli elenchi EAGER dello stand in JPA e aggiungere ad almeno uno di essi l'annotazione JPA @OrderColumn (con ovviamente il nome di un campo da ordinare). Non sono necessarie specifiche annotazioni di ibernazione. Ma tieni presente che potrebbe creare elementi vuoti nell'elenco se il campo scelto non ha valori a partire da 0

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

in Bambini quindi è necessario aggiungere il campo orderIndex


2

Abbiamo provato Set invece di List ed è un incubo: quando aggiungi due nuovi oggetti, equals () e hashCode () non riescono a distinguerli entrambi! Perché non hanno alcun ID.

strumenti tipici come Eclipse generano quel tipo di codice dalle tabelle del database:

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

Puoi anche leggere questo articolo che spiega correttamente quanto è incasinato JPA / Hibernate. Dopo aver letto questo, penso che questa sia l'ultima volta che uso qualsiasi ORM nella mia vita.

Ho anche incontrato ragazzi di Domain Driven Design che fondamentalmente dicono che gli ORM sono una cosa terribile.


1

Quando hai oggetti troppo complessi con raccolta saveral non potrebbe essere una buona idea averli tutti con EAGER fetchType, usa meglio LAZY e quando hai davvero bisogno di caricare le raccolte usa: Hibernate.initialize(parent.child)per recuperare i dati.


0

Per me, il problema era aver recuperato i recuperi EAGER .

Una soluzione è impostare i campi nidificati su LAZY e utilizzare Hibernate.initialize () per caricare i campi nidificati:

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

0

Alla mia fine, questo è successo quando ho avuto più raccolte con FetchType.EAGER, in questo modo:

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

Inoltre, le raccolte si stavano unendo sulla stessa colonna.

Per risolvere questo problema, ho cambiato una delle raccolte in FetchType.LAZY poiché andava bene per il mio caso d'uso.

In bocca al lupo! ~ J


0

Commentare entrambi Fetche LazyCollectiontalvolta aiuta a eseguire il progetto.

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

0

Una cosa positiva @LazyCollection(LazyCollectionOption.FALSE)è che diversi campi con questa annotazione possono coesistere mentreFetchType.EAGER non possono, anche nelle situazioni in cui tale convivenza è legittima.

Ad esempio, un Orderpuò avere un elenco di OrderGroup(uno breve) nonché un elenco di Promotions(anche breve). @LazyCollection(LazyCollectionOption.FALSE)può essere utilizzato su entrambi senza causare LazyInitializationExceptionnessuno dei dueMultipleBagFetchException .

Nel mio caso @Fetchho risolto il mio problema, MultipleBacFetchExceptionma poi le cause LazyInitializationException, il famigerato no Sessionerrore.


-5

È possibile utilizzare una nuova annotazione per risolvere questo:

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

In effetti, il valore predefinito di fetch è anche FetchType.LAZY.


5
JPA3.0 non esiste.
holmis83,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.