27 de junio de 2009

Hibernate - Parte 4: Relaciones / Uno a muchos

En el tutorial anterior dijimos que en las bases de datos existen 7 tipos de relaciones, y vimos cómo crear relaciones uno a uno, tanto unidireccionales com bidireccionales, con Hibernate usando archivos de mapeo y anotaciones (en ejemplos separados). Cubriendo con eso 2 de los 7 tipos.

Ahora veremos como trabajar con relaciones Uno a Muchos, unidireccionales y bidireccionales. Con lo que cubriremos otros dos tipos.

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 en el editor nuestra clase "Main".

Agregamos la biblioteca de Hibernate, que creamos en el primer tutorial de la serie. Hacemos clic derecho en el nodo "Libraries" del proyecto. 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.

Pues bien, comencemos.

En las relaciones Uno a Muchos, un objeto de la entidad "A" (el lado uno) está relacionado con muchos objetos de la entidad "B" (el lado muchos). En el caso de que la relación sea unidireccional, solo la entidad "A" tiene una referencia a los objetos de tipo "B" y esta relación está representada por una colección (un List o un Set) y la entidad "A" puede acceder a cada uno de los objetos de tipo "B" de esa colección. Un ejemplo de este tipo de relaciones puede ser una Persona puede tener muchos Libros. Donde la Persona es el lado Uno de la relación, y los Libros son el lado Muchos.

Si la relación es bidireccional, adicionalmente, las entidades "B" tendrán una referencia a la entidad "A" con la que están relacionados. Un ejemplo de esto es un Jefe y sus Empleados. En donde el Jefe es el lado Uno y los Empleados el lado Muchos.

Este tipo de relación se vería de forma gráfica más o menos así:



Bien, ahora comencemos con los ejemplos:


3 - Relaciones Uno a Muchos Unidireccionales


Primero crearemos las clases Persona y Libro (para la relación unidireccional), dentro del paquete "modelo" que creamos hace unos momentos. La clase Libro queda así:

public class Libro  
{ 
    private long id; 
    private String titulo;

    public Libro() 
    { 
    }  

    public long getId() 
    { 
        return id; 
    }  

    protected void setId(long id) 
    { 
        this.id = id; 
    }  
    
    public String getTitulo() 
    { 
        return titulo; 
    }  

    public void setTitulo(String titulo) 
    { 
        this.titulo = titulo; 
    } 
}


Como podemos ver es una clase muy simple, solo tiene dos atributos: id y titulo y, como no tiene ninguna referencia a la clase Persona, no sabe nada de la existencia de una relación con esta.

Pasemos a ver cómo queda la clase Persona:

public class Persona  
{ 
    private long id; 
    private String nombre; 
    private List<Libro> libros = new ArrayList<Libro>();  

    public Persona() 
    { 
    }  

    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 getLibros() 
    { 
        return libros; 
    }  

    public void setLibros(List libros) 
    { 
        this.libros = libros; 
    }  

    public void addLibro(Libro libro) 
    { 
        this.libros.add(libro); 
    } 
}


