Las mejores Prácticas para Asociaciones de Muchos a Muchos con Hibernación y JPA

Las asociaciones de muchos a muchos son una de las asociaciones más utilizadas con JPA e Hibernación. Puede encontrar muchos ejemplos para ellos en el mundo real, y puede mapearlos con JPA e Hibernar como asociación unidireccional o bidireccional en su modelo de dominio.

Pero probablemente también sepa que estas asignaciones proporcionan varios escollos. En este artículo, le mostraré 5 prácticas recomendadas que le ayudarán a evitar estos escollos e implementar asignaciones eficientes. Tú aprenderás:

  1. El tipo de datos más eficiente para su asociación
  2. Por qué necesita métodos de utilidad para administrar su asociación
  3. El tipo de búsqueda correcto para una asignación eficiente
  4. Cuándo y cómo usar la obtención específica de consultas
  5. El tipo de cascada que debe evitar a toda costa

No profundizaré en los detalles de una asignación básica de muchos a muchos. Si no está exactamente seguro de cómo crear dicha asignación, eche un vistazo a la sección muchos a muchos de mi guía de asignación de asociaciones.

El más eficiente de datos, el tipo de asociación

vídeo de YouTube

la Mayoría de los desarrolladores no gastar un montón de ideas sobre el tipo de datos de un a-muchos asociación. Simplemente eligen un java.útil.Lista porque es simple y no realiza ninguna comprobación para evitar duplicados.

Está bien, si implementa una clase Java básica o si modela una asociación Uno a Muchos/Muchos a Uno. Pero nunca debe usar una Lista si modela una asociación de Muchos a Muchos.

