¡Esta es una revisión vieja del documento!
La relación muchos a muchos consiste en que un objeto A tenga una lista de otros objetos B y también que el objeto B a su vez tenga la lista de objetos A.De forma que al persistirse cualquier objeto también se persista la lista de objetos que posee.
Antes de entrar en cómo se implementa en Hibernate , veamos las clases Java y las tablas que definen la relación uno a muchos.
Para nuestro ejemplo vamos a usar las clases:
Profesor
Modulo
Estas dos clases van a tener una relación muchos a muchos.
1: public class Profesor implements Serializable { 2: private int id; 3: private String nombre; 4: private String ape1; 5: private String ape2; 6: private Set<Modulo> modulos=new HashSet(); 7: 8: 9: public Profesor(){ 10: } 11: 12: public Profesor(int id, String nombre, String ape1, String ape2) { 13: this.id = id; 14: this.nombre = nombre; 15: this.ape1 = ape1; 16: this.ape2 = ape2; 17: } 18: 19: } 20: 21: public class Modulo implements Serializable { 22: private int idModulo; 23: private String nombre; 24: private Set<Profesor> profesores=new HashSet(); 25: 26: public Modulo() { 27: 28: } 29: 30: public Modulo(int idModulo, String nombre) { 31: this.idModulo = idModulo; 32: this.nombre = nombre; 33: 34: } 35: }
En el listado 1 podemos ver cómo la clase Profesor
tiene una propiedad de tipo Set
llamada modulos
de la clase Modulo
(línea 6) y además la clase Modulo
también posee un Set
de objetos Profesor
(línea 24).
El mecanismo que usamos en Java para almacenar la lista de objetos es el interfaz Set
. No vamos a usar el interfaz List
o un array ya que dichas formas implican un orden de los objetos mientras que usando un Set
no hay ningún tipo de orden.
Profesor
y Modulo
no se han incluido los métodos get/set de cada propiedad para facilitar la lectura pero deben estar en la clase Java.
En el siguiente diagrama UML se ve la relación entre Profesor
y Modulo
.
La tablas de base de datos quedarían de la siguiente forma:
Podemos ver cómo en este caso las tablas Profesor
y Modulo
se relacionan mediante la nueva tabla ProfesorModulo
que contiene las claves primarias de ambas tablas.
Al persistir dos clases serán necesarios dos ficheros de persistencia:
Profesor.hbm.xml
Modulo.hbm.xml
El fichero Profesor.hbm.xml
quedará de la siguiente forma
1: <?xml version="1.0" encoding="UTF-8"?> 2: <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 3: <hibernate-mapping> 4: <class name="ejemplo09.Profesor" > 5: <id column="Id" name="id" type="integer"/> 6: <property name="nombre" /> 7: <property name="ape1" /> 8: <property name="ape2" /> 9: 10: <set name="modulos" table="ProfesorModulo" cascade="all" inverse="true" > 11: <key> 12: <column name="idProfesor" /> 13: </key> 14: <many-to-many column="IdModulo" class="ejemplo09.Modulo" /> 15: </set> 16: </class> 17: </hibernate-mapping>
El fichero básicamente contiene lo que se ha explicado en las lecciones anteriores excepto por el tag <set>
(líneas 10 a 15).
El tag <set>
se utiliza para definir una lista desordenada entre las dos clases Java.
ProfesorModulo
.all
. Más información en Cascadetrue
y el otro con el valor false
. En Profesor
pondremos el valor a true
y en la parte de Modulo
la estableceremos a false
aunque no hay problema en establecerla al revés.
inverse=“true”
y en el otro lado de la relación inverse=“false”
.
Si ambos valores son true
no se realizará ninguna inserción en la tabla ProfesorModulo
y si ambos valores son false
se realizará dos veces la inserción en la tabla ProfesorModulo
dando un error de clave primaria duplicada.
Es decir que en este caso inverse
controla cuándo se realiza la inserción en la tabla ProfesorModulo
: si se realiza al guardar el Profesor
o al guardar el Modulo
.
column
con el atributo name
, el cual contiene el nombre de una columna de la base de datos. Esta columna debe ser una de la tabla de la relación muchos a muchos
y ser el nombre de la columna que contiene la clave ajena de tabla que estamos persistiendo. En nuestro ejemplo es idProfesor
ya que es el nombre de la clave ajena que se encuentra en la tabla ProfesorModulo
.class
con el FQCN de la clase Java con la que se establece la relación. En nuestro ejemplo es el nombre de la clase Modulo
cuyo FQCN es ejemplo09.Modulo
.
class
en el tag many-to-many
sea opcional ya que hibernate podría ser capaz de deducirlo como ha hecho en otras ocasiones, sin embargo no es así. En caso de no indicarlo se producirá la siguiente excepción:
Exception java.lang.NullPointerException
El fichero Modulo.hbm.xml
quedará de la siguiente forma:
1: <?xml version="1.0" encoding="UTF-8"?> 2: <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 3: <hibernate-mapping> 4: <class name="ejemplo09.Modulo" > 5: <id column="IdModulo" name="idModulo" type="integer"/> 6: <property name="nombre" /> 7: 8: <set name="profesores" table="ProfesorModulo" cascade="all" inverse="false" > 9: <key> 10: <column name="idModulo" /> 11: </key> 12: <many-to-many column="IdProfesor" class="ejemplo09.Profesor" /> 13: </set> 14: </class> 15: </hibernate-mapping>
El fichero básicamente contiene lo que se ha explicado en las lecciones anteriores excepto por el tag <many-to-one>
(líneas 8 a 13).
El tag <set>
se utiliza para definir una lista desordenada entre las dos clases Java.
ProfesorModulo
.all
. Más información en Cascade.true
y el otro con el valor false
. En Modulo
pondremos el valor a false
y en la parte de Profesor
la estableceremos a true
aunque no hay problema en establecerla al revés.
inverse=“false”
y en el otro lado de la relación inverse=“true”
.
Si ambos valores son true
no se realizará ninguna inserción en la tabla ProfesorModulo
y si ambos valores son false
se realizará dos veces la inserción en la tabla ProfesorModulo
dando un error de clave primaria duplicada.
Es decir, que en este caso inverse
controla cuándo se realiza la inserción en la tabla ProfesorModulo
si bien al guardarla en Profesor
o en Modulo
.
column
con el atributo name
el cual contiene el nombre de una columna de la base de datos. Esta columna debe ser una de la tabla de la relación muchos a muchos
y ser el nombre de la columna que contiene la clave ajena de tabla que estamos persistiendo. En nuestro ejemplo es idModulo
ya que es el nombre de la clave ajena que se encuentra en la tabla ProfesorModulo
.class
con el FQCN de la clase Java con la que se establece la relación. En nuestro ejemplo es el nombre de la clase Modulo
cuyo FQCN es ejemplo09.Profesor
.
class
en el tag many-to-many
sea opcional ya que hibernate podría ser capaz de deducirlo como ha hecho en otras ocasiones, sin embargo no es así. En caso de no indicarlo se producirá la siguiente excepción:
Exception org.hibernate.MappingException: An association from the table ProfesorModulo does not specify the referenced entity
Para usar anotaciones deberemos modificar el código fuente de las clases Java y no usar los ficheros .hbm.xml
.
El código fuente de la clase Profesor
queda de la siguiente forma:
1: @Entity 2: @Table(name="Profesor") 3: public class Profesor implements Serializable { 4: 5: @Id 6: @Column(name="Id") 7: private int id; 8: 9: @Column(name="nombre") 10: private String nombre; 11: 12: @Column(name="ape1") 13: private String ape1; 14: 15: @Column(name="ape2") 16: private String ape2; 17: 18: @ManyToMany(cascade = {CascadeType.ALL}) 19: @JoinTable(name="ProfesorModulo", joinColumns={@JoinColumn(name="IdProfesor")}, inverseJoinColumns={@JoinColumn(name="IdModulo")}) 20: private Set<Modulo> modulos=new HashSet(); 21: 22: 23: public Profesor(){ 24: } 25: 26: public Profesor(int id, String nombre, String ape1, String ape2) { 27: this.id = id; 28: this.nombre = nombre; 29: this.ape1 = ape1; 30: this.ape2 = ape2; 31: } 32: }
A la propiedad módulos
(línea 20) se han añadido dos anotaciones para indicar la relación muchos a muchos.
ProfesorModulo
.@JoinColumn
y en el atributo name
contiene el nombre de la columna.@JoinColumn
y en el atributo name
contiene el nombre de la columna.El código de la clase Modulo es el siguiente:
1: @Entity 2: @Table(name="Modulo") 3: public class Modulo implements Serializable { 4: 5: @Id 6: @Column(name="IdModulo") 7: private int idModulo; 8: 9: @Column(name="nombre") 10: private String nombre; 11: 12: @ManyToMany(cascade = {CascadeType.ALL},mappedBy="modulos") 13: private Set<Profesor> profesores=new HashSet(); 14: 15: public Modulo() { 16: 17: } 18: 19: public Modulo(int idModulo, String nombre) { 20: this.idModulo = idModulo; 21: this.nombre = nombre; 22: 23: } 24: }
A la propiedad profesores
(línea 13) se han añadido dos anotaciones para indicar la relación muchos a muchos.
modulos
.
mappedBy
ya no es necesario incluir la anotación @JoinTable
ya que dicha información ya se indica en el otro lado de la relación.
Ahora que ya tenemos preparadas las clase Java para que puedan persistirse veamos el código necesario para persistirlas.
1: Profesor profesor1=new Profesor(11, "Isabel", "Fuertes", "Gascón"); 2: Profesor profesor2=new Profesor(12, "Jose", "Valenciano", "Gimeno"); 3: 4: Modulo modulo1=new Modulo(1, "Sistemas Operativos en Red"); 5: Modulo modulo2=new Modulo(2, "Entornos de desarrollo"); 6: Modulo modulo3=new Modulo(3, "Sistemas Informáticos"); 7: 8: profesor1.getModulos().add(modulo1); 9: profesor1.getModulos().add(modulo2); 10: profesor2.getModulos().add(modulo3); 11: 12: modulo1.getProfesores().add(profesor1); 13: modulo2.getProfesores().add(profesor1); 14: modulo3.getProfesores().add(profesor2); 15: 16: 17: Session session=sessionFactory.openSession(); 18: session.beginTransaction(); 19: 20: session.save(profesor1); 21: session.save(profesor2); 22: 23: session.getTransaction().commit(); 24: session.close();
La explicación del código es la siguiente:
Profesor
Modulo
.Profesor
y automáticamente se guardan también los módulos.Apreciar cómo el código Java sigue siendo sencillo y no se complica prácticamente nada al guardarlo en la base de datos.Solo estamos añadiendo la complejidad en el ficheros de mapeo de Hibernate o en las anotaciones.
HashSet
ya que en el propio código de cada clase ya se creaba y de esa forma nos ahorramos unas líneas código aqui.