La clase Persona tiene, como habíamos dicho, una referencia a una colección (en este caso a una lista de objetos Libro, por lo que puede acceder a cada uno de los datos de los libros. La lista se inicializa con un ArrayList en el momento de la creación del objeto. Esto nos ayudará a agregar libros tan pronto como creamos un nuevo objeto Persona, sin recibir una NullPointerException.

He agregado además de los setters y los getters para la lista de Libros un método auxiliar llamado "addLibro" que permite agregar un libro a la lista.

Ahora veremos cómo indicarle a Hibernate que existen estas relaciones, primero usando archivos de mapeo y después usando anotaciones:


3.1 Relaciones Uno a Muchos Unidireccionales con Archivos de Mapeo


Creamos un nuevo documento XML en el paquete "mapeos". Le damos el nombre de "Libro.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 con 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.unomuchos.modelo.Libro" table="LIBROS">

    </class>
</hibernate-mapping>


El mapeo de la clase Libro 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.unomuchos.modelo.Libro" table="LIBROS"> 

        <id name="id"> 
            <generator class="identity" /> 
        </id> 
      
        <property name="titulo" /> 
    </class> 
</hibernate-mapping>




Ahora crearemos el mapeo para la clase "Persona", llamado "Persona.hbm.xml" en el paquete "mapeos", de la misma forma que lo hicimos para Libro. El mapeo, excluyendo el elemento que representa la relación debe estar de esta forma:

<?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.unomuchos.modelo.Persona" table="PERSONAS">
        <id name="id" column="ID_PERSONA" >
            <generator class="identity" />
        </id>

        <property name="nombre" />

    </class>
</hibernate-mapping>


Noten que en esta ocasión agregue el atributo "column" en el elemento "<id>". Esto es importante ya que deberemos indicar el nombre de esta columna para poder crear la relación (cuál será la llave foránea que se usará).

Ahora ocupémonos de la relación.

Las relaciones uno a muchos se representan usando el elemento acorde con el tipo de colección que estemos usando (list, set, map, array, y primitive-array) existe otro elemento llamado "bag" usado de forma menos frecuente y que tal vez veamos en otro tutorial.

Cada tipo de colección se mapea de forma un poco distinta. Como nosostros estamos usando una lista, veremos cómo mapear esta pero pueden encontrar como mapear los otros tipos de colecciones 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 Persona, que representa la relación. En este caso el atributo se llama "libros". 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 una Persona, las operaciones pasen también a todos sus Libros relacionados, por lo que usamos el valor "all".

En las relaciones uno a muchos existe dos estilos de cascada especiales llamados "delete-orphan" y "all-delete-orphan" (que solo existen si usamos archivos de mapeo) los cuales se encargan de que, en el caso de que se elimine el objeto padre (Persona), todos los objetos hijos (Libros) 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:
    
<list name="libros" cascade="all-delete-orphan">   

</list>


Ahora indicamos cuál será el valor que se usará como llave foránea para relacionar los Libros con la Persona. ¿Recuerdan que indicamos el nombre de la columna para el identificador de la Persona? Pues bien, es aquí en donde usaremos este valor:
    
<key column="ID_PERSONA" /> 


Con esto indicamos que, en la tabla que se creará para mantener las entidades Libro, se debe crear una llave foránea relacionada con el identificador de la tabla Persona, identificada en este caso por la columna "ID_PERSONA". Indicamos el nombre de la columna de forma explícita ya que si dejamos que sea Hibernate quien lo genere, el nombre de la llave foránea (id) será el mismo que el de la llave primara (id) y ocurrirán errores al tratar de insertar entidades (claro que esto también podría solucionarse dando a los atributos nombres distintos desde el principio, pero es cuestión de gustos).

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 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" />


Dejamos lo mejor para el final ^-^ ya que ahora debemos indicar qué tipo de relación representa esta colección. Como en este caso estamos representando una relación "uno a muchos", usamos el elemento "<one-to-many>". En el cual debemos indicar de qué clase son las entidades que estamos guardando en la lista (ya que este dato no puede ser obtenido usando reflexión):
    
<one-to-many class="hibernate.relaciones.unomuchos.modelo.Libro" />


Finalmente el mapeo de la relación queda así:

<list name="libros" cascade="all-delete-orphan"> 
    <key column="ID_PERSONA" /> 
    <index column="ORDEN" /> 
    <one-to-many class="hibernate.relaciones.unomuchos.modelo.Libro" /> 
</list>


Y el archivo de mapeo para la entidad Persona completo así:

<hibernate-mapping> 
    <class name="hibernate.relaciones.unomuchos.modelo.Persona" table="PERSONAS"> 
        <id name="id" column="ID_PERSONA"> 
            <generator class="identity" /> 
        </id>  

        <property name="nombre" />  

        <list name="libros" cascade="all-delete-orphan"> 
            <key column="ID_PERSONA" /> 
            <index column="ORDEN" /> 
            <one-to-many class="hibernate.relaciones.unomuchos.modelo.Libro" /> 
        </list>  
    </class> 
</hibernate-mapping>




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

Ahora colocaremos el siguiente código (como siempre auto-explicatvo) como nuestro método main:

public static void main(String[] args) 
{
    /*Primero creamos una persona y la asociamos con dos libros*/
    Libro libro1 = new Libro();
    libro1.setTitulo("20000 leguas de viaje submarino");

    Libro libro2 = new Libro();
    libro2.setTitulo("La maquina del tiempo");

    Persona persona1 = new Persona();
    persona1.setNombre("Persona que se eliminara");

    persona1.addLibro(libro1);
    persona1.addLibro(libro2);


    /*Creamos una segunda persona, que sera eliminada, y la asociamos con otros
    dos libros*/
    Libro libro3 = new Libro();
    libro3.setTitulo("El ingenioso hidalgo don Quijote de la Mancha");

    Libro libro4 = new Libro();
    libro4.setTitulo("La Galatea");

    Persona persona2 = new Persona();
    persona2.setNombre("Alex");

    persona2.addLibro(libro3);
    persona2.addLibro(libro4);

        
    /*En la primer sesion guardamos las dos personas (los libros correspondientes
    seran guardados en cascada*/
    Session sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();

    sesion.persist(persona1);
    sesion.persist(persona2);

    sesion.getTransaction().commit();
    sesion.close();


    /*En la segunda sesion eliminamos la persona1 (los dos primeros libros seran
    borrados en cascada)*/
    sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();

    sesion.delete(persona1);

    sesion.getTransaction().commit();
    sesion.close();
}


Según el código anterior en la base de datos debería quedar una Persona, la segunda que guardamos ("Alex"), con sus dos libros respectivos. Comprobemos que esto es verdad.



Podemos ver que efectivamente el resultado es el esperado ^-^.

Ahora veamos cómo hacer lo mismo pero usando anotaciones.


3.2 Relaciones Uno 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 la que indica la relación) serán las mismas que expliqué en el segundo tutorial de esta serie, quedan de la siguiente forma:

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

    public Libro()
    {
    }

    public long getId()
    {
        return id;
    }

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

    public String getTitulo()
    {
        return titulo;
    }

    public void setTitulo(String titulo)
    {
        this.titulo = titulo;
    }
}


