Saltar al contenido

Cómo usar múltiples JOIN FETCH en una consulta JPQL

Solución:

Considerando que tenemos las siguientes entidades:

Modelo de dominio de entidad JPA

Y quieres ir a buscar a un padre Post entidades junto con todos los asociados comments y tags colecciones.

Si está usando más de una JOIN FETCH directivas:

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á el MultipleBagFetchException:

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
]

La razón por la que Hibernate lanza esta excepción es que no permite buscar más de una bolsa porque eso generaría un producto cartesiano.

La peor “solución” que otros podrían intentar venderle

Ahora, encontrará muchas respuestas, publicaciones de blog, videos u otros recursos que le indican que use un Set en lugar de un List para tus colecciones.

Ese es un consejo terrible. ¡No hagas eso!

Utilizando Sets en lugar de Lists hará el MultipleBagFetchException desaparecerá, 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 haber aplicado esta “solución”.

La solucion 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, distinct NO va a la sentencia SQL. Es por eso que configuramos el PASS_DISTINCT_THROUGH Sugerencia de consulta JPA para false.

DISTINCT tiene dos significados en JPQL, y aquí, lo necesitamos para deduplicar las referencias de objetos Java devueltas por getResultList en el lado de Java, no en el lado de SQL.

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

Al utilizar varias consultas, evitará el Producto cartesiano desde cualquier otra colección, pero la primera se obtiene mediante una consulta secundaria.

Evite siempre el FetchType.EAGER estrategia

Si está usando el FetchType.EAGER estrategia en el momento del mapeo para @OneToMany o @ManyToMany asociaciones, entonces fácilmente podría terminar con una MultipleBagFetchException.

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

Conclusión

Evitar FetchType.EAGER y no cambies de List para Set solo porque hacerlo hará que Hibernate oculte el MultipleBagFetchException debajo 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 tiene colecciones para inicializar, está bien. Simplemente no inicialice las colecciones en un bucle, ya que eso activará N+1 problemas de consulta, que también son perjudiciales para el rendimiento.

A continuación, se muestra un ejemplo práctico de combinación compleja y construcción múltiple:

    String query_findByProductDepartmentHospital = "select location from ProductInstallLocation location "
            + " join location.product prod " + " join location.department dep "
            + " join location.department.hospital hos " + " where  prod.name = :product "
            + " and dep.name.name = :department " + " and hos.name = :hospital ";

    @Query(query_findByProductDepartmentHospital)
    ProductInstallLocation findByProductDepartmentHospital(@Param("product") String productName,@Param("department") String departName, @Param("hospital") String hospitalName);
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *