jueves, 4 de junio de 2009

HIbernate - Parte 3: Relaciones / Uno a uno

En los tutoriales anteriores vimos cómo persistir objetos simples en la base de datos usando Hibernate, tanto con archivos de mapeo XML como con anotaciones. Gracias a eso no tuvimos que escribir ni una sola línea de código SQL.

Sin embargo solo en raras ocasiones deberemos guardar un objeto simple como lo vimos. Por lo regular guardamos grafos de objetos u objetos relacionados con otros objetos. En estos casos querremos que los objetos se guarden, actualicen, o eliminen en el momento que lo haga nuestro objeto principal (o tal vez no ^-^!).

En este tutorial veremos cómo manejar los objetos relacionados (con mapeos y con XML).

En Hibernate (y en general en las bases de datos) existen 4 tipos de relaciones:

  • Uno a Uno
  • Uno a Muchos
  • Muchos a Uno
  • Muchos a Muchos

Si le ponemos direcciones a estas relaciones (unidireccional, o bidireccional) tendremos 7 tipos de relaciones:

  • Uno a uno unidireccional
  • Uno a uno bidireccional
  • Uno a muchos unidireccional
  • Uno a muchos bidireccional
  • Muchos a uno unidireccional
  • muchos a muchos unidireccional
  • Muchos a muchos bidireccional

La relación muchos a uno bidireccional es igual que la relación uno a muchos bidireccional, así que la dejamos fuera.

Bien, pues en este y los siguientes tutoriales veremos cómo manejar estos 7 tipo de relaciones usando Hibernate.

En este tutorial solo veremos las relaciones uno a uno y cubriré los siguientes tipos de relaciones en las siguientes entregas, ya que hay mucho que se puede decir sobre cada uno de los tipos de relaciones.

Crearemos un proyecto en NetBeans para mostrar los ejemplos (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.

Ahora sí, comencemos con el tema del tutorial.

En las relaciones uno a uno un objeto entidad de una clase A está relacionado con uno y solo un objeto entidad de una clase B. Si la relación es unidireccional solo el objeto A está consciente de la relación (El objeto A tiene una referencia al objeto B) y el objeto B NO sabe nada de esta. Por ejemplo, cuando tenemos una relación Persona -> Direccion (es uno a uno porque una Persona, en teoría, solo puede tener una Direccion). Donde la Persona es la entidad "A" y la Direccion es la entidad "B". O sea, la Persona conoce su Direccion pero la Direccion no conoce a la Persona a la que pertenece.

En las relaciones bidireccionales las dos entidades están conscientes de la relación. Por ejemplo, si tenemos una relación Pais <-> Presidente (es uno a uno porque un Pais solo debería tener un Presidente y un Presidente solo puede serlo de un Pais). En este caso ambos lados de la relación conocen el otro lado, o su "inverso".

Veamos estos dos ejemplos. Las clases que usaremos serán muy simples y solo pondré un atributo adicional al "id" y al de la relación para que esta última sea más clara.


1 - Relaciones Uno a Uno Unidireccionales


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

public class Direccion implements Serializable
{
    private long id;
    private String calle;
    private String codigoPostal;

    public Direccion()
    {
    }

    public long getId()
    {
        return id;
    }

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

    public void setCalle(String calle)
    {
        this.calle = calle;
    }

    public String getCodigoPostal()
    {
        return codigoPostal;
    }

    public void setCodigoPostal(String codigoPostal)
    {
        this.codigoPostal = codigoPostal;
    }
}


Como podemos ver, la clase Direccion es muy simple, solo tiene el "id y dos atributos más. De hecho esta entidad no tiene idea que será relacionada con una entidad Persona, la cual por cierto queda de esta forma:

public class Persona implements Serializable
{
    private long id;
    private String nombre;

    private Direccion direccion;

    public Persona()
    {
    }

    public Direccion getDireccion()
    {
        return direccion;
    }

    public void setDireccion(Direccion direccion)
    {
        this.direccion = direccion;
    }

    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;
    }
}


La clase Persona es un poco más interesante. Esta clase tiene, además del "id" y el atributo "nombre", un atributo de tipo "Direccion" (con sus correspondientes setters y getters). Por lo tanto la Persona tendrá acceso a los datos de la Direccion. En este caso se dice que Persona es la "dueña" (owner) de la relación. O sea que ella controla qué es lo que pasa cuando se realiza una modificación en un objeto entidad (si queremos que al eliminarse el dueño se elimine la entidad relacionada, o se actualice, o no pase nada, etc.). Esto se configura en el archivo de mapeo XML o en las anotaciones, dependiendo del método que estemos utilizando.

Veamos cómo indicar que existe este relación. Primero veremos cómo indicarlo con archivos de mapeo y después veremos cómo se hace con anotaciones.


1.1 Relaciones Uno a Uno Unidireccionales con Archivos de Mapeo


Creamos, en el paquete "mapeos" un nuevo documento XML. Le damos de nombre "Direccion.hbm" (el asistente se encargará de colocar el ".xml")


Presionamos el botón "Next >" e indicamos que queremos crear un documento XML bién 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.modelo.Direccion" table="DIRECCIONES">

    </class>
</hibernate-mapping>


El mapeo de la clase "Dirección" es muy simple, como el que expliqué en el primer tutorial de la serie. El mapeo queda de la siguiente forma:

<hibernate-mapping> 
<class name="hibernate.relaciones.modelo.Direccion" table="DIRECCIONES"> 
        <id name="id" column="ID"> 
            <generator class="identity" /> 
        </id> 
        <property name="calle" /> 
        <property name="codigoPostal" /> 
    </class> 
</hibernate-mapping>




Ahora crearemos el mapeo de la clase "Persona", en un archivo llamado llamado "Persona.hbm.xml" en el paquete "mapeos", la cuál de hecho será muy parecido al de "Direccion", excepto en el identificador. Queremos que el identificador de la "Persona" y de la "Direccion" sean el mismo, ya que en las relaciones uno a uno Hibernate supone que ambas entidades tendrán el mismo identificador (lo cual podría no siempre ser verdad) y de esta forma la recuperación de la Direccion se hará automáticamente al momento de recuperar la Persona, lo mismo ocurre con la eliminación. Pero como solamente Persona está consciente de la relación, es ella quien recibirá el mismo identificador que la Direccion.

Para lograr esto usamos un generador especial de id llamado "foreign", indicando que el identificador será tomado de otra entidad. Decimos que la entidad está referenciada como la propiedad "direccion" de nuestra clase "Persona". Por lo que el identificador queda así:

<id name="id" column="ID">         
    <generator class="foreign">             
        <param name="property">direccion</param> 
    </generator>
</id>


El archivo, solamente con los mapeos para los atributos "id" y "nombre", se ve así:

<?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.modelo.Persona" table="PERSONAS">
        <id name="id" column="ID">
            <generator class="foreign"> 
            <param name="property">direccion</param> 
        </generator>
        </id>
        <property name="nombre" />
        
    </class>
</hibernate-mapping>


Ahora agregaremos el elemento para indicar la relación uno a uno con la clase "Direccion". Para esto usamos el elemento "<one-to-one>". Usamos el atributo "name" para indicar cómo se llama el atributo en la clase "Persona" que indica la relación con "Direccion", que en este caso es "direccion":

<one-to-one name="direccion" />


Queremos que la entidad "Direccion" se guarde en la base de datos en el momento en el que se guarda la "Persona". De la misma forma, queremos que cuando la "Persona" sea eliminada de la base de datos también se elimine su correspondiente "Direccion". Para lograr esto usamos el atributo "cascade" del elemento "<one-to-one>".

Las operaciones en cascada son operaciones que se realizan en los hijos al mismo momento que en los padres (o en las entidades relacionadas con la entidad en la que estamos realizando la operación)

Este atributo puede tener los siguientes valores:

  • persist
  • merge
  • save-update
  • delete
  • lock
  • refresh
  • evict
  • replicate
  • all
  • none

La mayoría de los valores corresponde con un método, con el mismo nombre, del objeto Session de Hibernate, con excepción de "all" y "none", que no corresponden con ninguno.

Por default ninguna operación se realiza en cascada, pero nosotros dijimos que queremos que la Direccion se actualice en el momento en el que guardamos y eliminamos la Persona entonces indicamos como el valor de cascade "persist, delete":

<one-to-one name="direccion" cascade="persist, delete"/>


Al final el mapeo de Persona queda de la siguiente forma:

<hibernate-mapping> 
    <class name="hibernate.relaciones.modelo.Persona" table="PERSONAS">
        <id name="id" column="ID">
            <generator class="foreign"> 
            <param name="property">direccion</param> 
        </generator>
        </id>

        <property name="nombre" /> 

        <one-to-one name="direccion" cascade="persist, delete"/> 
    </class> 
</hibernate-mapping>




Esto es todo lo que necesitamos para esta relación. No olviden agregar estos dos archivos de mapeo al archivo "hibernate.cfg.xml":

<mapping resource="hibernate/relaciones/mapeos/Persona.hbm.xml"/>
<mapping resource="hibernate/relaciones/mapeos/Direccion.hbm.xml"/>


Ahora usaremos la clase "HibernateUtil" que creamos en el primer tutorial para trabajar con nuestras entidades.

En la clase "Main" agregamos el siguiente código autoexplicativo:

public static void main(String[] args)  
{ 
    Persona persona1 = new Persona(); 
    persona1.setNombre("Persona que sera borrada");  

    Persona persona2 = new Persona(); 
    persona2.setNombre("Persona que permanecera");  

    Direccion direccion1 = new Direccion(); 
    direccion1.setCalle("Calle 1"); 
    direccion1.setCodigoPostal("12345");  

    Direccion direccion2 = new Direccion(); 
    direccion2.setCalle("Calle 2"); 
    direccion2.setCodigoPostal("54321");  

    persona1.setDireccion(direccion1); 
    persona2.setDireccion(direccion2);  

    Session sesion = HibernateUtil.getSessionFactory().openSession();   

    /*Esta direccion se agrega para comprobar que las personas tomen el mismo 
    identificador que las direcciones (ninguna persona debe tener el mismo id de
    esta direccion)*/ 

    Direccion d = new Direccion(); 
    d.setCalle("Calle de Prueba de identificadores"); 
    d.setCodigoPostal("21345");           

    /*En la primer sesion a la base de datos almacenamos los dos objetos Persona 
    los objetos Direccion se almacenaran en cascada*/ 
    sesion.beginTransaction();  
    
    sesion.persist(d); 
    sesion.persist(persona1); 
    sesion.persist(persona2);  

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


    /*En la segunda sesion eliminamos el objeto persona1, 
    la direccion1 sera borrada en cascada*/ 
    sesion = HibernateUtil.getSessionFactory().openSession(); 
    sesion.beginTransaction();  

    sesion.delete(persona1);  

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


Como podemos ver, se crean tres Direcciones y dos Personas; dos de las Direcciones se relacionan con las Personas y finalmente se elimina el objeto "persona1". Esto debería dejarnos solamente con una Persona almacenada y dos Direcciones. Comprobemos que es así:



Como podemos ver, se eliminaron tanto la Persona como la Direccion con id = 2, comprobando que nuestra configuración funciona ^-^. Como dije anteriormente, en una relación uno a uno Hibernate supondrá que ambas entidades en la relación tienen el mismo id. Nosotros nos aseguramos de que esto ocurra indicando que el id de Persona, que es la única entidad que sabe de la relación, sea tomado en base al id de su Direccion asociada. Claro que esto nos obliga a que siempre debe haber una Direccion asociada con una Persona, de lo contrario esta última no podrá obtener su id de ningún lado, por lo que debemos tener cuidado al usar este tipo de relación.

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


1.2 Relaciones Uno a Uno Unidireccionales con Anotaciones


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

Las anotaciones que colocaremos en la clase "Direccion" serán las mismas que expliqué en el segundo tutorial de esta serie, como son muy sencillas no volveré a explicarlos. La clase "Direccion anotada queda de la siguiente forma:

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

    private String calle; 
    private String codigoPostal;  

    public Direccion() 
    { 
    }  

    public long getId() 
    { 
        return id; 
    }  

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

    public String getCalle() 
    { 
        return calle; 
    }  

    public void setCalle(String calle) 
    { 
        this.calle = calle; 
    }  

    public String getCodigoPostal() 
    { 
        return codigoPostal; 
    }  

    public void setCodigoPostal(String codigoPostal) 
    { 
        this.codigoPostal = codigoPostal; 
    } 
}


Listo, con tan solo tres anotaciones nos ahorramos el crear un archivo de mapeo para esta clase.

La clase "Persona" se anota casi de la misma forma, con excepción de la indicación de la relación uno a uno con la clase "Direccion". Explicaré primero esto.

Para cada una de los 4 tipos de relaciones que expliqué antes existe una anotación especial. En el caso de las relaciones uno a uno se usa la anotación "@OneToOne" indicando cuál atributo representa la relación. En este caso la relación está representado por el atributo "direccion", así que la colocamos así:

@OneToOne 
private Direccion direccion;


Es así de simple ^-^. Habiamos dicho que nos interesa que cuando guardamos y eliminamos la "Persona" también se elimine la "Direccion" asociada, y que esto se logra indicando que esas operaciones se hagan en cascada. Usando anotaciones también podemos indicar eso usando el atributo "cascade" de la anotación que representa la relación (en este caso "@OneToOne"). Este atributo recibe un arreglo de objetos tipo "javax.persistence.CascadeType" la cual es una enumeración. Cada elemento de la enumeración representa una operación que será realizada en cascada. Usando las anotaciones de JPA tenemos un número menor de operaciones que con los archivos de mapeo. De hecho solo tenemos:

  • merge
  • persist
  • refresh
  • remove
  • all

Por default ninguna operación se realiza en cascada, pero nosotros queremos que se realicen en cascada las operaciones de guardar y eliminar, por lo que colocamos estos valores de la siguiente forma:

@OneToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
private Direccion direccion;