Y esta es la de la clase Persona:

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

    public Persona()
    {
    }

    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 getLibros()
    {
        return libros;
    }

    public void setLibros(List libros)
    {
        this.libros = libros;
    }

    public void addLibro(Libro libro)
    {
        this.libros.add(libro);
    }
}


Ahora explicaré la anotación que usaremos para indicar la relación. Esta anotación es "@OneToMany" y la colocamos en el atributo de tipo colección de Libros (en este caso la clase Persona será la dueña de la relación ya que es la única que está consciente de esta).

Igual que lo hicimos en la anotación "@OneToOne" aqui 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. Explico: solo existen dos tipo de recuperación: tardado (lazy)e inmediato (eager). Si decidimos que la recuperación sea "lazy" entonces las entidades relacionadas (los Libros de la colección) no serán recuperados de la base de datos al momento que se recupera la Persona dueña, sino hasta que se usen estos elementos (siempre y cuando estemos dentro de una transacción). Si la recuperación es "eager" entonces los Libros relacionados se recuperarán al mismo tiempo que la Persona dueña. En este caso yo usaré un fetch de tipo eager.

Al final la relación queda de esta forma:

@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
private List<Libro> libros = new ArrayList<Libro>();


Y la clase Persona, con todas sus anotaciones, así:

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

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    private List&ltLibro> libros = new ArrayList<Libro>();

    public Persona()
    {
    }

    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 getLibros()
    {
        return libros;
    }

    public void setLibros(List libros)
    {
        this.libros = libros;
    }

    public void addLibro(Libro libro)
    {
        this.libros.add(libro);
    }
}


Y listo, con esto ya tenemos una relación Uno a Muchos unidireccional con la Persona como dueña de la relación y varios Libros como entidad inversa.

No olviden que debemos colocar en el archivo de configuración de Hibernate estas clases:

<mapping class="hibernate.relaciones.unomuchos.anotaciones.modelo.Libro" /> 
<mapping class="hibernate.relaciones.unomuchos.anotaciones.modelo.Persona" />


