lunes, 24 de agosto de 2009

Hibernate - Parte 6: Relaciones / Muchos a Muchos

En ocasiones necesitamos relacionar muchas entidades de un tipo con muchas entidades de otro tipo. Este tipo de relaciones es muy común cuando trabajamos con bases de datos relacionales, por eso es importante saber cómo trabajar con ellas.

En este último tutorial de la serie veremos cómo crear los últimos dos tipos de relaciones el framework de Hibernate: relaciones muchos a muchos unidireccionales y bidireccionales.

Este tipo de relaciones es especial ya que, como mencione antes, tenemos que muchos registros de la entidad tipo A están relacionadas con muchos registros de la entidad tipo B.

Normalmente los registros en base de datos se relacionan usando una llave foránea de una tabla con el identificador de otra tabla, como en la siguiente imagen:



Sin embargo, como en las relaciones muchos a muchos necesitamos relacionar muchos registros de la entidad A, con muchos registros de la entidad B, y es por esto que NO nos basta con una llave foránea en una de las tablas ya que, por ejemplo, el registro con ID 1 de la entidad A podría estar relacionado con los registros con IDs 1, 3, y 5 de la entidad B (hasta aquí sería una relación uno a muchos). Sin embargo el registro 3 de la entidad B puede estas relacionado con los registros 1, 2, y 4 de la entidad A, como lo muestra la siguiente figura:



Esto no puede ser logrado utilizando simplemente llaves foráneas en las tablas de las entidades A y B. En estos casos se utiliza una tercera tabla conocida como tabla de join, o tabla de enlace, o tabla de unión.

Esta tercera tabla lo único que hace es mantener las relaciones de las entidades A que están relacionadas con las entidades B, y las entidades B que están relacionadas con las entidades A. Como lo único que necesita para esto son los identificadores de ambas tablas como llaves foráneas, esto es lo que mantiene esta tabla de unión:



Afortunadamente con Hibernate no debemos preocuparnos de esta tabla de unión, ya que es el propio framework el que se encarga de hacerlo. De lo único que debemos preocuparnos es de hacer de forma correcta los mapeos para representar esta relación (ya que tiene algunos trucos).

Comencemos con el tutorial para ver cómo crear estas relaciones. Para los ejemplos usaremos dos clases: "Estudiante" y "Materia", que veremos un poco más adelante.

Lo primero que haremos es crear un proyecto en NetBeans (menú "File -> New Project... -> Java -> Java Application"). Le damos un nombre y una ubicación al proyecto y nos aseguramos de que las opciones "Create Main Class" y "Set as Main Project" estén habilitadas. Presionamos el botón "Finish" y veremos aparecer nuestra clase "Main" en el editor.

Agregamos la biblioteca de Hibernate, que creamos en el primer tutorial de la serie. Hacemos clic derecho en el nodo "Libraries" del proyecto y en el menú contextual que se abre seleccionamos la opción "Add Library...":



En la ventana que se abre seleccionamos la biblioteca "Hibernate":



Presionamos el botón "Add Library" para que la biblioteca se agregue a nuestro proyecto. Aprovechamos también para agregar el conector de MySQL. Debemos tener los siguientes archivos en nuestro proyecto:



Ahora creamos dos paquetes, uno con el nombre "modelo", que contendrá las clases entidades, y otro con el nombre "mapeos" que contendrá los archivos de mapeo XML. Hacemos clic derecho en el nodo del paquete que se creó al generar el proyecto, en el menú contextual que se abre seleccionamos la opción "New -> Java Package..." y creamos los dos paquetes.



Que deben quedar así:



Aprovecharemos para crear nuestro archivo de configuración de Hibernate, "hibernate.cfg.xml", el cual será muy parecido al del primer tutorial:

<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>

        <!-- parametros para la conexion a la base de datos -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost/hibernaterelaciones</property>
        <property name="connection.username">usuario</property>
        <property name="connection.password">password</property>

        <!-- Configuracion del pool interno -->
        <property name="connection.pool_size">1</property>

        <!-- Dialecto de la base de datos -->
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>

        <!-- Otras propiedades importantes -->
        <property name="show_sql">true</property>
        <property name="hbm2ddl.auto">create-drop</property>

        <!-- Aqui iran los archivos de mapeo -->
        
    </session-factory>
</hibernate-configuration>


Para los ejercicios usaremos una base de datos llamada "hibernaterelaciones", en MySQL.

Nota: Yo usaré dos proyectos, uno para usar archivos de mapeo y otro para usar anotaciones y que el código de ambos no se mezcle.

Comencemos con el tutorial.


6 - Relaciones Muchos a Muchos Unidireccionales


Primero crearemos la clase "Materia", dentro del paquete “modelo”. Esta clase solo contendrá los elementos básicos: id y nombre. Por lo que la clase "Materia" queda así:

public class Materia
{
    private long id;
    private String nombre;

    public Materia()
    {
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }
}


Materia no tiene alguna referencia a la clase Estudiante, por lo que no sabe que existe una relación entre ellas.

