Hibernate lanza MultipleBagFetchException: no puede recuperar simultáneamente varias bolsas

471

Hibernate lanza esta excepción durante la creación de SessionFactory:

org.hibernate.loader.MultipleBagFetchException: no puede recuperar simultáneamente varias bolsas

Este es mi caso de prueba:

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;

}

¿Qué tal este problema? ¿Que puedo hacer?


EDITAR

OK, el problema que tengo es que hay otra entidad "padre" dentro de mi padre, mi comportamiento real es este:

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 no le gustan dos colecciones FetchType.EAGER, pero esto parece ser un error, no estoy haciendo cosas inusuales ...

La eliminación FetchType.EAGERdel Parento AnotherParentresuelve el problema, sino que lo necesita, por lo que verdadera solución es utilizar @LazyCollection(LazyCollectionOption.FALSE)en lugar de FetchType(gracias a Bozho para la solución).

soplo
fuente
Preguntaría, ¿qué consulta SQL espera generar que recupere dos colecciones separadas simultáneamente? Los tipos de SQL que podrían lograr esto requerirían una unión cartesiana (potencialmente altamente ineficiente) o una UNIÓN de columnas disjuntas (también feas). Presumiblemente, la incapacidad para lograr esto en SQL de una manera limpia y eficiente influyó en el diseño de la API.
Thomas W
@ThomasW Estas son las consultas SQL que debería generar:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
nurettin
1
Usted puede obtener un error de simillar si tiene más de una List<child>con la fetchTypedefinida por más de una List<clield>
Grandes Zed

Respuestas:

555

Creo que una versión más nueva de hibernate (compatible con JPA 2.0) debería manejar esto. Pero de lo contrario, puede solucionarlo anotando los campos de recopilación con:

@LazyCollection(LazyCollectionOption.FALSE)

Recuerde eliminar el fetchTypeatributo de la @*ToManyanotación.

Pero tenga en cuenta que en la mayoría de los casos a Set<Child>es más apropiado que List<Child>, por lo tanto, a menos que realmente necesite un List- vaya aSet

Pero recuerde que con el uso de conjuntos no eliminará la capa subyacente Producto cartesiano como lo describe Vlad Mihalcea en su respuesta !

Bozho
fuente
44
extraño, me ha funcionado. ¿Quitaste el fetchTypede la @*ToMany?
Bozho
101
El problema es que las anotaciones JPA se analizan para no permitir más de 2 colecciones cargadas con entusiasmo. Pero las anotaciones específicas de hibernación lo permiten.
Bozho
14
La necesidad de más de 1 EAGER parece totalmente realista. ¿Es esta limitación solo una supervisión de JPA? ¿Cuáles son las preocupaciones que debo tener en cuenta al tener EAGER múltiples?
AR3Y35
66
La cuestión es que Hibernate no puede obtener las dos colecciones con una sola consulta. Entonces, cuando consulta la entidad principal, necesitará 2 consultas adicionales por resultado, que normalmente es algo que no desea.
Bozho
77
Sería genial tener una explicación de por qué esto resuelve el problema.
Webnet
290

Simplemente cambie de Listtipo a Settipo.

¡Pero recuerde que no eliminará el Producto cartesiano subyacente como lo describe Vlad Mihalcea en su respuesta !

Ahmad Zyoud
fuente
42
Una Lista y un Conjunto no son lo mismo: un conjunto no conserva el orden
Matteo
17
LinkedHashSet conserva el orden
egallardo
15
Esta es una distinción importante y, cuando lo piensas, completamente correcta. El típico muchos a uno implementado por una clave externa en la base de datos no es realmente una Lista, es un Conjunto porque el orden no se conserva. Entonces Set es realmente más apropiado. Creo que eso hace la diferencia en hibernar, aunque no sé por qué.
fool4jesus
3
Estaba teniendo lo mismo , no puedo buscar simultáneamente varias bolsas, pero no por las anotaciones. En mi caso, estaba haciendo uniones izquierdas y disyunciones con los dos *ToMany. Cambiar el tipo para Setresolver mi problema también. Excelente y ordenada solución. Esta debería ser la respuesta oficial.
L. Holanda
20
Me gustó la respuesta, pero la pregunta del millón es: ¿por qué? ¿Por qué con Set no se muestran excepciones? Gracias
Hinotori
140

Agregue una anotación @Fetch específica de Hibernate a su código:

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

Esto debería solucionar el problema relacionado con el error de Hibernate HHH-1718