Usaremos la clase HibernateUtil que creamos en el segundo tutorial de la serie para implementar el código de prueba y el siguiente código dentro la clase main:

public static void main(String[] args) 
{ 
    /*Primero creamos una persona y la asociamos con dos libros*/ 
    Libro libro1 = new Libro(); 
    libro1.setTitulo("20000 leguas de viaje submarino");  

    Libro libro2 = new Libro(); 
    libro2.setTitulo("La maquina del tiempo");  

    Persona persona1 = new Persona(); 
    persona1.setNombre("Persona que se eliminara");  
    persona1.addLibro(libro1); 
    persona1.addLibro(libro2);   

    /*Creamos una segunda persona, que sera eliminada, y la asociamos
    con otros dos libros*/ 
    Libro libro3 = new Libro(); 
    libro3.setTitulo("El ingenioso hidalgo don Quijote de la Mancha");  

    Libro libro4 = new Libro(); 
    libro4.setTitulo("La Galatea");  

    Persona persona2 = new Persona(); 
    persona2.setNombre("Alex");  
    persona2.addLibro(libro3); 
    persona2.addLibro(libro4);   

    /*En la primer sesion guardamos las dos personas (los libros
    correspondientes seran guardados en cascada*/ 
    Session sesion = HibernateUtil.getSessionFactory().openSession(); 
    sesion.beginTransaction();  

    sesion.persist(persona1); 
    sesion.persist(persona2);  

    sesion.getTransaction().commit(); 
    sesion.close();   

    /*En la segunda sesion eliminamos la persona1 (los dos primeros
    libros seran borrados en cascada)*/ 
    sesion = HibernateUtil.getSessionFactory().openSession(); 
    sesion.beginTransaction();  

    sesion.delete(persona1);  

    sesion.getTransaction().commit(); 
    sesion.close();     
}


Según el código anterior, debería haber quedado una sola Persona en la base de datos con sus dos Libros relacionados, vemos que esto sea así:



Como podemos ver, todo ha salido bien ^-^. Ahora podemos pasar a ver cómo funciona la relación Uno a Muchos bidireccional.


4 - Relaciones Uno a Muchos Bidireccionales


Las relaciones bidireccionales son muy parecidas a las unidireccionales, con la diferencia que el lado inverso de la relación también sabe de esta, por lo que tiene una referencia al dueño. Veamos esto con un ejemplo para que quede más claro. Nuevamente comenzaremos con un ejemplo usando archivos de mapeo y posteriormente usaremos anotaciones.


4.1 Relaciones Uno a Muchos Bidireccionales con Archivos de Mapeo


Modificaremos un poco nuestra clase "Libro" para agregar una referencia a la "Persona" (con sus correspondientes setters y getters):

private Persona persona;

public Persona getPersona()
{
    return persona;
}

public void setPersona(Persona persona)
{
    this.persona = persona;
}


Por lo que la clase queda de la siguiente forma:

public class Libro 
{
    private long id;
    private String titulo;
    private Persona persona;

    public Libro()
    {
    }

    public long getId()
    {
        return id;
    }

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

    public String getTitulo()
    {
        return titulo;
    }

    public void setTitulo(String titulo)
    {
       this.titulo = titulo;
    }

    public Persona getPersona()
    {
        return persona;
    }

    public void setPersona(Persona persona)
    {
        this.persona = persona;
    }
}


La clase "Persona" queda intacta.

Ahora modificaremos el archivo "Libro.hbm.xml" para que refleje los campos que acabamos de realizar. Pero primero veamos como quedo el archivo de mapeo de la ("PersonaPersona.hbm.xml"):

<hibernate-mapping>
    <class name="hibernate.relaciones.unomuchos.modelo.Persona" table="PERSONAS">
        <id name="id" column="ID_PERSONA">
            <generator class="identity" />
        </id>

        <property name="nombre" />

        <list name="libros" cascade="all-delete-orphan">
            <key column="ID_PERSONA" />
            <index column="ORDEN" />
            <one-to-many class="hibernate.relaciones.unomuchos.modelo.Libro" />
        </list>
    </class>
</hibernate-mapping>