La clase "Estudiante", colocada también en el paquete “modelo”, será la dueña de la relación, así que contendrá, además de tus atributos básicos (id y nombre), una lista de referencias a "Materia". La clase Estudiante queda de esta forma:

public class Estudiante
{
    private long id;
    private String nombre;
    private List<Materia> materias = new ArrayList<Materia>();

    public Estudiante()
    {
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public List<Materia> getMaterias()
    {
        return materias;
    }

    public void setMaterias(List<Materia> materias)
    {
        this.materias = materias;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }
}


Agregaré a la clase Estudiante un método auxiliar, addMateria, que nos permitirá agregar una materia nueva a la lista de materias de este Estudiante:
    
public void addMateria(Materia materia)
{
    this.materias.add(materia);
}


Por lo que la clase Estudiante completa queda así:

public class Estudiante
{
    private long id;
    private String nombre;
    private List<Materia> materias = new ArrayList<Materia>();

    public Estudiante()
    {
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public List<Materia> getMaterias()
    {
        return materias;
    }

    public void setMaterias(List<Materia> materias)
    {
        this.materias = materias;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }

    public void addMateria(Materia materia)
    {
        this.materias.add(materia);
    }
}


La lista de Materias que tiene el Estudiante será la referencia que usaremos para la relación muchos a muchos unidireccional. Ahora vemos cómo representar esta relación, como de costumbre, primero veremos cómo hacerlo con archivos de mapeo en XML y después veremos cómo hacerlo con anotaciones:


6.1 Relaciones Muchos a Muchos Unidireccionales con Archivos de Mapeo


Creamos un nuevo documento XML en el paquete “mapeos”. Le damos el nombre “Materia.hbm” (el asistente se encargará de colocar el .xml):



Presionamos el botón “Next >” e indicamos que queremos crear un documento XML bien formado (la primer opción) y presionamos el botón “Finish”.

Eliminamos el contenido del archivo creado y lo reemplazamos por el siguiente:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.relaciones.muchos.muchos.modelo.Materia" table="MATERIAS">

    </class>
</hibernate-mapping>


El mapeo de la clase Materia es similar al que expliqué en el primer tutorial de la serie y, al final, queda de la siguiente forma:

<hibernate-mapping>
    <class name="hibernate.relaciones.muchos.muchos.modelo.Materia" table="MATERIAS">
        <id name="id" column="ID_MATERIA">
            <generator class="identity" />
        </id>

        <property name="nombre" />

    </class>
</hibernate-mapping>




Noten que he agregado el atributo column=”ID_MATERIA” del elemento <id>. Esto no es necesario para las relaciones unidireccionales, pero es vital para las relaciones bidireccionales (si, como yo, le ponen a todos los identificadores de sus entidades el mismo nombre, id), por lo que lo colocamos desde el principio.

Ahora crearemos el mapeo para la clase Estudiante. Creamos, en el paquete “mapeos”, el archivo “Estudiante.hbm”. Eliminamos el contenido del archivo y lo reemplazamos por el siguiente:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.relaciones.muchos.muchos.modelo.Estudiante" table="ESTUDIANTES">
        

    </class>
</hibernate-mapping>


Colocamos en el archivo el mapeo de los atributos “id” y “nombre” de la clase Estudiante antes de pasar a ver el mapeo de la relación. Hasta ahora el mapeo debe verse así:

<hibernate-mapping>
    <class name="hibernate.relaciones.muchos.muchos.modelo.Estudiante" table="ESTUDIANTES">
        
        <id name="id" column="ID_ESTUDIANTE">
            <generator class="identity" />
        </id>

        <property name="nombre" />

