Muchos a muchos
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.
Clases Java
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:
Estas dos clases van a tener una relación muchos a muchos.
1 | Listado 1.Relación n a n
public class Profesor implements Serializable {
private int id;
private String nombre;
private String ape1;
private String ape2;
private Set< Modulo> modulos= new HashSet ( ) ;
public Profesor( ) {
}
public Profesor( int id, String nombre, String ape1, String ape2) {
this .id = id;
this .nombre = nombre;
this .ape1 = ape1;
this .ape2 = ape2;
}
}
public class Modulo implements Serializable {
private int idModulo;
private String nombre;
private Set< Profesor> profesores= new HashSet ( ) ;
public Modulo( ) {
}
public Modulo( int idModulo, String nombre) {
this .idModulo = idModulo;
this .nombre = nombre;
}
}
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.
En la clases Java 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
.
class Profesor
Profesor : int id
Profesor : String nombre
Profesor : String ape1
Profesor : String ape2
class Modulo
Modulo: int idModulo
Modulo: String nombre
Profesor "1" --> "0..n" Modulo: modulos
Profesor "0..n" <-- "1" Modulo: profesores
Tablas
La tablas de base de datos quedarían de la siguiente forma:
class Profesor <>
Profesor : INTEGER id
Profesor : VARCHAR nombre
Profesor : VARCHAR ape1
Profesor : VARCHAR ape2
class Modulo <>
Modulo: INTEGER idModulo
Modulo: VARCHAR nombre
class ProfesorModulo <>
ProfesorModulo: INTEGER idProfesor
ProfesorModulo: INTEGER idModulo
Profesor "1" -- "0..n" ProfesorModulo
ProfesorModulo "0..n" -- "1" Modulo
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.
Fichero de mapeo ''.hbm.xml''
Al persistir dos clases serán necesarios dos ficheros de persistencia:
Profesor.hbm.xml
Modulo.hbm.xml
Profesor.hbm.xml
El fichero Profesor.hbm.xml
quedará de la siguiente forma
1| Fichero Profesor.hbm.xml
<?xml version ="1.0" encoding ="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name ="ejemplo09.Profesor" >
<id column ="Id" name ="id" type ="integer" />
<property name ="nombre" />
<property name ="ape1" />
<property name ="ape2" />
<set name ="modulos" table ="ProfesorModulo" cascade ="all" inverse ="true" >
<key>
<column name ="idProfesor" />
</key>
<many-to-many column ="IdModulo" class ="ejemplo09.Modulo" />
</set>
</class>
</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).
Tag set
El tag <set>
se utiliza para definir una lista desordenada entre las dos clases Java.
Atributos
name : Es el nombre de la propiedad Java del tipo
Set en la cual se almacenan todos los objetos relacionados.En nuestro ejemplo el valor es
modulos
ya que es la propiedad que contiene el
Set .
table : Es el nombre de la tabla de la base de datos que contiene la relación muchos a muchos .En nuestro ejemplo es la tabla ProfesorModulo
.
cascade : Como ya hemos explicado en anteriores lecciones, este atributo indica que se realizan las mismas operaciones con el objeto principal que con los objetos relacionados, es decir si uno se borra los otros también, etc. Su valor habitual es
all
. Más información en
Cascade
inverse : Como ya hemos ido explicando durante el curso, la documentación de hibernate no es clara explicando su significado. En el caso de las relaciones muchos a muchos es necesario poner un lado de la relación con el valor true
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.
Recuerda poner en este lado de la relación el valor de
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
.
key Este tag contiene otro anidado llamado 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
.
many-to-many Este tag contiene el atributo 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
.
Podemos pensar que el valor del atributo
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
Modulo.hbm.xml
El fichero Modulo.hbm.xml
quedará de la siguiente forma:
1| Fichero Modulo.hbm.xml
<?xml version ="1.0" encoding ="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name ="ejemplo09.Modulo" >
<id column ="IdModulo" name ="idModulo" type ="integer" />
<property name ="nombre" />
<set name ="profesores" table ="ProfesorModulo" cascade ="all" inverse ="false" >
<key>
<column name ="idModulo" />
</key>
<many-to-many column ="IdProfesor" class ="ejemplo09.Profesor" />
</set>
</class>
</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).
Tag set
El tag <set>
se utiliza para definir una lista desordenada entre las dos clases Java.
Atributos
name : Es el nombre de la propiedad Java del tipo
Set en la cual se almacenan todos los objetos relacionados.En nuestro ejemplo el valor es
profesores
ya que es la propiedad que contiene el
Set .
table : Es el nombre de la tabla de la base de datos que contiene la relación muchos a muchos .En nuestro ejemplo es la tabla ProfesorModulo
.
cascade : Como ya hemos explicado en anteriores lecciones, este atributo indica que se realizan las mismas operaciones con el objeto principal que con los objetos relacionados, es decir, si uno se borra los otros también, etc. Su valor habitual es
all
. Más información en
Cascade .
inverse : Como ya hemos ido explicando durante el curso, la documentación de hibernate no es clara explicando su significado. En el caso de las relaciones muchos a muchos es necesario poner un lado de la relación con el valor 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.
Recuerda poner en este lado de la relación el valor de
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
.
key Este tag contiene otro anidado llamado 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
.
many-to-many Este tag contiene el atributo 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
.
Podemos pensar que el valor del atributo
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
Anotaciones
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 | Clase Profesor anotada
@Entity
@Table( name= "Profesor" )
public class Profesor implements Serializable {
@Id
@Column( name= "Id" )
private int id;
@Column( name= "nombre" )
private String nombre;
@Column( name= "ape1" )
private String ape1;
@Column( name= "ape2" )
private String ape2;
@ManyToMany( cascade = { CascadeType.ALL } )
@JoinTable( name= "ProfesorModulo" , joinColumns= { @JoinColumn( name= "IdProfesor" ) } , inverseJoinColumns= { @JoinColumn( name= "IdModulo" ) } )
private Set< Modulo> modulos= new HashSet ( ) ;
public Profesor( ) {
}
public Profesor( int id, String nombre, String ape1, String ape2) {
this .id = id;
this .nombre = nombre;
this .ape1 = ape1;
this .ape2 = ape2;
}
}
A la propiedad módulos
(línea 20) se han añadido dos anotaciones para indicar la relación muchos a muchos .
ManyToMany :Como su nombre indica le dice a Hibernate que la propiedad contendrá una lista de objetos que participa en una relación muchos a muchos .
cascade : Este atributo tiene el mismo significado que el del fichero de mapeo de Hibernate. Más información en
Cascade .
JoinTable : Esta anotación contiene la información sobre la tabla que realiza la relación muchos a muchos
name : Nombre de la tabla que realiza la relación muchos a muchos . En nuestro ejemplo es ProfesorModulo
.
joinColumns : Contiene cada una de las columnas que forman la clave primaria de esta clase que estamos definiendo. Cada columna se indica mediante una anotación @JoinColumn
y en el atributo name
contiene el nombre de la columna.
inverseJoinColumns : Contiene cada una de las columnas que forman la clave primaria de la clase clase con la que tenemos la relación. Cada columna se indica mediante una anotación @JoinColumn
y en el atributo name
contiene el nombre de la columna.
El código de la clase Modulo es el siguiente:
1 | Clase Modulo anotada
@Entity
@Table( name= "Modulo" )
public class Modulo implements Serializable {
@Id
@Column( name= "IdModulo" )
private int idModulo;
@Column( name= "nombre" )
private String nombre;
@ManyToMany( cascade = { CascadeType.ALL } ,mappedBy= "modulos" )
private Set< Profesor> profesores= new HashSet ( ) ;
public Modulo( ) {
}
public Modulo( int idModulo, String nombre) {
this .idModulo = idModulo;
this .nombre = nombre;
}
}
A la propiedad profesores
(línea 13) se han añadido dos anotaciones para indicar la relación muchos a muchos .
Al poner el atributo 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.
Código Java
Ahora que ya tenemos preparadas las clase Java para que puedan persistirse veamos el código necesario para persistirlas.
1 | Persistiendo la clase Profesor
Profesor profesor1= new Profesor( 11 , "Isabel" , "Fuertes" , "Gascón" ) ;
Profesor profesor2= new Profesor( 12 , "Jose" , "Valenciano" , "Gimeno" ) ;
Modulo modulo1= new Modulo( 1 , "Sistemas Operativos en Red" ) ;
Modulo modulo2= new Modulo( 2 , "Entornos de desarrollo" ) ;
Modulo modulo3= new Modulo( 3 , "Sistemas Informáticos" ) ;
profesor1.getModulos ( ) .add ( modulo1) ;
profesor1.getModulos ( ) .add ( modulo2) ;
profesor2.getModulos ( ) .add ( modulo3) ;
modulo1.getProfesores ( ) .add ( profesor1) ;
modulo2.getProfesores ( ) .add ( profesor1) ;
modulo3.getProfesores ( ) .add ( profesor2) ;
Session session= sessionFactory.openSession ( ) ;
session.beginTransaction ( ) ;
session.save ( profesor1) ;
session.save ( profesor2) ;
session.getTransaction ( ) .commit ( ) ;
session.close ( ) ;
La explicación del código es la siguiente:
En las líneas 1 y 2 se crean dos objetos Profesor
En las líneas 4, 5 y 6 se crean tres objetos Modulo
.
De las líneas 8 a 10 se añaden los módulos a los profesores.
De las líneas 12 a 14 se añaden los profesores a los módulos.
En las líneas 20 y 21 se guardan los dos objetos 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.
En este ejemplo no se ha creado el objeto
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.
unidades/03_relaciones/05_muchos_a_muchos_desordenada.txt · Última modificación: 2023/04/07 21:26 por 127.0.0.1
Herramientas de la página