Además, y para terminar con esta parte, había comentado que Hibernate realizará las operaciones en cascada suponiendo que tanto la Persona como la Direccion tienen el mismo identificador, pero que esto podría no siempre ser verdad (como en el ejemplo que vimos anteriormente). Por lo que debemos indicar que el identificador de Persona debe ser el mismo que el de Direccion.

Para indicar esto con anotaciones se supone que usamos la anotación "@PrimaryKeyJoinColumn" en el elemento del que se tomará el id, que en ese caso también es "direccion", pero en la práctica esto no funciona ^-^!, de hecho hay muchos reportes en el sitio de Hibernate indicando esto. Así que dejamos la anotación de la relación como está. Con esto lograremos que en la tabla en la que se almacene las entidades "Persona" se agregue una columna más para indicar con cuál "Direccion" está relacionada.

Finalmente la relación queda indicada de esta forma:

@OneToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE}) 
private Direccion direccion; 


La clase "Persona" anotada queda de la siguiente forma:

@Entity public class Persona implements Serializable
{ 
    @Id 
    private long id; 

    private String nombre;  

    @OneToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE}) 
    private Direccion direccion;  

    public Persona() 
    { 
    }  

    public Direccion getDireccion() 
    { 
        return direccion; 
    }  

    public void setDireccion(Direccion direccion) 
    { 
        this.direccion = direccion; 
    }  

    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; 
    } 
}


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

<mapping class="hibernate.relacionesanotaciones.modelo.Persona" /> 
<mapping class="hibernate.relacionesanotaciones.modelo.Direccion" />


En esta ocasión usaremos la clase "HibernateUtil" que creamos en el segundo tutorial para trabajar con nuestras entidades.

La clase "Main" tiene el mismo código auto-explicativo de antes:

