Hoy vamos a analizar el Mapeo Many to Many de Hibernate utilizando configuraciones XML y de anotaciones. Anteriormente vimos cómo implementar el One To One y el One To Many mapping en Hibernate.
El Mapeo Many to Many de Hibernate
Many-to-Many se implementa generalmente en la base de datos utilizando una Tabla de Unión. Por ejemplo, podemos tener las tablas Cart
y Item
, y la tabla Cart_Items
para el mapeo many-to-many. Cada carrito puede tener varios artículos y cada artículo puede ser parte de varios carritos, por lo que tenemos un mapeo de muchos a muchos aquí.
Configuración de la Base de Datos para el Mapeo Many to Many de Hibernate
El script siguiente se puede utilizar para crear nuestras tablas de base de datos de ejemplo many-to-many, estos scripts son para la base de datos MySQL. Si estás utilizando otra base de datos, es posible que necesites hacer pequeños cambios para que funcione.
DROP TABLE IF EXISTS `Cart_Items`;
DROP TABLE IF EXISTS `Cart`;
DROP TABLE IF EXISTS `Item`;
CREATE TABLE `Cart` (
`cart_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`cart_total` decimal(10,0) NOT NULL,
PRIMARY KEY (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `Item` (
`item_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`item_desc` varchar(20) NOT NULL,
`item_price` decimal(10,0) NOT NULL,
PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `Cart_Items` (
`cart_id` int(11) unsigned NOT NULL,
`item_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`cart_id`,`item_id`),
CONSTRAINT `fk_cart` FOREIGN KEY (`cart_id`) REFERENCES `Cart` (`cart_id`),
CONSTRAINT `fk_item` FOREIGN KEY (`item_id`) REFERENCES `Item` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Nota que la tabla Cart_Items no tiene columnas adicionales, de hecho, no tiene mucho sentido tener columnas adicionales en una tabla de mapeo muchos a muchos. Pero si tienes columnas adicionales, la implementación cambia un poco y lo veremos en otra publicación. El diagrama a continuación muestra la relación de entidad entre estas tablas. Nuestra configuración de base de datos está lista ahora, pasemos a crear el proyecto de mapeo muchos a muchos con Hibernate.
Estructura del Proyecto de Mapeo Muchos a Muchos con Hibernate
Crea un proyecto Maven en Eclipse o tu IDE favorito, la imagen a continuación muestra la estructura y los diferentes componentes en la aplicación. Primero veremos las implementaciones de mapeo basadas en XML y luego pasaremos a utilizar anotaciones JPA.
Dependencias Maven de Hibernate
Nuestro archivo pom.xml final contiene dependencias de Hibernate con la última versión 4.3.5.Final y dependencias del controlador de mysql. pom.xml
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.hibernate</groupId>
<artifactId>HibernateManyToManyMapping</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.5.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
</dependency>
</dependencies>
</project>
Clases de Modelo de Configuración XML de Muchos a Muchos de Hibernate
Cart.java
package com.journaldev.hibernate.model;
import java.util.Set;
public class Cart {
private long id;
private double total;
private Set<Item> items;
public double getTotal() {
return total;
}
public void setTotal(double total) {
this.total = total;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Set<Item> getItems() {
return items;
}
public void setItems(Set<Item> items) {
this.items = items;
}
}
Item.java
package com.journaldev.hibernate.model;
import java.util.Set;
public class Item {
private long id;
private double price;
private String description;
private Set<Cart> carts;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Set<Cart> getCarts() {
return carts;
}
public void setCarts(Set<Cart> carts) {
this.carts = carts;
}
}
Observa que Cart tiene un conjunto de Item y Item tiene un conjunto de Cart, de esta manera estamos implementando asociaciones bidireccionales. Significa que podemos configurarlo para guardar Item cuando guardamos Cart y viceversa. Para el mapeo unidireccional, usualmente tenemos un conjunto en una de las clases de modelo. Utilizaremos anotaciones para el mapeo unidireccional.
Configuración XML de Mapeo de Muchos a Muchos de Hibernate
Creemos archivos de configuración XML de mapeo de muchos a muchos de Hibernate para Cart e Item. Implementaremos un mapeo muchos a muchos bidireccional. cart.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"https://hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.journaldev.hibernate.model">
<class name="Cart" table="CART">
<id name="id" type="long">
<column name="cart_id" />
<generator class="identity" />
</id>
<property name="total" type="double" column="cart_total" />
<set name="items" table="CART_ITEMS" fetch="select" cascade="all">
<key column="cart_id" />
<many-to-many class="Item" column="item_id" />
</set>
</class>
</hibernate-mapping>
Observa que el conjunto de ítems está mapeado a la tabla CART_ITEMS. Dado que Cart es el objeto primario, cart_id es la clave y el mapeo muchos a muchos
está utilizando la columna item_id de la clase Item. item.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"https://hibernate.org/dtd/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="com.journaldev.hibernate.model">
<class name="Item" table="ITEM">
<id name="id" type="long">
<column name="item_id" />
<generator class="identity" />
</id>
<property name="description" type="string" column="item_desc" />
<property name="price" type="double" column="item_price" />
<set name="carts" table="CART_ITEMS" fetch="select" cascade="all">
<key column="item_id" />
<many-to-many class="Cart" column="cart_id" />
</set>
</class>
</hibernate-mapping>
Como puedes ver arriba, el mapeo es muy similar a las configuraciones de mapeo de Cart.
Configuración de Hibernate para el mapeo de muchos a muchos basado en XML
Nuestro archivo de configuración de Hibernate se ve así. hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"https://hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.password">pankaj123</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
<property name="hibernate.connection.username">pankaj</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<mapping resource="cart.hbm.xml" />
<mapping resource="item.hbm.xml" />
</session-factory>
</hibernate-configuration>
Clase de utilidad de Hibernate SessionFactory para el mapeo basado en XML
HibernateUtil.java
package com.journaldev.hibernate.util;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
public class HibernateUtil {
private static SessionFactory sessionFactory;
private static SessionFactory buildSessionFactory() {
try {
// Crea la SessionFactory desde hibernate.cfg.xml
Configuration configuration = new Configuration();
configuration.configure("hibernate.cfg.xml");
System.out.println("Hibernate Configuration loaded");
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.applySettings(configuration.getProperties()).build();
System.out.println("Hibernate serviceRegistry created");
SessionFactory sessionFactory = configuration
.buildSessionFactory(serviceRegistry);
return sessionFactory;
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
ex.printStackTrace();
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
if (sessionFactory == null)
sessionFactory = buildSessionFactory();
return sessionFactory;
}
}
Es una clase de utilidad simple que funciona como una fábrica para SessionFactory
.
Programa de prueba de configuración XML de mapeo de muchos a muchos de Hibernate
Nuestra configuración de mapeo de hibernate de muchos a muchos está lista, probémosla. Escribiremos dos programas, uno para guardar un carrito y ver que la información del artículo y de los elementos del carrito también se guarda. Otro para guardar datos del artículo y comprobar que se guardan el carrito correspondiente y los elementos del carrito. HibernateManyToManyMain.java
package com.journaldev.hibernate.main;
import java.util.HashSet;
import java.util.Set;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import com.journaldev.hibernate.model.Cart;
import com.journaldev.hibernate.model.Item;
import com.journaldev.hibernate.util.HibernateUtil;
public class HibernateManyToManyMain {
// Guardando muchos a muchos donde Cart es primario
public static void main(String[] args) {
Item iphone = new Item();
iphone.setPrice(100); iphone.setDescription("iPhone");
Item ipod = new Item();
ipod.setPrice(50); ipod.setDescription("iPod");
Set- items = new HashSet
- ();
items.add(iphone); items.add(ipod);
Cart cart = new Cart();
cart.setItems(items);
cart.setTotal(150);
Cart cart1 = new Cart();
Set
- items1 = new HashSet
- ();
items1.add(iphone);
cart1.setItems(items1);
cart1.setTotal(100);
SessionFactory sessionFactory = null;
try{
sessionFactory = HibernateUtil.getSessionFactory();
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();
session.save(cart);
session.save(cart1);
System.out.println("Before committing transaction");
tx.commit();
sessionFactory.close();
System.out.println("Cart ID="+cart.getId());
System.out.println("Cart1 ID="+cart1.getId());
System.out.println("Item1 ID="+iphone.getId());
System.out.println("Item2 ID="+ipod.getId());
}catch(Exception e){
e.printStackTrace();
}finally{
if(sessionFactory != null && !sessionFactory.isClosed()) sessionFactory.close();
}
}
}
Cuando ejecutamos el programa de ejemplo de mapeo de hibernate de muchos a muchos anterior, obtenemos la siguiente salida.
Hibernate Configuration loaded
Hibernate serviceRegistry created
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into CART (cart_total) values (?)
Before committing transaction
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Cart ID=1
Cart1 ID=2
Item1 ID=1
Item2 ID=2
Ten en cuenta que una vez que los datos del artículo se guardan a través del primer carrito, se genera el id del artículo y mientras se guarda el segundo carrito, no se guarda de nuevo. Otro punto importante a tener en cuenta es que los datos de la tabla de unión de muchos a muchos se guardan cuando confirmamos la transacción. Se hace para un mejor rendimiento en caso de que optemos por revertir la transacción. HibernateBiDirectionalManyToManyMain.java
package com.journaldev.hibernate.main;
import java.util.HashSet;
import java.util.Set;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import com.journaldev.hibernate.model.Cart;
import com.journaldev.hibernate.model.Item;
import com.journaldev.hibernate.util.HibernateUtil;
public class HibernateBiDirectionalManyToManyMain {
//Guardando muchos a muchos donde Item es primario
public static void main(String[] args) {
Item iphone = new Item();
iphone.setPrice(100); iphone.setDescription("iPhone");
Item ipod = new Item();
ipod.setPrice(50); ipod.setDescription("iPod");
Cart cart = new Cart();
cart.setTotal(150);
Cart cart1 = new Cart();
cart1.setTotal(100);
Set cartSet = new HashSet();
cartSet.add(cart);cartSet.add(cart1);
Set cartSet1 = new HashSet();
cartSet1.add(cart);
iphone.setCarts(cartSet1);
ipod.setCarts(cartSet);
SessionFactory sessionFactory = null;
try{
sessionFactory = HibernateUtil.getSessionFactory();
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();
session.save(iphone);
session.save(ipod);
tx.commit();
sessionFactory.close();
System.out.println("Cart ID="+cart.getId());
System.out.println("Cart1 ID="+cart1.getId());
System.out.println("Item1 ID="+iphone.getId());
System.out.println("Item2 ID="+ipod.getId());
}catch(Exception e){
e.printStackTrace();
}finally{
if(sessionFactory != null && !sessionFactory.isClosed()) sessionFactory.close();
}
}
}
La salida del programa anterior es:
Hibernate Configuration loaded
Hibernate serviceRegistry created
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into CART_ITEMS (item_id, cart_id) values (?, ?)
Hibernate: insert into CART_ITEMS (item_id, cart_id) values (?, ?)
Hibernate: insert into CART_ITEMS (item_id, cart_id) values (?, ?)
Cart ID=3
Cart1 ID=4
Item1 ID=3
Item2 ID=4
Puedes relacionarlo fácilmente con el programa de prueba anterior, ya que hemos configurado un mapeo bidireccional, podemos guardar el artículo o el carrito y los datos mapeados se guardarán automáticamente.
Mapeo de muchos a muchos de Hibernate con anotaciones
Ahora que hemos visto cómo configurar el mapeo many-to-many utilizando configuraciones XML de hibernate, veamos un ejemplo de cómo implementarlo a través de anotaciones. Implementaremos un mapeo many-to-many unidireccional utilizando anotaciones JPA.
Archivo de configuración XML de Hibernate
Nuestro archivo de configuración de hibernate basado en anotaciones se ve así. hibernate-annotation.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"https://hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.password">pankaj123</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
<property name="hibernate.connection.username">pankaj</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<mapping class="com.journaldev.hibernate.model.Cart1" />
<mapping class="com.journaldev.hibernate.model.Item1" />
</session-factory>
</hibernate-configuration>
Clase de utilidad de SessionFactory de Hibernate
Nuestra clase de utilidad para crear SessionFactory se ve así. HibernateAnnotationUtil.java
package com.journaldev.hibernate.util;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
public class HibernateAnnotationUtil {
private static SessionFactory sessionFactory;
private static SessionFactory buildSessionFactory() {
try {
// Crear la SessionFactory desde hibernate-annotation.cfg.xml
Configuration configuration = new Configuration();
configuration.configure("hibernate-annotation.cfg.xml");
System.out.println("Hibernate Annotation Configuration loaded");
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
System.out.println("Hibernate Annotation serviceRegistry created");
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
return sessionFactory;
}
catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
ex.printStackTrace();
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
if(sessionFactory == null) sessionFactory = buildSessionFactory();
return sessionFactory;
}
}
Clases de modelo de anotación de mapeo muchos a muchos de Hibernate
Esta es la parte más importante para el mapeo basado en anotaciones, primero veamos la clase de modelo de tabla de artículo y luego veremos la clase de modelo de tabla de carrito. Item1.java
package com.journaldev.hibernate.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="ITEM")
public class Item1 {
@Id
@Column(name="item_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
@Column(name="item_price")
private double price;
@Column(name="item_desc")
private String description;
// Métodos Getter Setter
}
La clase Item1 parece simple, no hay mapeo relacional aquí. Cart1.java
package com.journaldev.hibernate.model;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name = "CART")
public class Cart1 {
@Id
@Column(name = "cart_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "cart_total")
private double total;
@ManyToMany(targetEntity = Item1.class, cascade = { CascadeType.ALL })
@JoinTable(name = "CART_ITEMS",
joinColumns = { @JoinColumn(name = "cart_id") },
inverseJoinColumns = { @JoinColumn(name = "item_id") })
private Set items;
// Métodos Getter Setter
}
La parte más importante aquí es el uso de la anotación ManyToMany
y la anotación JoinTable
donde proporcionamos el nombre de la tabla y las columnas que se utilizarán para el mapeo de muchos a muchos.
Programa de prueba de mapeo de anotaciones de muchos a muchos de Hibernate
Aquí hay un programa de prueba simple para nuestra configuración de mapeo de muchos a muchos de Hibernate basada en anotaciones. HibernateManyToManyAnnotationMain.java
package com.journaldev.hibernate.main;
import java.util.HashSet;
import java.util.Set;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import com.journaldev.hibernate.model.Cart1;
import com.journaldev.hibernate.model.Item1;
import com.journaldev.hibernate.util.HibernateAnnotationUtil;
public class HibernateManyToManyAnnotationMain {
public static void main(String[] args) {
Item1 item1 = new Item1();
item1.setDescription("samsung"); item1.setPrice(300);
Item1 item2 = new Item1();
item2.setDescription("nokia"); item2.setPrice(200);
Cart1 cart = new Cart1();
cart.setTotal(500);
Set<Item1> items = new HashSet<Item1>();
items.add(item1); items.add(item2);
cart.setItems(items);
SessionFactory sessionFactory = null;
try{
sessionFactory = HibernateAnnotationUtil.getSessionFactory();
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();
session.save(cart);
System.out.println("Before committing transaction");
tx.commit();
sessionFactory.close();
System.out.println("Cart ID="+cart.getId());
System.out.println("Item1 ID="+item1.getId());
System.out.println("Item2 ID="+item2.getId());
}catch(Exception e){
e.printStackTrace();
}finally{
if(sessionFactory != null && !sessionFactory.isClosed()) sessionFactory.close();
}
}
}
Cuando ejecutamos el programa anterior, produce la siguiente salida.
Hibernate Annotation Configuration loaded
Hibernate Annotation serviceRegistry created
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Before committing transaction
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Cart ID=5
Item1 ID=6
Item2 ID=5
Está claro que guardar el carrito también guarda datos en las tablas Item y Cart_Items. Si solo guarda información del ítem, notará que los datos de Cart y Cart_Items no se guardan. Eso es todo para el ejemplo de mapeo de muchos a muchos de Hibernate, puede descargar el proyecto de muestra desde el siguiente enlace y jugar con él para aprender más.
Source:
https://www.digitalocean.com/community/tutorials/hibernate-many-to-many-mapping-join-tables