Dave Richardson
fuente
55
@DaveRlz por qué subSelect resuelve este problema. Intenté su solución y está funcionando, pero ¿no sabe cómo se resolvió el problema al usar esto?
HakunaMatata
Esta es la mejor respuesta a menos que Setrealmente tenga sentido. Tener una sola OneToManyrelación utilizando Setresultados en 1+<# relationships>consultas, mientras que utilizando FetchMode.SUBSELECTresultados en 1+1consultas. Además, el uso de la anotación en la respuesta aceptada ( LazyCollectionOption.FALSE) hace que se ejecuten incluso más consultas.
mstrthealias
1
FetchType.EAGER no es una solución adecuada para esto. Necesito proceder con Hibernate Fetch Profiles y necesito resolverlo
Milinda Bandara
2
Las otras dos respuestas principales no resolvieron mi problema. Este lo hizo. ¡Gracias!
Blindworks
3
¿Alguien sabe por qué SUBSELECT lo arregla, pero JOIN no?
Innokenty
42

Esta pregunta ha sido un tema recurrente tanto en StackOverflow como en el foro de Hibernate, así que decidí convertir la respuesta en un artículo también.

Teniendo en cuenta que tenemos las siguientes entidades:

ingrese la descripción de la imagen aquí

Y quieres traer a algún padre Post entidades junto con todos los commentsytags colecciones .

Si está usando más de uno JOIN FETCH directiva:

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 arrojará a los infames:

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 no permite buscar más de una bolsa porque eso generaría un producto cartesiano .

La peor "solución"

Ahora, encontrará muchas respuestas, publicaciones de blog, videos u otros recursos que le indican que use un en Setlugar de unList para sus colecciones.

Ese es un consejo terrible. ¡No hagas eso!

Usar en Setslugar de Listshará que elMultipleBagFetchException desaparezca, pero el Producto cartesiano seguirá estando allí, lo que en realidad es aún peor, ya que descubrirá el problema de rendimiento mucho después de aplicar esta "solución".

La solución adecuada

Puedes hacer el siguiente truco:

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

En la primera consulta JPQL, distinctNO va a la instrucción SQL. Es por eso que configuramos la PASS_DISTINCT_THROUGHsugerencia de consulta JPA en false.

DISTINCT tiene dos significados en JPQL, y aquí, lo necesitamos para deduplicar las referencias de objetos Java devueltas getResultListen el lado de Java, no en el lado de SQL. Mira este artículo para más detalles.

Siempre que obtenga como máximo una colección usando JOIN FETCH , estará bien.

Al utilizar múltiples consultas, evitará el Producto cartesiano ya que cualquier otra colección, pero la primera se obtiene mediante una consulta secundaria.

Hay más que puedes hacer

Si está utilizando la FetchType.EAGERestrategia en el momento del mapeo @OneToManyo las @ManyToManyasociaciones, podría terminar fácilmente con unMultipleBagFetchException .

Es mejor cambiar de FetchType.EAGERa, Fetchype.LAZYya que la búsqueda ansiosa es una idea terrible que puede conducir a problemas críticos de rendimiento de la aplicación .

Conclusión

Evita FetchType.EAGERy no cambies Lista Setsolo porque hacerlo hará que Hibernate esconda MultipleBagFetchExceptiondebajo de la alfombra. Obtenga solo una colección a la vez y estará bien.

Siempre que lo haga con la misma cantidad de consultas que las colecciones para inicializar, está bien. Simplemente no inicialice las colecciones en un bucle, ya que eso provocará problemas de consulta N + 1 , que también son malos para el rendimiento.

Vlad Mihalcea
fuente
Gracias por el conocimiento compartido. Sin embargo, DISTINCTes asesino de rendimiento en esta solución. ¿Hay alguna forma de deshacerse de él distinct? (Intenté regresar Set<...>, no ayudó mucho)
Leonid Dashko
1
DISTINCT no va a la instrucción SQL. Por eso PASS_DISTINCT_THROUGHestá configurado como false. DISTINCT tiene 2 significados en JPQL, y aquí, lo necesitamos para deduplicar en el lado de Java, no en el lado de SQL. Mira este artículo para más detalles.
Vlad Mihalcea
Vlad, gracias por la ayuda, me parece realmente útil. Sin embargo, el problema estaba relacionado con hibernate.jdbc.fetch_size(eventualmente lo configuré en 350). Por casualidad, ¿sabes cómo optimizar las relaciones anidadas? Por ejemplo, entidad1 -> entidad2 -> entidad3.1, entidad 3.2 (donde entidad3.1 / 3.2 son relaciones @OneToMany)
Leonid Dashko
1
@LeonidDashko Consulte el capítulo Obtención de mi libro de Persistencia Java de alto rendimiento para obtener muchos consejos relacionados con la obtención de datos.
Vlad Mihalcea
1
No, no puedes. Piénselo en términos de SQL. No puede UNIRSE a múltiples asociaciones uno a muchos sin generar un Producto cartesiano.
Vlad Mihalcea
31