Como podemos ver la relación de Persona a Libros esta mapeada usando el elemento "one-to-many" (una Persona puede tener muchos Libros), por lo que para indicar la relación de Libros a Persona usamos el elemento contrario: "many-to-one" (muchos Libros pertenecen a una Persona).

En este elemento debemos indicar el nombre del elemento que representa la relación (en la clase Libro), que en este caso es "persona", y el nombre de la columna donde se almacena el identificador de la Persona ("ID_PERSONA"). Por lo que el elemento queda así:
     
<many-to-one name="persona" column="ID_PERSONA"  />


Y al final el archivo "Libro.hbm.xml" completo así:

<hibernate-mapping>
    <class name="hibernate.relaciones.unomuchos.modelo.Libro" table="LIBROS">
        <id name="id" column="id">
            <generator class="identity" />
        </id>
        <property name="titulo" />

        <many-to-one name="persona" column="ID_PERSONA"  />
    </class>
</hibernate-mapping>


Y eso es todo ^-^. Probémoslo usando el mismo código que ya teníamos en la clase Main:

public static void main(String[] args)      
{         
    /*Primero creamos una persona y la asociamos con dos libros*/         
    Libro libro1 = new Libro();         
    libro1.setTitulo("20000 leguas de viaje submarino");          
      
    Libro libro2 = new Libro();         
    libro2.setTitulo("La maquina del tiempo");          
    Persona persona1 = new Persona();         
    persona1.setNombre("Persona que se eliminara");          
    persona1.addLibro(libro1);         
    persona1.addLibro(libro2);           
      
    /*Creamos una segunda persona, que sera eliminada, y la asociamos con otros dos libros*/         
    Libro libro3 = new Libro();         
    libro3.setTitulo("El ingenioso hidalgo don Quijote de la Mancha");          
      
    Libro libro4 = new Libro();         
    libro4.setTitulo("La Galatea");          
      
    Persona persona2 = new Persona();         
    persona2.setNombre("Alex");          
    persona2.addLibro(libro3);         
    persona2.addLibro(libro4);           
      
    /*En la primer sesion guardamos las dos personas (los libros correspondientes seran guardados en cascada*/         
    Session sesion = HibernateUtil.getSessionFactory().openSession();         
    sesion.beginTransaction();          
    sesion.persist(persona1);         
    sesion.persist(persona2);          
    sesion.getTransaction().commit();         
    sesion.close();           
      
    /*En la segunda sesion eliminamos la persona1 (los dos primeros libros seran borrados en cascada)*/         
    sesion = HibernateUtil.getSessionFactory().openSession();         
    sesion.beginTransaction();          
    sesion.delete(persona1);          
    sesion.getTransaction().commit();         
    sesion.close();     
}


Ahora comprobemos que en la base de datos solo quedan los datos de la persona2, junto con sus libros correspondientes:



Como podemos ver, todo ha salido bien ^-^.

Para terminar este tutorial veamos cómo trabajar con relaciones Uno a Muchos usando anotaciones.


4.2 Relaciones Uno a Muchos Bidireccionales con Anotaciones


Como ya teníamos una relación uno a muchos unidireccional con anotaciones, representada en el atributo "libros" de la clase "Persona". Siendo la clase Persona la única que esta consciente de la relación (por eso es unidireccional).

Para transformar esta relación en bidireccional lo único que debemos hacer es agregar un atributo que represente la relación con la clase "Persona" en la clase "Libro". De la misma forma que ocurre cuando usamos archivos de mapeo: Como la relación uno a muchos está representada en la clase "Persona" usando la anotación "@OneToMany" (una Persona puede tener muchos Libros), en el lado inverso de la relación debe usarse la anotación inversa "@ManyToOne" (muchos Libros pueden pertenecer a una Persona).

Esta anotación es un poco distinta a la anotación "@OneToOne" que vimos anteriormente. En este caso es importante quién es el dueño de la relación, ya que es el dueño quien determina cómo es que el motor de persistencia hace actualizaciones en la base de datos.

En el caso de las relaciones bidireccionales, usando anotaciones, el dueño SIEMPRE es el lado muchos (esta es una regla). Por lo que en este caso el dueño de la relación es la clase "Libro".