public static void main(String[] args) 
{ 
    Persona persona1 = new Persona(); 
    persona1.setNombre("Persona que sera borrada");  

    Persona persona2 = new Persona(); 
    persona2.setNombre("Persona que permanecera");  

    Direccion direccion1 = new Direccion(); 
    direccion1.setCalle("Calle 1"); 
    direccion1.setCodigoPostal("12345");  

    Direccion direccion2 = new Direccion(); 
    direccion2.setCalle("Calle 2"); 
    direccion2.setCodigoPostal("54321");  

    persona1.setDireccion(direccion1); 
    persona2.setDireccion(direccion2);  

    Session sesion = HibernateUtil.getSessionFactory().openSession();   

    /*Esta direccion se agrega para comprobar que las personas tomen el mismo 
    identificador que las direcciones*/ 
    Direccion d = new Direccion(); 
    d.setCalle("Calle de Prueba de identificadores"); 
    d.setCodigoPostal("21345");   

    /*En la primer sesion a la base de datos almacenamos los dos objetos Persona 
    los objetos Direccion se almacenaran en cascada*/ 
    sesion.beginTransaction();  

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

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

    /*En la segunda sesion eliminamos el objeto persona1, 
    la direccion1 sera borrada en cascada*/ 
    sesion = HibernateUtil.getSessionFactory().openSession(); 
    sesion.beginTransaction();  

    sesion.delete(persona1);  

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


Nuevamente revisamos que se hayan eliminado la persona1 y direccion1:



Como podemos ver también este ejemplo funciona ^-^. Ahora veremos cómo crear relaciones uno a uno pero en esta ocasión bidireccionales.


2 - Relaciones Uno a Uno Bidireccionales


El crear relaciones uno a uno bidireccionales no es muy distinto a crearlas unidireccionales. La única diferencia es que en este caso ambos lados de la relación están conscientes de la misma. Veamos el ejemplo. Para esto usaremos dos clases: "Pais y "Presidente. Estas clases las pondremos en el paquete "modelo" que creamos anteriormente.

La clase Pais queda así:

public class Pais implements Serializable  
{ 
    private int id;     
    private String nombre; 

    private Presidente presidente;  

    public Pais() 
    { 
    }  

    public int getId() 
    { 
        return id; 
    }  

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

    public String getNombre() 
    { 
        return nombre; 
    }  

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

    public Presidente getPresidente() 
    { 
        return presidente; 
    }  

    public void setPresidente(Presidente presidente) 
    { 
        this.presidente = presidente; 
    } 
}


Vemos que la clase "Pais" tiene una referencia a un objeto de tipo "Presidente" y por lo tanto sabe que existe la relación y puede acceder a la misma.

Ahora veamos cómo queda la clase "Presidente":

public class Presidente implements Serializable 
{ 
    private int id; 
    private String nombre;     

    private Pais pais;  

    public Presidente() 
    { 
    }  

    public int getId() 
    { 
        return id; 
    }  

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

    public String getNombre() 
    { 
        return nombre; 
    }  

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

    public Pais getPais() 
    { 
        return pais; 
    }  

    public void setPais(Pais pais) 
    { 
        this.pais = pais; 
    } 
}


Como podemos ver, "Presidente" también sabe de la relación por lo que tiene una referencia a un objeto "Pais". Ahora veremos cómo realizar el mapeo de las relaciones uno a uno bidireccionales de estas dos clases que, como podremos ver, es igual que para las relaciones unidireccionales. Comencemos usando los archivos de mapeo:


2.1 Relaciones Uno a Uno Bidireccionales con Archivos de Mapeo


Creamos, en el paquete "mapeos" un nuevo documento XML. Le damos de nombre "Pais.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.modelo.Pais" table="Paises"> 
        <id name="id" column="ID"> 
            <generator class="identity" /> 
        </id> 

        <property name="nombre" />        

        <one-to-one name="presidente" cascade="persist,delete"/> 
    </class> 
</hibernate-mapping>




Como podemos ver, el mapeo de "Pais" es prácticamente el mismo que para la relación uno a uno.

Ahora crearemos el mapeo para la clase "Presidente". Creamos, en el paquete "mapeos" un nuevo documento XML. Le damos de nombre "Presidente.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.modelo.Presidente" table="Presidentes">
        <id name="id" column="ID">
            <generator class="foreign">              
             <param name="property">pais</param>         
         </generator>
        </id>
        
        <property name="nombre" />
        
        <one-to-one name="pais" constrained="true" />
        
    </class>
</hibernate-mapping>




Podemos ver que nuevamente usamos la estrategia de colocar "foreign" como generador para el "id" del Presidente, para que sea tomado el mismo que el Pais con el que está asociado. Aquí estoy haciendo una suposición para que eso funcione: No puede existir un Presidente si antes no existe el Pais que gobernará. O sea que la entidad "Pais" debe ser guardado en la base de datos antes, o al menos al mismo tiempo, que el "Presidente".

En esta ocasión en el elemento "<one-to-one>" agregué el elemento "constrained". Esto colocará una restricción, evitando que se almacene el Presidente si antes no existe el Pais (para evitar que tratemos de almacenar un Presidente sin Pais, o de un Pais que no exista).

Eso es todo para mapear las relaciones. No olviden agregar los dos mapeos nuevos al archivo "hibernate.cfg.xml":

<mapping resource="hibernate/relaciones/mapeos/Pais.hbm.xml"/>
<mapping resource="hibernate/relaciones/mapeos/Presidente.hbm.xml"/>


Ahora en nuestra clase "Main" colocamos el siguiente código que, nuevamente, es auto-explicativo:

public static void main(String[] args) 
{
    Pais pais1 = new Pais();
    pais1.setNombre("China");

    Pais pais2 = new Pais();
    pais2.setNombre("Corea");

        
    Presidente presidente1 = new Presidente();
    presidente1.setNombre("Jiang Zemin");
        
    Presidente presidente2 = new Presidente();
    presidente2.setNombre("Kim Dae-Jung");

    pais1.setPresidente(presidente1);
    pais2.setPresidente(presidente2);

    presidente1.setPais(pais1);
    presidente2.setPais(pais2);

    Session sesion = HibernateUtil.getSessionFactory().openSession();


    /*Este pais se agrega para comprobar que los presidentes tomen el mismo
    identificador que los paises*/
    Pais p = new Pais();
    p.setNombre("Chipre");

        
    /*En la primer sesion a la base de datos almacenamos los dos objetos Pais
    los objetos Presidente se almacenaran en cascada*/
    sesion.beginTransaction();

    sesion.persist(p);
    sesion.persist(pais1);
    sesion.persist(pais2);

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


    /*En la segunda sesion eliminamos el objeto pais1,
    el presidente1 sera borrado en cascada*/
    sesion = HibernateUtil.getSessionFactory().openSession();
    sesion.beginTransaction();

    sesion.delete(pais1);

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


En esta ocasión debemos agregar los dos objetos de los dos lados de la relación, es decir, debemos agregar el Pais al Presidente y el Presidente al Pais haciendo:

pais.setPresidente(presidente);
presidente.setPais(pais);


Una forma más elegante sería llamar al método "setPais" del objeto Presidente al momento de establecer su Pais, o sea, el método "setPresidente" de la clase Pais quedaría así:

public void setPresidente(Presidente presidente)
{
    this.presidente = presidente;
    presidente.setPais(this);
}


Así solo tendremos que hacer:

pais.setPresidente(presidente);


Pero es cuestión de gustos.

Ahora pasemos a ver los resultados de las operaciones realizadas por Hibernate. Segun el código de la clase Main, debemos haber guardado 3 Paises y 2 Presidentes y, posteriormente, haber eliminado un Pais (y por lo tanto un Presidente), y quedarnos con 2 Paises y 1 Presidente. Vemos qué es lo que tenemos en la base de datos:



Como podemos ver, una vez más ^-^, todo ha salido bien, por lo que podemos pasar a la última parte de este tutorial:


2.2 Relaciones Uno a Uno Bidireccionales con Anotaciones


Nuevamente las anotaciones que hay que usar son muy simples y similares a las que ya hemos usado, por lo que las colocaré sin mucha explicación. La clase Pais queda así:

@Entity 
public class Pais implements Serializable
{  
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private int id; 

    private String nombre;  

    @OneToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE}) 
    private Presidente presidente;  

    public Pais() 
    { 
    }  

    public int getId() 
    {  
        return id; 
    }  

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

    public String getNombre() 
    {  
        return nombre; 
    }  

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

    public Presidente getPresidente() 
    {  
        return presidente; 
    }  

    public void setPresidente(Presidente presidente) 
    {  
        this.presidente = presidente; 
    } 
}