Después de probar con todas las opciones descritas en estas publicaciones y en otras, llegué a la conclusión de que la solución es la siguiente.

En cada lugar de XToMany @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) e intermediamente después

@Fetch(value = FetchMode.SUBSELECT)

Esto funciono para mi

Javier
fuente
55
agregar @Fetch(value = FetchMode.SUBSELECT)fue suficiente
user2601995
1
Esta es una solución única de Hibernate. ¿Qué sucede si está utilizando una biblioteca JPA compartida?
Michel
3
Estoy seguro de que no fue su intención, pero DaveRlz ya escribió lo mismo 3 años antes
phil294
21

Para solucionarlo, simplemente tome el Setlugar de Listsu objeto anidado.

@OneToMany
Set<Your_object> objectList;

y no olvides usar fetch=FetchType.EAGER

funcionará.

Hay un concepto mas CollectionId en Hibernate si desea seguir solo con la lista.

¡Pero recuerde que no eliminará el Producto cartesiano subyacente como lo describe Vlad Mihalcea en su respuesta !

Prateek Singh
fuente
6

puede mantener listas EAGER de stand en JPA y agregar al menos a una de ellas la anotación JPA @OrderColumn (obviamente con el nombre de un campo que se debe ordenar). No necesita anotaciones específicas de hibernación. Pero tenga en cuenta que podría crear elementos vacíos en la lista si el campo elegido no tiene valores que comiencen desde 0

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

en Niños, entonces debe agregar el campo orderIndex

Massimo
fuente
2

Intentamos Set en lugar de List y es una pesadilla: cuando agrega dos objetos nuevos, equals () y hashCode () no logran distinguirlos a ambos. Porque no tienen ninguna identificación.

Las herramientas típicas como Eclipse generan ese tipo de código de las tablas de la base de datos:

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

También puede leer este artículo que explica correctamente qué tan desordenado es JPA / Hibernate. Después de leer esto, creo que es la última vez que uso un ORM en mi vida.

También me he encontrado con tipos de diseño impulsado por dominio que básicamente dicen que ORM es algo terrible.

Mike Dudley
fuente
1

Cuando tenga objetos demasiado complejos con una colección saveral, no podría ser una buena idea tenerlos todos con EAGER fetchType, mejor use LAZY y cuando realmente necesite cargar las colecciones, use: Hibernate.initialize(parent.child)para buscar los datos.

Felipe Cadena
fuente
0

Para mí, el problema era haber anidado las recuperaciones de EAGER .

Una solución es establecer los campos anidados en LAZY y usar Hibernate.initialize () para cargar los campos anidados:

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());
mantequilla de ciruela
fuente
0

Al final, esto sucedió cuando tuve varias colecciones con FetchType.EAGER, así:

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

Además, las colecciones se unieron en la misma columna.

Para resolver este problema, cambié una de las colecciones a FetchType.LAZY ya que estaba bien para mi caso de uso.

¡Buena suerte! ~ J

JAD
fuente
0

Comentando ambos Fetchy a LazyCollectionveces ayuda a ejecutar el proyecto.

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)
prisar
fuente
0

Una cosa buena @LazyCollection(LazyCollectionOption.FALSE)es que varios campos con esta anotación pueden coexistir mientras FetchType.EAGERque no pueden, incluso en las situaciones en que dicha coexistencia es legítima.

Por ejemplo, un Orderpuede tener una lista de OrderGroup(una corta) así como una lista de Promotions(también corta). @LazyCollection(LazyCollectionOption.FALSE)se puede usar en ambos sin causar LazyInitializationExceptionninguno MultipleBagFetchException.

En mi caso @Fetchresolvió mi problema MultipleBacFetchExceptionpero luego causa LazyInitializationExceptionel infame no Sessionerror.

WesternGun
fuente
-5

Podría usar una nueva anotación para resolver esto:

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

De hecho, el valor predeterminado de fetch también es FetchType.LAZY.

leee41
fuente
55
JPA3.0 no existe.
holmis83