El lado inverso debe saber cuál es el nombre que lo representa en el lado dueño (esto se aclarará en el ejemplo). Para esto se usa el elemento "mappedBy".

Para ver cómo funciona lo dicho vayamos al ejemplo que teníamos. Primero modificaremos la clase "Libro" para agregar el atributo de tipo Persona que representará la relación (recuerden que debemos usar la aniotación "@ManyToOne"):

@ManyToOne
private Persona persona;


Y eso es todo lo que hay que modificar en la clase Libro. Como ven es un cambio muy pequeño. Ahora veamos la clase Persona.

Hace unos momentos dije que la clase Libro es la clase dueña de la relación y la clase Persona es la clase inversa. También dije que la clase inversa debe indicar en la anotación que representa la relación el nombre con el que el lado dueño la representa (que en este caso es "persona") y que esto se hace en el elemento "mappedBy" de la anotación. Por lo que la relación queda representada en la clase "Persona" de la siguiente forma:

@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="persona")
private List libros = new ArrayList();


Y listo ^-^. Con estos pequeños cambios ya tenemos una relación bidireccional. Al final la clase Persona queda de la siguiente forma:

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

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="persona")
    private List libros = new ArrayList();

    public Persona()
    {
    }

    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 getLibros()
    {
        return libros;
    }

    public void setLibros(List libros)
    {
        this.libros = libros;
    }

    public void addLibro(Libro libro)
    {
        this.libros.add(libro);
    }
}


Y la clase Libro así:

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

    @ManyToOne
    private Persona persona;

    public Libro()
    {
    }

    public long getId()
    {
        return id;
    }

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

    public String getTitulo()
    {
        return titulo;
    }

    public void setTitulo(String titulo)
    {
        this.titulo = titulo;
    }

    public Persona getPersona()
    {
        return persona;
    }

    public void setPersona(Persona persona)
    {
        this.persona = persona;
    }
}


Probemos que todo funciona correctamente usando el código que teníamos anteriormente en la clase Main:

public static void main(String[] args)
{
    /*Primero creamos una persona y la asociamos con dos libros*/
    Libro libro1 = new Libro();
    libro1.setTitulo("20000 leguas de viaje submarino");

    Libro libro2 = new Libro();
    libro2.setTitulo("La maquina del tiempo");

    Persona persona1 = new Persona();
    persona1.setNombre("Persona que se eliminara");

    persona1.addLibro(libro1);
    persona1.addLibro(libro2);
    libro1.setPersona(persona1);
    libro2.setPersona(persona1);


    /*Creamos una segunda persona, que sera eliminada, y la asociamos con otros dos libros*/
    Libro libro3 = new Libro();
    libro3.setTitulo("El ingenioso hidalgo don Quijote de la Mancha");

    Libro libro4 = new Libro();
    libro4.setTitulo("La Galatea");

    Persona persona2 = new Persona();
    persona2.setNombre("Alex");

    persona2.addLibro(libro3);
    persona2.addLibro(libro4);
    libro3.setPersona(persona2);
    libro4.setPersona(persona2);


    /*En la primer sesion guardamos las dos personas (los libros correspondientes seran guardados en cascada*/
    Session sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();

    sesion.persist(persona1);
    sesion.persist(persona2);

    sesion.getTransaction().commit();
    sesion.close();


    /*En la segunda sesion eliminamos la persona1 (los dos primeros libros seran borrados en cascada)*/
    sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();

    sesion.delete(persona1);

    sesion.getTransaction().commit();
    sesion.close();
}


Nuevamente solo deben haber quedado en la base de datos los datos de la persona2 y sus libros relacionados. Comprobemos que efectivamente esto es así:



Como podemos ver todo ha salido como esperábamos ^-^.

Bien, esto es todo lo que respecta a las relaciones uno a muchos. Con este tutorial ya llevamos 4 de los 7 tipos de relaciones que podemos tener en Hibernate. En los siguientes dos tutoriales veremos los 3 tipos restantes.

Cualquier duda, comentario, o sugerencia por favor no duden en colocarlo.

Saludos.

Descarga los archivos de este tutorial desde aquí:

Entradas Relacionadas: