Tabla de Contenidos

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.

Tags anidados
  • 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.

Tags anidados
  • 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.

  • ManyToMany:Indica que la propiedad contiene una lista de objetos que participan en una relación muchos a muchos.
    • cascade: Este atributo tiene el mismo significado que el del fichero de mapeo de Hibernate. Mas información en Cascade.
    • mappedBy: Contiene el nombre de la propiedad Java de la otra clase desde la cual se relaciona con ésta. En nuestro ejemplo es la propiedad modulos.
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.