    </class>
</hibernate-mapping>


Ahora podemos centrarnos en mapear la relación.

Al igual que ocurrió cuando vimos las relaciones uno a muchos en el cuarto tutorial, representamos las relaciones muchos a muchos usando el elemento acorde al tipo de colección que estemos usando (list, set, map, array, y primitive-array).

En este caso estamos usando una lista, por lo que veremos cómo hacer el mapeo usando esta colección, pero si están usando otra pueden encontrar más información en esta página.

Para mapear una lista usamos el elemento "<list>". En este elemento indicamos cuál es el nombre del atributo, dentro de la clase Estudiante, que representa la realción. En este caso el atributo se llama “materias”. También aquí indicamos cuáles operaciones queremos que se realicen en cascada. En este caso queremos que todas las operaciones de guardar, actualizar y eliminar que ocurran en el padre sean pasadas a la colección, o sea que cuando guardemos, actualicemos, o eliminemos un Estudiante, las operaciones pasen también a todas sus Materias relacionadas, por lo que usamos el valor "all".

En las relaciones muchos a muchos, igual que en las relaciones uno a muchos, existen dos estilos de cascada especiales llamados "delete-orphan" y "all-delete-orphan" (que solo pueden usarse con archivos de mapeo) los cuales se encargan de que, en el caso de que se elimine el objeto padre ("Estudiante"), todos los objetos hijos ("Materia") serán eliminados de la base de datos. Adicionalmente "all-delete-orphan" se encarga de que todas las otras operaciones que mencionamos antes (guardar, actualizar, y eliminar) también sean realizados en cascada, por lo que usaremos este valor.

Finalmente, si recuerdan, al principio del tutorial dije que cuando tenemos relaciones muchos a muchos, se usa una “tabla de unión” o “tabla join” para mantener los datos de qué objetos de la entidad A (Estudiante) están relacionados con qué objetos de la entidad B (Materia). En este caso debemos especificar cuál será el nombre de esta tabla de unión, usando el atributo "table" del elemento <list>. Por lo que este elemento queda, por el momento, así:

<list name="materias" table="ESTUDIANTES_MATERIAS" cascade="all-delete-orphan">
</list>


La tabla de unión generada ("ESTUDIANTES_MATERIAS") tendrá como columnas "id", que es la llave foránea de la tabla estudiantes, y una segunda columna llamada "elt" (elt significa element, que es el nombre que da por default hibérnate si no especificamos uno) que es la llave foránea de la tabla "materias".

Ahora indicamos cuál será el valor que se usará como llave foránea para relacionar las Materias con el Estudiante. Siempre, lo que usaremos será la llave primaria de la entidad que estamos mapeando (en este caso Estudiante), por lo que colocamos aquí el nombre de la columna que mantiene este valor, que en este caso es "ID_ESTUDIANTE":

<key column="ID_ESTUDIANTE " />


Ahora bien, las listas son una estructura de datos con una característica única: tienen un orden. Esto significa que el orden en el que los elementos entran en la lista es importante e, internamente, se usa un índice para saber el orden de los elementos.

Cuando tratamos de almacenar estos datos nos interesa que en el momento que sean recuperados, los elementos de la lista estén en el mismo orden en el que los guardamos y es por esta razón que se debe usar una columna extra en la tabla de unión generada para guardar este índice (el cual comienza en cero). Para indicar el nombre que tendrá esta columna usamos el elemento "index" y colocamos en su atributo "column" el nombre que tendrá esta columna:

<index column="ORDEN" />


Ahora si, por fin ha llego el momento de usar el elemento que representa la relación ^-^.

Las relaciones muchos a muchos las representamos usando el elemento <many-to-many>. Este elemento se coloca dentro del elemento <list> (o el elemento que esten usando para representar su relación) que acabo de explicar, y lo único que debemos indicarle de qué clase son las entidades que estamos guardando en la lista (ya que este dato no puede ser obtenido usando reflexión), y cuál es la columna que se usa para almacenar el id de esta entidad:

<many-to-many class="hibernate.relaciones.muchos.muchos.modelo.Materia" column="ID_MATERIA" /> 


Finalmente, el mapeo del a relación queda así:

<list name="materias" table="ESTUDIANTES_MATERIAS" cascade="all-delete-orphan" >
    <key column="ID_ESTUDIANTE" />
    <list-index column="ORDEN" />
    <many-to-many class="hibernate.relaciones.muchos.muchos.modelo.Materia" column="ID_MATERIA" />
</list>


Y el archivo de mapeo para la entidad Estudiante queda así:

<hibernate-mapping>
    <class name="hibernate.relaciones.muchos.muchos.modelo.Estudiante" table="ESTUDIANTES">
        <id name="id" column="ID_ESTUDIANTE">
            <generator class="identity" />
        </id>

        <property name="nombre" />

        <list name="materias" table="ESTUDIANTES_MATERIAS" cascade="all-delete-orphan" >
            <key column="ID_ESTUDIANTE" />
            <list-index column="ORDEN" />
            <many-to-many class="hibernate.relaciones.muchos.muchos.modelo.Materia" column="ID_MATERIA" />
        </list>