@Entitypublic class Book {// DON'T DO THIS!!!@ManyToMany@JoinTable(name = "book_author", joinColumns = { @JoinColumn(name = "fk_book") }, inverseJoinColumns = { @JoinColumn(name = "fk_author") })private List<Author> authors = new ArrayList<Author>();...}

Hibernate maneja operaciones de eliminación en relaciones de Muchos a muchos que están asignadas a un java.útil.La lista es muy ineficiente.

em = emf.createEntityManager();em.getTransaction().begin();// Get Book entity with 2 Authorsb = em.find(Book.class, 1L);// Remove one of the Authorb.getAuthors().remove(a);em.getTransaction().commit();em.close();

primero elimina todos los registros de la tabla de asociación antes de insertar todos los restantes.

09:54:28,876 DEBUG - update Book set title=?, version=? where id=? and version=?09:54:28,878 DEBUG - delete from book_author where fk_book=?09:54:28,882 DEBUG - insert into book_author (fk_book, fk_author) values (?, ?)

En su lugar, debería modelar una asociación de muchos a muchos como java.útil.Establecer.

@Entitypublic class Book {@ManyToMany@JoinTable(name = "book_author", joinColumns = { @JoinColumn(name = "fk_book") }, inverseJoinColumns = { @JoinColumn(name = "fk_author") })private Set<Author> authors = new HashSet<Author>();...}

Hibernar luego maneja las operaciones de eliminación en la asociación mucho mejor. Ahora solo elimina los registros esperados de la asociación y mantiene los demás intactos.

10:00:37,709 DEBUG - update Book set title=?, version=? where id=? and version=?10:00:37,711 DEBUG - delete from book_author where fk_book=? and fk_author=?

Por qué necesita métodos de utilidad para administrar su asociación

Las asociaciones bidireccionales se asignan a un atributo de entidad en ambos extremos de las relaciones. Por lo tanto, en el ejemplo anterior, tiene un atributo authors en la entidad Book y un atributo books en la entidad Author. Esto hace que la implementación de un JPQL o CriteriaQuery sea muy cómoda, ya que puede usar estos atributos para definir una cláusula de unión.

Pero agregar o eliminar una asociación se vuelve más complicado. Siempre es necesario realizar el cambio en ambos extremos de la asociación. Por ejemplo, si desea agregar un libro al Autor, debe agregarlo al atributo libros de la entidad Autor, y también debe agregar el atributo Autor en la entidad libro. De lo contrario, su contexto de persistencia actual contiene datos inconsistentes que usará hasta el final de su transacción actual.

Book b = new Book();b.setTitle("Hibernate Tips - More than 70 solutions to common Hibernate problems");em.persist(b);Author a = em.find(Author.class, 1L);a.getBooks().add(b);b.getAuthors().add(a);

Los métodos de utilidad de las entidades Autor y Libro facilitan mucho la actualización y eliminación. Dentro de estos métodos, realiza las operaciones necesarias en ambas entidades.

@Entitypublic class Author {@ManyToMany(mappedBy = "authors")private Set<Book> books = new HashSet<Book>();...public void addBook(Book book) {this.books.add(book);book.getAuthors().add(this);}public void removeBook(Book book) {this.books.remove(book);book.getAuthors().remove(this);}}

El derecho FetchType para una eficiente asignación de

vídeo de YouTube

Este es uno rápido. Siempre debes usar FetchType.PEREZOSO para sus asociaciones de muchos a muchos. Le indica a su proveedor de persistencia que no obtenga las entidades asociadas de la base de datos hasta que las use. Ese suele ser el caso cuando llamas a su método getter por primera vez.

Afortunadamente, ese es el valor predeterminado para todas las asociaciones a muchas. Así que, por favor, asegúrate de no cambiarlo.

Y si quieres aprender más sobre los diferentes tipos de FETCH de JPA, echa un vistazo a mi Introducción a los tipos de FETCH de JPA.

Cuándo y cómo usar la obtención específica de consultas

Si está utilizando FetchType.PEREZOSO, necesita saber sobre la obtención de consultas específicas. De lo contrario, su aplicación será muy lenta porque creó muchos problemas de selección n+1.

Video de YouTube

Cuando carga una entidad y usa la búsqueda específica de una consulta, le indica a Hibernate qué asociaciones asignadas inicializará para cada entidad recuperada. A continuación, extiende la cláusula SELECT de su consulta para que incluya las columnas asignadas por estas otras entidades e inicialice las asociaciones. Y debido a que las asociaciones ya están inicializadas, Hibernate no necesita realizar una consulta adicional cuando accede a su método getter por primera vez.

Puede implementar la obtención de consultas específicas de varias maneras diferentes. La más simple es una cláusula de búsqueda de UNIÓN, que les mostraré aquí. Pero también puedes usar un @NamedEntityGraph o un EntityGraph, que expliqué en artículos anteriores.

La definición de una cláusula de búsqueda de UNIÓN es casi idéntica a una cláusula de unión simple en una consulta JPQL. Solo necesita agregar la palabra clave FETCH.

Author a = em.createQuery("SELECT a FROM Author a JOIN FETCH a.books WHERE a.id = 1", Author.class).getSingleResult();

Aún así, una cláusula JOIN y una cláusula de búsqueda de UNIÓN se ven muy similares, la cláusula de búsqueda de UNIÓN tiene un efecto mucho mayor en la consulta SQL generada. No solo se traduce a una COMBINACIÓN SQL, como es el caso de una cláusula de combinación JPQL, sino que también obliga a su proveedor de persistencia a extender la cláusula SELECT a todas las columnas asignadas por la entidad asociada.

16:21:03,046 DEBUG SQL:94 - select author0_.id as id1_0_0_, book2_.id as id1_1_1_, author0_.firstName as firstNam2_0_0_, author0_.lastName as lastName3_0_0_, author0_.version as version4_0_0_, book2_.format as format2_1_1_, book2_.publishingDate as publishi3_1_1_, book2_.title as title4_1_1_, book2_.version as version5_1_1_, books1_.author_id as author_i2_2_0__, books1_.book_id as book_id1_2_0__ from Author author0_ inner join book_author books1_ on author0_.id=books1_.author_id inner join Book book2_ on books1_.book_id=book2_.id where author0_.id=1

El tipo de cascada que debe evitar a toda costa

Si activa la conexión en cascada en una asociación, su proveedor de persistencia aplica las operaciones que realiza en la entidad a todas las entidades asociadas. Si lo hace para todas las operaciones o solo para algunas seleccionadas, depende del tipo de cascada configurado.

vídeo de YouTube

esto puede sonar como una idea increíble que hace que la aplicación de su lógica de negocio mucho más fácil. Y eso no está del todo mal.

Pero evite los tipos de cascada REMOVE y ALL, que incluye REMOVE, para asociaciones de muchos a muchos. En el mejor de los casos, solo crea problemas de rendimiento, pero en el peor de los casos, también puede eliminar más registros de los que pretendía.

Expliqué ambos escollos y su solución con grandes detalles en un artículo anterior. O si desea que sea sencillo, active la información requerida mediante programación en las entidades asociadas. Esto puede requerir algunas líneas de código más, pero evita cualquier efecto secundario inesperado.

Conclusión

Puede encontrar muchos ejemplos de asociaciones de muchos a muchos en el mundo real, y puede mapearlos fácilmente con JPA e Hibernar. Desafortunadamente, estas asignaciones simples ocultan algunas trampas que puede evitar siguiendo estas 5 prácticas recomendadas:

  1. Asociaciones de modelos como java.útil.Establecer.
  2. Proporcionar métodos de utilidad para agregar o quitar una entidad de una asociación.
  3. Siempre use FetchType.PEREZOSO, que es el valor predeterminado, para evitar problemas de rendimiento.
  4. Aplicar búsquedas específicas de consultas para evitar problemas de selección n+1.
  5. No utilice CascadeTypes REMOVE y TODO.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.