La anotación que debemos usar es, nuevamente "@OneToOne" y se usa de la misma forma que en el caso de las relaciones unidireccionales.

La clase Presidente queda así:

@Entity 
public class Presidente implements Serializable 
{ 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private int id; 

    private String nombre;  

    @OneToOne  
    private Pais pais;  

    public Presidente() 
    { 
    }  

    public int getId() 
    {  
        return id; 
    }  

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

    public String getNombre() 
    {  
        return nombre; 
    }  

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

    public Pais getPais() 
    {  
        return pais; 
    }  

    public void setPais(Pais pais) 
    {  
        this.pais = pais; 
    }
}


Y listo, esto es todo lo que debemos hacer para tener nuestra aplicación con una relación uno a uno bidireccional ^-^. Ahora probemos que todo funciona correctamente.

No olviden agregar estas dos clases al archivo "hibernate.cfg.xml":

<mapping class="hibernate.relacionesanotaciones.modelo.Pais" /> 
<mapping class="hibernate.relacionesanotaciones.modelo.Presidente" />


Para probar que todo funciona bien, coloquen el siguiente código en la clase "Main":

public static void main(String[] args) 
{  
    Pais pais1 = new Pais();  
    pais1.setNombre("China");   

    Pais pais2 = new Pais();  
    pais2.setNombre("Corea");    

    Presidente presidente1 = new Presidente();  
    presidente1.setNombre("Jiang Zemin");   

    Presidente presidente2 = new Presidente();  
    presidente2.setNombre("Kim Dae-Jung");   

    pais1.setPresidente(presidente1);  
    pais2.setPresidente(presidente2);   

    presidente1.setPais(pais1);  
    presidente2.setPais(pais2);   

    Session sesion = HibernateUtil.getSessionFactory().openSession();    

    /*Este pais se agrega para comprobar que los presidentes tomen el mismo  
    identificador que los paises*/  
    Pais p = new Pais();  
    p.setNombre("Chipre");    

    /*En la primer sesion a la base de datos almacenamos los dos objetos Pais  
    los objetos Presidente se almacenaran en cascada*/  
    sesion.beginTransaction();   
    
    sesion.persist(p);  
    sesion.persist(pais1);  
    sesion.persist(pais2);   

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

    /*En la segunda sesion eliminamos el objeto pais1,  
    el presidente1 sera borrado en cascada*/  

    sesion = HibernateUtil.getSessionFactory().openSession();  
    sesion.beginTransaction();   

    sesion.delete(pais1);   

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


Comprobemos que, en la base de datos, solo haya dos Paises y un Presidente:



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

Espero que todo haya quedado claro, en caso contrario no duden en dejar sus dudas, comentarios y sugerencias.

En los siguientes tutoriales veremos cómo manejar los otros tipos de relaciones que podemos tener usando Hibernate.

Saludos

Descarga los archivos de este tutorial desde aquí:

Entradas Relacionadas:

18 comentarios:

  1. Hola Muchas Gracias por el tutorial esta excelente como siempre....

    solo queria comentar que me paso un problema con la relacion Uno a Uno Unidireccionales dado q al correr el programa con las anotaciones(Metadatos) me daba este problema::

    a different object with the same identifier value was already associated with the sessio

    que era el que explicabas entoces me puse a analizar y para que lo genere tal como tu dices, que genere otro campo con el id_Direccion, que para mi opinion es lo mejor. Se tiene que agregar a Persona, en el ID, la estrategia de solucion. quedando la clase persona asi::

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

    private String nombre;

    @OneToOne(cascade={CascadeType.PERSIST,
    cascadeType.REMOVE})
    private Direccion direccion;

    {Setters and getters}
    }

    Bueno solo comentaba la solucion a mi error...

    Muchas gracias por estos tutoriales......

    ResponderEliminar
  2. Excelente Alex muchas gracias.
    Hola Alex, disculpa una pregunta, hay modo de establecer una relación uno a uno siendo que los id's no sean el mismo, por ejemplo en una relación de este tipo

    *Dirección(Tabla)
    **idDireccion(columna)
    **calle(columna)
    **codigoPostal(columna)

    *Cliente(Tabla)
    **idCliente(columna)
    **nombre(columna)
    **idDireccion(columna)

    donde "idDireccion" de "Cliente" sea una llave foránea que apunte a "idDireccion" de la tabla "Direccion", esto porque yo tengo secuencias de Oracle que generan estos valores y no están sincronizadas....

    Por tu atención, gracias...
    Saludos cordiales

    ResponderEliminar
  3. Hola Alex!!

    una preguntota, en la version de hibernate que yo tengo en el atributo de "cascade=persist,delete" solo puedo poner una opcion y no me aparecen todas las que pones en la lista casi al principio del tutorial, solo puedo escoger una de "save-update,delete,all y none" a lo cual yo trabaje con save-update pero no me hace la insercion automatica del otro objeto con el que esta relacionado uno a uno, tuve que hacerlo con dos instrucciones session.save

    que opinas??

    ResponderEliminar
  4. Hola Juan Carlos;

    Pues en realidad es un poco raro, la listsa de valores de cascade la obtuve de la documentación oficial de Hibernate.

    En cuanto a lo otro debes tener cuidado de en cuál entidad colocar el cascade para que funcione correctamente, no debería ser necesario que uses las dos instrucciones save.

    ¿Puedes entrar un poco mas en detalle de lo que estas haciendo?

    Saludos

    ResponderEliminar
  5. Hola Alex!!!

    ya se por que no me salia la lista de valores que te comentaba!!

    y era que en el archivo de mapeo ponia:




    y era el 3.0.dtd!!!

    ResponderEliminar
  6. Hola Alex soy nuevo en Hibernate, he estado realizando todo y me ha servido muchísimo. En este tutorial cuando intente correr la aplicación me mandaba el mismo error y lo solucione solo indicándole al atributo id que se generara automaticamente.

    @GeneratedValue(strategy=(GenerationType.IDENTITY))
    private long id;

    ResponderEliminar
  7. Hola..! Quiero seguir agradeciendo por este tutorial y el aporte de cada uno, la verdad no hubiera podido hacerlo sola.
    Muchisimas gracias!

    Paula

    ResponderEliminar
  8. Excelente Tutorial !!!!
    Nunca habia visto el tema de hibernate pero gracias al tutorial me quedo todo bien claro, ademas muestra las dos maneras de hacerlo ya sea con XML o Anotaciones...

    Muchas Gracias Alex...

    ResponderEliminar
  9. Primero muchas gracis pot estos tutoriales estan FANTASTICOS solo una pregunta como asegurarias que los paises no se repitan 2 veces? una select antes del insert?

    ResponderEliminar
  10. @Octavio
    Hola Octavio;

    Pues esa podría ser una forma, aunque en realidad me parece que hay maneras más correctas. En este caso usariamos el mecanismo usado en las bases de datos, que es indicar qué valor no puede repetirse (en este caso sería el nombre del país) marcando la columna como unique. En Hibernate esto se hace con el atributo unique de la anotación column:

    @Column(unique=true)

    La cual se coloca en el elemento que no deseas que se repita. Esto también puedes hacerlo con el atributo "uniqueConstraints" de la anotación @Table:

    @Table(name="paises",uniqueConstraints = {@UniqueConstraint(columnNames={"nombre"})})

    En el caso de los archivos de mapeo, se coloca el valor true en el atrubuto "unique" del elemento "property":

    <property name="nombre" unique="true" />


    Con esto, si intentamos agregar dos veces el mismo Pais obtendremos una excepción que deberemos atrapar y manejar :D.

    Espero que esto haya resuelto tu duda ^_^

    Saludos.

    ResponderEliminar
  11. buenas tardes, como los demas foristas me sumo al reconocimiento por la calidad del material publicado. de antemano ofrezco disculpas si la pregunta que hago ya esta resuelta en el tutorial, yo he revisado solamente las 3 primeras partes.
    la pregunta tiene que ver con la manera de manejar en una consulta que involucre tablas de diferentes esquemas de bd, dado que es una situacion muy frecuente, como se hace esto??? Gracias

    ResponderEliminar
  12. Hola javatutoriales, muchas gracias por tu contenido.

    Quería anotar que el primer caso de uno a uno unidireccional con archivos xml me genera en la base de datos derby 10.9.1.0 las tablas separadas, sin relación de llave foranea.

    Al menos con los datos del ejercicio los ID's coinciden pero es como peligroso no tener la restricción de integridad foranea... pero bueno, hay un detalle, en el ejercicio de relación 1 a 1 bidireccional con XML SI me genera la llave foranea desde el ID de la tabla dirección al ID de la tabla persona, que me parece que es como se supone que debe quedar el primer ejercicio (1 a 1 unidireccional con xml).

    Estas relaciones de llaves foraneas desde hacia tablas las veo facilmente con el programa DBVisualizer, en mi caso version 8.0.8 gratuita de forma gráfica ;-)

    Bueno, voy a ver que puedo hacer para generar el modelo de bases de datos de forma mas correcta en Derby (no se si pase lo mismo en otras bases de datos)

    Saludos

    ResponderEliminar
    Respuestas
    1. Ya supe como hacer esto, solo hay que añadir dentro de la etiqueta one-to-one del archivo persona.hbm.xml la instrucción::

      constrained="true"

      Eso logra que se cree la llave foranea desde el ID de dirección hacia el ID de persona.

      Saludos.

      Eliminar
    2. Este comentario ha sido eliminado por el autor.

      Eliminar
    3. Hola Samz, esta respuesta llega con AÑOS de retraso, pero yo sí que pude configurar el uno-a-uno con anotaciones para que se comportara como el de xml, es decir SIN CREAR CLAVE FORÁNEA en Persona:

      Para que el identificador de Persona tome el valor del identificador de Direccion es necesario anotar el identificador de Persona con:

      @GenericGenerator(name="generator", strategy="foreign", parameters={
      @Parameter(name="property", value="direccion")})
      @Id
      @GeneratedValue(generator="generator")
      private long id;

      y anotar la propiedad 'direccion' con:

      @OneToOne(cascade={PERSIST, REMOVE})
      @PrimaryKeyJoinColumn
      private Direccion direccion;

      Se ve que con la versión de Álex la anotación @PrimaryKeyJoinColumn no funcionaba, pero sí funciona en la versión 4 de Hibernate.
      Sé que esto es justo lo contrario que tú pretendías (que era crear una clave foránea en ambos ejemplos) pero de este modo se logra hacerlo sin claves foráneas, sólo con claves primarias iguales, que es de lo que va esta relación.

      Eliminar
  13. pRIMERO QUE TODO MUCHAS GRACIAS POR EL TUTORIAL. Disculpa una pregunta, hay modo de establecer una relación uno a uno siendo que los id's no sean el mismo, por ejemplo en una relación de este tipo

    *Dirección(Tabla)
    **idDireccion(columna)
    **calle(columna)
    **codigoPostal(columna)

    *Cliente(Tabla)
    **idCliente(columna)
    **nombre(columna)
    **idDireccion(columna)

    donde "idDireccion" de "Cliente" sea una llave foránea que apunte a "idDireccion" de la tabla "Direccion".

    ResponderEliminar
  14. este si no lo pude echar andar. utilice el de anotaciones y me sale el siguien error

    INFO org.hibernate.cfg.annotations.EntityBinder - Bind entity hibernate.relaciones.unouno.anotaciones.modelo.Persona on table Persona
    Exception in thread "main" java.lang.NoSuchMethodError: javax.persistence.OneToOne.orphanRemoval()Z

    ResponderEliminar
  15. Saludos de nuevo!! enhorabuena por el tutorial, me está sirviendo sobremanera.
    como en la primera parte me está dando un error y lo pongo aquí por si alguien le ocurre, en los próximos días veré como lo puedo arreglar y postearé la solución.

    Creacion de la Session factory a fallado, HibernateUtil org.hibernate.MappingException: Could not get constructor for org.hibernate.persister.entity.SingleTableEntityPersister
    Exception in thread "main" java.lang.NullPointerException
    at proyecto_apuestas.Modelo.EquipoDAO.guardaEquipo(EquipoDAO.java:32)
    at proyecto_apuestas.Proyecto_Apuestas.main(Proyecto_Apuestas.java:55)

    Clase:

    public class Equipo implements Serializable {


    private long id;
    private String Nombre;
    private long puntos;
    private List Plantilla = new ArrayList();

    public Equipo(){

    }
    public Equipo(String aNom, long aPun, List aJugadores){
    this.Nombre = aNom;
    this.puntos = aPun;
    this.Plantilla = aJugadores;

    }
    public Equipo(String aNom, long aPun){
    this.Nombre = aNom;
    this.puntos = aPun;
    }
    public Equipo(long aId, String aNom, long aPun, List aJugadores){
    this.id=aId;
    this.Nombre = aNom;
    this.puntos = aPun;
    this.Plantilla = aJugadores;

    }
    public Equipo(long aId, String aNom, long aPun){
    this.id=aId;
    this.Nombre = aNom;
    this.puntos = aPun;

    }
    y como véis tengo todos los constructores posibles que se me ocurren xDDD

    ResponderEliminar