    </class>
</hibernate-mapping> 




Ahora agregamos la ruta a los dos archivos de mapeo que acabamos de crear al archivo “hibernate.cfg.xml”:

<mapping resource="hibernate/relaciones/muchos/muchos/mapeos/Materia.hbm.xml" />
<mapping resource="hibernate/relaciones/muchos/muchos/mapeos/Estudiante.hbm.xml" />


Para este ejemplo usaremos la clase HibernateUtil que creamos en el primer tutorial.

Crearemos dos objetos Estudiante, “estudiante1” y “estudiante2”. Después crearemos 6 Materias “materia1” a “materia6” y asociaremos 3 Materias con cada Estudiante. Posteriormente guardaremos en la base de datos a los 2 Estudiantes, con lo que las 6 Materias serán almacenadas en la base de datos por las operaciones en cascada que definimos. Finalmente eliminaremos al “estudiante1”, con lo que esperamos que se eliminen además “materia1”, “materia2”, y “materia3”.

Colocaremos el siguiente código, auto-explicativo, en el método main de nuestra clase Main:
    
public static void main(String[] args) 
{
    Estudiante estudiante1 = new Estudiante();
    estudiante1.setNombre("estudiante1");

    Materia materia1 = new Materia();
    materia1.setNombre("materia1");
    Materia materia2 = new Materia();
    materia2.setNombre("materia2");
    Materia materia3 = new Materia();
    materia3.setNombre("materia3");

    estudiante1.addMateria(materia1);
    estudiante1.addMateria(materia2);
    estudiante1.addMateria(materia3);

        
    Estudiante estudiante2 = new Estudiante();
    estudiante2.setNombre("estudiante2");

    Materia materia4 = new Materia();
    materia4.setNombre("materia4");
    Materia materia5 = new Materia();
    materia5.setNombre("materia5");
    Materia materia6 = new Materia();
    materia6.setNombre("materia6");

    estudiante2.addMateria(materia4);
    estudiante2.addMateria(materia5);
    estudiante2.addMateria(materia6);

    Session sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();
    sesion.save(estudiante1);
    sesion.save(estudiante2);
    sesion.getTransaction().commit();
    sesion.close();

    sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();
    sesion.delete(estudiante1);
    sesion.getTransaction().commit();
    sesion.close();
}


Probemos que todo funciona correctamente. Según lo anterior solo debe estar el “estudiante2” en la base de datos y sus 3 Materias asociadas “materia1”, “materia2”, y “materia3”:



En la imagen anterior podemos comprobar que el la base de datos están los datos correctos ^-^. También podemos ver que, efectivamente, se creó una tercera tabla que mantiene las relaciones entre Estudiante y Materia.

Ahora veremos cómo hacer lo mismo pero usando anotaciones:


6.2 Relaciones Muchos a Muchos Unidireccionales con Anotaciones


Recuerden que pasa usar anotaciones debemos agregar al proyecto la biblioteca "HibernateAnotaciones" que creamos en el segundo tutorial.

Las anotaciones (así como los archivos de configuración) de ambas clases (omitiendo el atributo que indica la relación) serán las mismas que expliqué en el segundo tutorial de esta serie, quedan de la siguiente forma:

Esta es la clase Materia:

@Entity
public class Materia implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String nombre;

    public Materia()
    {
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }
}


Y esta es la clase Estudiante:

@Entity
public class Estudiante implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String nombre;
    private List<Materia> materias = new ArrayList<Materia>();

    public Estudiante()
    {
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public List<Materia> getMaterias()
    {
        return materias;
    }

    public void setMaterias(List<Materia> materias)
    {
        this.materias = materias;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }

    public void addMateria(Materia materia)
    {
        this.materias.add(materia);
    }
}


La anotación que nos interesa, para representar la relación muchos a muchos es @ManyToMany. Esta anotación debemos colocarla en el atributo que representa la relación (en este caso “materias”).

Igual que lo hicimos en la anotación "@OneToOne", y con "@OneToMany", aquí definiremos qué operaciones serán realizadas en cascada. Además también podemos indicar el tipo de "fetch" o recuperación que tendrá la colección, de la misma forma que lo expliqué en el tutorial anterior.

Al final, la relación queda de esta forma:

@ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
private List<Materia> materias = new ArrayList<Materia>();


Y la clase Estudiante, con todas sus anotaciones, queda así:

@Entity
public class Estudiante implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String nombre;
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<Materia> materias = new ArrayList<Materia>();

    public Estudiante()
    {
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public List<Materia> getMaterias()
    {
        return materias;
    }

    public void setMaterias(List<Materia> materias)
    {
        this.materias = materias;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }

    public void addMateria(Materia materia)
    {
        this.materias.add(materia);
    }
}


Y eso es todo lo que necesitamos para tener una relación muchos a muchos unidireccional usando anotaciones.

Recuerden que debemos colocar estas dos clases en el archivo de configuración de Hibernate:

<mapping class="hibernate.relaciones.muchos.muchos.anotaciones.modelo.Estudiante" />
<mapping class="hibernate.relaciones.muchos.muchos.anotaciones.modelo.Materia" />


Ahora usaremos la clase HibernateUtil del segundo tutorial para implementar nuestra prueba. La prueba será la misma que describí anteriormente: Crearemos 2 objetos Estudiante, “estudiante1” y “estudiante2”. Después crearemos 6 Materias “materia1” a “materia6” y asociaremos 3 materias con cada estudiante. Posteriormente guardaremos en la base de datos a los 2 estudiantes, con lo que las 6 materias serán almacenadas en la base de datos por las operaciones en cascada que definimos. Finalmente eliminaremos al “estudiante1”, con lo que esperamos que se eliminen además “materia1”, “materia2”, y “materia3”.

Colocamos el siguiente código como el método main de nuestra clase Main:

public static void main(String[] args)
{
    Estudiante estudiante1 = new Estudiante();
    estudiante1.setNombre("estudiante1");

    Materia materia1 = new Materia();
    materia1.setNombre("materia1");
    Materia materia2 = new Materia();
    materia2.setNombre("materia2");
    Materia materia3 = new Materia();
    materia3.setNombre("materia3");

    estudiante1.addMateria(materia1);
    estudiante1.addMateria(materia2);
    estudiante1.addMateria(materia3);


    Estudiante estudiante2 = new Estudiante();
    estudiante2.setNombre("estudiante2");

    Materia materia4 = new Materia();
    materia4.setNombre("materia4");
    Materia materia5 = new Materia();
    materia5.setNombre("materia5");
    Materia materia6 = new Materia();
    materia6.setNombre("materia6");

    estudiante2.addMateria(materia4);
    estudiante2.addMateria(materia5);
    estudiante2.addMateria(materia6);

    Session sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();
    sesion.save(estudiante1);
    sesion.save(estudiante2);
    sesion.getTransaction().commit();
    sesion.close();

    sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();
    sesion.delete(estudiante1);
    sesion.getTransaction().commit();
    sesion.close();
}


Ahora comprobemos que el ejemplo funciona:



Como podemos observar todo ha funcionado correctamente ^-^.

Ahora, veamos cómo crear relaciones muchos a muchos bidireccionales. Primero veamos cómo hacerlo mediante archivos de mapeo.


7 - Relaciones Muchos a Muchos Bidireccionales


Las relaciones muchos a muchos bidireccionales son muy parecidas a las relaciones unidireccionales, pero con la diferencia de que, como en el caso de todas las relaciones bidireccionales, ambos lados de la relación saben de la existencia de esta. Veamos cómo representar estas relaciones, nuevamente usando primero archivos de mapeo y después con anotaciones.


7.1 Relaciones Muchos a Muchos Bidireccionales con Archivos de Mapeo


Las relaciones muchos a muchos bidireccionales son muy parecidas a las relaciones unidireccionales, por lo que los cambios que deberemos hacer son mínimos.

Modificaremos un poco nuestra clase “Materia” para que contenga una referencia a todos los Estudiantes que estén tomando esa Materia. Como muchos Estudiantes pueden tomar una Materia, deberemos agregar una lista de objetos “Estudiante” a la clase Materia, de esta forma:

private List<Estudiante> estudiantes = new ArrayList<Estudiante>();


También agregamos los setters y los getters para este atributo.

public List<Estudiante> getEstudiantes()
{
    return estudiantes;
}

public void setEstudiantes(List<Estudiante> estudiantes)
{
    this.estudiantes = estudiantes;
}


Además pondré un método auxiliar extra que nos permitirá que se agregue un Estudiante a la lista de Estudiantes para esta Materia. Además, al mismo tiempo, agregará esta Materia a la lista de Materias para el Estudiante:

public void addEstudiante(Estudiante estudiante)
{
    this.estudiantes.add(estudiante);
    estudiante.addMateria(this);
}


La clase Materia queda de esta forma:

public class Materia
{
    private long id;
    private String nombre;
    private List<Estudiante> estudiantes = new ArrayList<Estudiante>();

    public Materia()
    {
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }

    public List<Estudiante> getEstudiantes()
    {
        return estudiantes;
    }

    public void setEstudiantes(List<Estudiante> estudiantes)
    {
        this.estudiantes = estudiantes;
    }

    public void addEstudiante(Estudiante estudiante)
    {
        this.estudiantes.add(estudiante);
        estudiante.addMateria(this);
    }
}


La clase Estudiante queda igual, por lo que no es necesario modificarle nada.

Ahora hay que indicar esta relación en el archivo de mapeo “Materia.hbm.xml”.

Esta relación la indicamos exactamente como lo hicimos en el archivo “Estudiante.hbm.xml”: Agregamos un elemento <list>, con el nombre del atributo que representa la relación en la clase Materia, que en este caso es “estudiantes”, y, de la misma forma que hicimos antes, indicamos el nombre de la tabla de unión para estas dos entidades.

El siguiente paso es muy importante. ¿Recuerdan que cuando hablé sobre las relaciones uno a muchos bidireccionales dije que existía un lado dueño de la relación y un lado inverso. Pues bien, en este caso ocurre lo mismo, una de las entidades es la dueña de la relación y el otro es el lado inverso. Quién debe ser el dueño y quién el inverso depende de la lógica de su aplicación. Para este ejemplo, supondré que “Estudiante” es el dueño de la relación (para no tener que modificar su archivo de mapeo), y que “Materias” es el lado inverso. Esto lo indicamos usando el atributo “inverse” con valor “true” en el elemento <list>.

El resto de los elementos que representan el mapeo de la relación son exactamente los mismos que expliqué en el mapeo de la relación unidireccional, por lo que el mapeo queda de esta forma:

<list name="estudiantes" table="ESTUDIANTES_MATERIAS" inverse="true" >
    <key column="ID_MATERIA" />
    <list-index column="ORDEN" />
    <many-to-many class="hibernate.relaciones.muchos.muchos.modelo.Estudiante" column="ID_ESTUDIANTE" />
</list>


Y el archivo completo de mapeo así:

<hibernate-mapping>
    <class name="hibernate.relaciones.muchos.muchos.modelo.Materia" table="MATERIAS">
        <id name="id" column="ID_MATERIA">
            <generator class="identity" />
        </id>

        <property name="nombre" />

        <list name="estudiantes" table="ESTUDIANTES_MATERIAS" inverse="true" >
            <key column="ID_MATERIA" />
            <list-index column="ORDEN" />
            <many-to-many class="hibernate.relaciones.muchos.muchos.modelo.Estudiante" column="ID_ESTUDIANTE" />
        </list>

    </class>
</hibernate-mapping>




El archivo “Estudiante.hbm.xml” queda exactamente igual.

Modificaremos un poco el código del método main de la clase Main para usar el método auxiliar “addEstudiante” de la clase “Materia”, dejándolo así:

public static void main(String[] args)
{
    Estudiante estudiante1 = new Estudiante();
    estudiante1.setNombre("estudiante1");

    Materia materia1 = new Materia();
    materia1.setNombre("materia1");
    Materia materia2 = new Materia();
    materia2.setNombre("materia2");
    Materia materia3 = new Materia();
    materia3.setNombre("materia3");

    materia1.addEstudiante(estudiante1);
    materia2.addEstudiante(estudiante1);
    materia3.addEstudiante(estudiante1);


    Estudiante estudiante2 = new Estudiante();
    estudiante2.setNombre("estudiante2");

    Materia materia4 = new Materia();
    materia4.setNombre("materia4");
    Materia materia5 = new Materia();
    materia5.setNombre("materia5");
    Materia materia6 = new Materia();
    materia6.setNombre("materia6");

    materia4.addEstudiante(estudiante2);
    materia5.addEstudiante(estudiante2);
    materia6.addEstudiante(estudiante2);

    Session sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();
    sesion.save(estudiante1);
    sesion.save(estudiante2);
    sesion.getTransaction().commit();
    sesion.close();

    sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();
    sesion.delete(estudiante1);
    sesion.getTransaction().commit();
    sesion.close();
}


Debe ocurrir lo mismo que en la ocasión anterior: se crean 2 Estudiantes y 6 Materias, relacionando 3 Materias con cada Estudiante. Después se almacenan los Estudiantes, y sus Materias en cascada, y finalmente se elimina el primer Estudiante. Por lo que solo deben quedar en la base el “estudiante2” y las materias 4 a 6:



Podemos comprobar que todo salió como esperábamos ^-^. Por lo que solo queda que veamos cómo crear relaciones bidireccionales con anotaciones:


7.2 Relaciones Muchos a Muchos Bidireccionales con Anotaciones


Finalmente, y para terminar este tutorial, modificaremos el ejemplo de la relación muchos a muchos unidireccional con anotaciones para transformarlo en bidireccional.

Para hacer eso, debemos agregar un atributo que represente la relación dentro de la clase “Materia”. Como la relación es muchos a muchos, esta relación estará representada por un atributo de tipo lista de Estudiantes, de esta forma:

private List<Estudiante> estudiantes = new ArrayList<Estudiante>();


A este atributo lo marcamos con la anotación @ManyToMany, de la misma forma que lo está el atributo “materias” de la clase Estudiante:

@ManyToMany
private List<Estudiante> estudiantes = new ArrayList<Estudiante>();


Agregamos además los setters y los getters de este atributo y el método auxiliar addEstudiante, igual que lo hicimos en el caso de los archivos de mapeo:

public List<Estudiante> getEstudiantes()
{
    return estudiantes;
}

public void setEstudiantes(List<Estudiante> estudiantes)
{
    this.estudiantes = estudiantes;
}

public void addEstudiante(Estudiante estudiante)
{
    this.estudiantes.add(estudiante);
    estudiante.addMateria(this);
}


Bien, con esto ya casi hemos terminado. Si recuerdan, cuando hable de las relaciones uno a muchos bidireccionales con anotaciones dije que uno de los dos lados de la relación debe ser el dueño y el otro el inverso, y que existe una regla: el lado muchos SIEMPRE es el lado dueño. Pues bien, esto también aplica para las relaciones muchos a muchos. Claro que como aquí ambos lados son el lado mucho, podemos elegir el que nosotros queramos para que sea el dueño.

En mi caso escogeré la entidad Estudiante como el dueño, solo para no modificar la clase, y por lo tanto, la entidad Materia será el lado inverso, por lo que debemos indicarlo colocando, en la anotación @ManyToMany el atributo “mappedBy”, como expliqué en el cuarto tutorial, cuyo valor será el nombre del atributo que representa la lista de Materias en la clase Estudiante, que en este caso es “materias”. Por lo que el atributo queda de esta forma:

@ManyToMany(mappedBy="materias")
private List<Estudiante> estudiantes = new ArrayList<Estudiante>();


Y la clase Estudiante así:

@Entity
public class Materia implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String nombre;
    @ManyToMany(mappedBy = "materias")
    private List<Estudiante> estudiantes = new ArrayList<Estudiante>();

    public Materia()
    {
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }

    public List<Estudiante> getEstudiantes()
    {
        return estudiantes;
    }

    public void setEstudiantes(List<Estudiante> estudiantes)
    {
        this.estudiantes = estudiantes;
    }

    public void addEstudiante(Estudiante estudiante)
    {
        this.estudiantes.add(estudiante);
        estudiante.addMateria(this);
    }
}


Ahora comprobemos que todo esté bien configurado colocando el siguiente código en el método main de nuestra clase Main:

public static void main(String[] args)
{
    Estudiante estudiante1 = new Estudiante();
    estudiante1.setNombre("estudiante1");

    Materia materia1 = new Materia();
    materia1.setNombre("materia1");
    Materia materia2 = new Materia();
    materia2.setNombre("materia2");
    Materia materia3 = new Materia();
    materia3.setNombre("materia3");

    materia1.addEstudiante(estudiante1);
    materia2.addEstudiante(estudiante1);
    materia3.addEstudiante(estudiante1);


    Estudiante estudiante2 = new Estudiante();
    estudiante2.setNombre("estudiante2");

    Materia materia4 = new Materia();
    materia4.setNombre("materia4");
    Materia materia5 = new Materia();
    materia5.setNombre("materia5");
    Materia materia6 = new Materia();
    materia6.setNombre("materia6");

    materia4.addEstudiante(estudiante2);
    materia5.addEstudiante(estudiante2);
    materia6.addEstudiante(estudiante2);

    Session sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();
    sesion.save(estudiante1);
    sesion.save(estudiante2);
    sesion.getTransaction().commit();
    sesion.close();

    sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();
    sesion.delete(estudiante1);
    sesion.getTransaction().commit();
    sesion.close();
}


El cual hace lo mismo que en el caso anterior: se crean 2 Estudiantes y 6 Materias, relacionando 3 Materias con cada Estudiante. Después se almacenan los Estudiantes, y sus Materias en cascada, y finalmente se elimina el primer Estudiante. Por lo que solo deben quedar en la base el “estudiante2” y las materias 4 a 6:



Como podemos ver todo ha salido bien ^-^ (¿Notaron que cuando usamos anotaciones, en la tabla de unión no se genera una columna de orden?), por lo que podemos dar por terminado este tutorial, y lo que respecta a las relaciones con hibérnate ^-^.

En el siguiente post mostraré cómo hacer consultas con Hibernate usando un lenguaje especial de consultas llamado HQL (Hibernate Query Language) para ver cómo podemos recuperar las entidades con las distintas relaciones que hemos visto.

Si tienen alguna duda, comentario o sugerencia no duden en escribir un comentario.

Saludos.

Descarga los archivos de este tutorial desde aquí:

Entradas Relacionadas:

23 comentarios:

  1. Hola este comemntario te lo escribí en la parte 4 por equivocación, te lo vuelvo a poner aquí ya que esto mismo le podría interesar a más gente: estoy empleando una relación muchos a muchos y en la tabla intermedia que se genera quería añadir un nuevo campo fecha. ¿Es esto posible? He buscado por todos los lados y no he encontrado nada.

    Saludos

    ResponderEliminar
  2. Hola Jose Luis;

    Lamentablemente esto que preguntas no es posible, ya que la unica razón de ser de esta tabla intermedia es la de mantener la relación entre las entidades.

    Se supone que hay formas de engañar a Hibernate para que haga esto, pero yo mismo he tratado de hacelo sin lograrlo. De hecho los mismos creadores de Hibernate comentan que, en caso de necesitar algo como esto lo más recomendable es que cres una nueva entidad que mantenga estas relaciones y con los datos extra que necesites.

    En este foro explican un poco eso:

    https://forum.hibernate.org/viewtopic.php?p=2378153

    Aunque si logras encontrar alguna forma de hacerlo te agradecería que nos dijeras, ya que, es algo que nos puede ser de mucha ayuda a todos.

    Saludos.

    ResponderEliminar
  3. Muy buen aporte gracias por tus tutoriales.

    ResponderEliminar
  4. Tengo una duda, en la parte de:



    Significa que en mi tabla intermedia, debe de tener un campo que se llame ORDEN? o como yo la quiera nombrar? esto para ordenar la lista.

    Lo que pasa es que sino agrego ese campo me muestra error, si lo quiero ordenar por alguno de los id's de la tabla intermedia me marca q la columna está repedita.

    Saludos, muchas gracias

    ResponderEliminar
  5. No aparecio el código en mi comentario de arriba, me referia a la parte de
    index column = "ORDEN"

    Gracias

    ResponderEliminar
  6. Por favor podrias seguir con los turoriales de hibernate!!!

    ResponderEliminar
  7. Alex, Tengo una relacion Muchos a Muchos, la cual al eliminar me da un error como el siguiente:

    Cannot delete or update a parent row: a foreign key constraint fails....

    Cuando entro en la administracion de las tablas de mysql, en esos indices, le doy la opcion Ondelete = Cascade y OnUpdate= Cascade, anda bien.
    Como puedo hacer o que anotacion me puede resolver esto? Probe varios opciones como poner @Cascade({org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN}), pero sigo teniendo el mismo problema.


    Saludos

    ResponderEliminar
  8. En el ejemplo de materias, el error aparece cuando quiero eliminar una materia.


    Saludos

    ResponderEliminar
  9. Hola JuanPi;

    Pues el error que te da es porque la entidad que estas intentando eliminar esta siendo referenciada por un registro de una tabla. Debes eliminar primero las relaciones que tiene ese objeto en todos lados, para después poder eliminar el objeto.

    ¿Probaste con los archivos de ejemplo del tutoral para ver si también te produce esa excepción?

    Saludos.

    ResponderEliminar
  10. Alex, si probe. En este ejemplo, con anotaciones, intente en vez de eliminar estudiante, eliminar una materia: Me da exactamente el error.

    El problema se resuelve editando la tabla cambiando el seteo de las claves foraneas: On delete: Cascade y On Update: Cascade (ambos estan en Restrict); esto lo hago manualmente. Hay alguna forma de hacerlo con alguna anotacion?

    Gracias Alex!!

    ResponderEliminar
  11. Muy bueno el tutorial es lo que estaba buscando, lo he realizado como lo pone en el ejemplo y me funciona bien pero tengo un problema y me da un error, tengo la relación de mucho a mucho con mi tabla intermedia con las llaves de cada tabla y el orden, al insertar un nuevo objeto en cada una de las tablas y agregarlo a la lista de objetos me funciona genial al igual que modificar uno de estos objetos, donde esta el problema cuando intento modificar un objeto existente agregando a la lista de objetos que tiene un nuevo objeto me da el siguiente error .
    org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [domain.Ejemplar#9] org.hibernate.engine.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:590) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:223) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:89) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70) at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507) at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499) at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:218)at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
    Es solo un una parte de la traza del error alguien tiene alguna idea de lo que me esta pasando, gracias por el tiempo dedicado y espero que me puedan ayudar. Saludos

    ResponderEliminar
  12. I hope you will keep updating your content constantly as you have one dedicated reader here.

    Aromatherapy Training

    ResponderEliminar
  13. Hola, muchas gracias por este artículo, me ha sido de gran utilidad, pero tengo un problema a la hora de realizar consultas con Criteria sobre esta relación, ya que Hibernate me da el siguiente error:

    JDBCExceptionReporter:234 - Falta el parámetro IN o OUT en el índice:: 1
    org.hibernate.exception.GenericJDBCException: could not execute query

    La instrucción de Criteria es la siguiente:

    estudiantes = entornoHB.getSession().createCriteria(modelos.Estudiantes.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).add(Restrictions.in("materias", listadoMaterias)).list();

    Donde listadoMaterias es una lista de objetos Materia, vamos que quiero sacar una lista de estudiantes que estudien X materias y no consigo encontrar la manera de hacer esto.


    Gracias y un saludo.

    ResponderEliminar
  14. @Alonso
    Hola Alonso;

    El código en realidad lo veo bien. ¿Podrías enviarme el proyecto para que lo revise con más calma?

    programadorjavablog@gmail.com

    Saludos.

    ResponderEliminar
  15. Que tal Alex, estuve haciendo todos los tutoriales hasta aquí.. me han funcionado a la perfección ahora que aprendí a usar Maven...

    Te hago una consulta de la que no pude encontrar mucha info... ¿Cuál es la diferencia entre usar una relación unidireccional y bidireccional? ¿Hay alguna razón particular para usar una y no otra en ciertos casos?

    Por lo que veo, la única diferencia es que podría agregar métodos en ambas clases para hacer la persistencia de la relación entre ellas...

    ResponderEliminar
  16. Hola, antes que nada te agradesco por el tiempo invertido en hacer estos tutoriales, tengo una duda, en la parte de muchos a muchos unidireccional con anotaciones ¿Dónde indicas la tabla de Join? de antemano gracias y buen día.

    ResponderEliminar
  17. Muy buenos blogs, explicas muy bien y eres una gran ayuda para los que nos estamos iniciando

    ResponderEliminar
  18. Bnas yo quisiera saber que necesito hacer para cambiar la dirección ip de mi servidor en el xml cuando voy a instalar mi aplicación en otro lugar, me toca abrir el codigo fuente y cambiarlo?

    ResponderEliminar
  19. Hola Alex !!!!

    Muchas gracias por el tutorial, está estupendo, sobretodo para mi que estoy iniciándome en estos temas.

    Saludos desde Colombia.

    ResponderEliminar
  20. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  21. hola Alex! como se haria la estructura de la tabla intermedia al agregar la columna orden?

    ResponderEliminar
  22. Hola. ¿Es posible crear un tabla (Entidad) relacionadora de mas de dos llaves? por ejemplo (extendiendo un poco tu ejemplo), si se tuviera ademas la taba maestro, como se haria el relacionado Alumno, Materia, Maestro?

    ResponderEliminar