Antecedentes
El relevante rol de JPA (Java Persistence API) en cualquier aplicacion java es innegable, desde aplicaciones de escritorio hasta el ambito empresarial JEE; es por ésto que el presente post pretende abordar la relación ManyToMany (una de las anotaciones de mapeo que JPA tiene para relacionar el mundo orientado a objetos con el mundo de tablas relacionales de base de datos).
En forma general JPA es un API destinado a la persistencia de datos es decir, sirve de puente para poder interactuar entre las tablas de una base de datos con un mundo orientado a objetos y mediante un proveedor de persistencia (Hibernate, EclipseLink, TopLink, etc) poder conseguir manipular la informacion que formará parte de las tablas.
Una de las piezas clave para trabajar con JPA son las entidades, que basicamente son la representación en código java de una tabla de la base de datos (claro usando anotaciones especiales para ello). JPA usa un EntityManager que en definitiva es quien se encarga de adminstrar las Entidades y a su cargo tambien está el hacer posible acciones como: crear, borrar, actualizar, etc.; pero lo repito, todo ésto es posible gracias a un proveedor de persistencia pues JPA como tal no puede trabajar si no cuenta con un proveedor de persistencia, de ahí que un error común es pensar que JPA es igual o podría trabajar autónomamente tal cual una herramientas ORM como Hibernate, y hay que dejar en claro que JPA es la especificación para peristencia en Java, mientras que Hibernate, Toplink, EclipseLink son proveedores de persistencia, es decir implementan lo que la especificación de persistencia define.
Hablando de JPA surgen algunas dudas comunes:
¿Por que Java no usa un proveedor de peristencia propio de JPA?.
Porque eso precisamente refuerza la idea de libertad en uso de software libre, es decir, que el propio desarrollador decida cuál es la mejor implementación (o proveedor de persistencia) que se ajuste a sus necesidades.
¿Cual es el mejor proveedor de persistencia?
Hibernate por la experiencia y recorrido que tiene probablemente podría ser la mejor elección al momento de definir el proveedor de persistencia, pues de heho sirvió como blueprint para la definición de JPA, y no lo niego, Hibernate es una muy buena alternativa, sin embargo, por la experiencia propia de trabajar con EclipseLink debo decir que he tenido muy buenos resultados, por tanto queda a criterio de cada desarrollador el uso del proveedor de persistencia.
¿La peristencia en Java se consigue unicamente con JPA?
No, existen diversas tecnologías como la tradicional JDBC, harramientas ORM como Hibernate, el proyecto Apache Cayenne, Data Nucleous, etc.
Manos a la obra:
Como lo mencióne antes, el presente post se enfoca en la relación ManyToMany pues la misma supone un poco más de esfuerzo que las relaciones tradicionales @OneToOne, @OneToMany... sin embargo si el tema de persistencia es interesante para la comunidad (y si me lo hacen saber) podría escribir mas posts sobre cada una de ellas tanto unidireccional como bidireccionalmente.
El objetivo de éste post es lograr almacenar información de una relacion ManyToMany entre dos tablas desde una aplicacion web empresarial JEE.
En términos generales la relación se puede esquematizar como lo demuestra la siguiente imagen, donde es notorio el relacionamiento entre dos tablas (A y B) y una tabla intermedia de relacionamiento (A_B):
Nuestro caso de uso de ejemplo sera una anulación de documento en el que se pueda seleccionar multiples errores para dicha anulación, es decir, muchas anulaciones de documento pueden contener varios errores (escenario típico de una relacion Many To Many).
Nuestras tablas son las siguientes: ANULACION, ERROR y la tabla de rompimiento ANULACION_ERROR. Considérese que tanto ANULACION como ERROR deben tener bien definida su clave priaria (que posterioremente se transladara a la tabla de rompimiento).
La tabla ANULACION_ERROR (como se muestra en la siguiente imagen), se compone de dos claves foráneas, transladadas desde las tablas ANULACION y ERROR.
NOTA: Si decidimos que JPA realicé el mapeo de una relacion ManyToMany desde código java para que generen las tablas de la base de datos de manera automática, JPA seguirá estándares de generación (como muestra la siguiente imagen): El nombre de la tabla de rompimiento se formará por la unión de las dos tablas de los extremos por ejemplo, entre una tabla A y una tabla B, la tablade rompimiento se llamará A_B; y JPA generará automáticamente una key por las dos claves foráneas heredadas (mírese la imagen SYS_C0069738) pero como se mostrará en el desarrollo del post no es necesario ésto ultimo.
Ahora desde el código Java, necesitamos dos clases que representen a las tablas; éstas clases se llaman entidades y deben cumplir dos requisitos básicos: 1) llevar las anotación @Entity u @Id y 2) Implementar la Interfaz Serializable (por ser objetos que serán enviados a travez de una red).
La entidad para la tabla ANULACION se muestra a continuación:
La entidad para la tabla ERROR se muestra a continuación:
Considérese:
1) El uso de la anotacion @Table para indicar la tabla correspondiente a cada entidad.
2) La anotacion @ManyToMany indica que hay una relacion ManyToMany entre la Entidades: Anulacion y Error1.
3) el argumento: mappedBy indica cuál es el lado dueño de la relacion, para éste caso mappedBy = "err" indica que el propietario de la relacion es Anulacion, es decir primero debo asignar el listado de errores a Anulacion y posteriormente persisto Anulación con sus errores correspondientes, en la tabla ANULACION_ERROR se reflejará los datos de Anulacion y Error.
4) No hace falta una Entidad para la tabla intermedia ANULACION_ERROR.
5) Si la tabla intermedia estuviera constuida fuera de los standares de generación automática de tablas que usa por defecto JPA, deberá usarse la anotación @JoinTable donde indicaremos el nombre de la tabla así como las columnas foráneas. Por ejemplo:
Ahora, desde la capa de vista invocamos la relación de la siguiente manera:
En donde, en un backing bean llamado "anulacionController" tendremos una referencia a:
- erroresAnulacion que es un List de Error1, que mediante algun método de la lógica de negocio (en nuestro caso usamos la tecnología EJB) lo poblamos con los objetos que están guardados previamente en la base de datos :
- selectedErroresAnulacion que es un List de Errores que almacenará los Errores seleccionados por el usuario y que se persistirán en la tabla ANULACION_ERROR
Nótese también que se hace uso de un convertidor, el cual se localiza dentro del controlador en la vista para de Error1 con el siguiente código:
Finalmente desde la vista se podrá visualizar los diversos campos de error, los cuales se seleccionarán para realizar el guardado en la base de datos:
Recomendación: Como se vio en el código al manejar las relaciones para minimizar la complejidad y mejorar el rendimiento, se recomienda realizarlo con objetos propios de la aplicación, por ej: Anulacion, Error, etc. en lugar de realizarlo con Objetos de tipo SelectItem o DataModel.