Persistencia de Objetos Java: El Camino hacia Hibernate

Ya hemos visto como mapear un nico objeto, ahora vamos a aadir varias asociaciones de objetos. Para empezar aadiremos usuarios a nuestra aplicacin, y almacenaremos una lista de usuarios participantes en cada evento. Adems le daremos a cada usuario la posibilidad de ver varios eventos para su actualizacin. Cada usuario tendr una lista estndar de datos personales, incluyendo una lista de direcciones de e-mail.

.Mapear la clase User

Para empezar, nuestra clase User ser muy simple:

.User.java

package de.gloegl.road2hibernate;

public class User {
    private int age;
    private String firstname;
    private String lastname;
    private Long id;
    
    // ... getters and setters for the properties
    // private getter again for the id property
}

Y el mapeo:

.User.hbm.xml

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

<hibernate-mapping>

        <class name="de.gloegl.road2hibernate.User" table="USERS">
                <id name="id" column="uid" type="long">
                        <generator class="increment"/>
                </id>
                <property name="age"/>
                <property name="firstname"/>
                <property name="lastname"/>
        </class>

</hibernate-mapping>

Necesitamos ajustar el fichero hibernate.cfg.xml para aadir el nuevo recurso:

.hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//hibernate/hibernate Configuration DTD 2.0//EN"

 "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<hibernate-configuration>

    <session-factory>
        <property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
        <property name="hibernate.connection.url">jdbc:hsqldb:data/test</property>
        <property name="hibernate.connection.username">sa</property>
        <property name="hibernate.connection.password"></property>
        <property name="dialect">net.sf.hibernate.dialect.HSQLDialect</property>
        <property name="show_sql">true</property>
        <property name="transaction.factory_class">
             net.sf.hibernate.transaction.JDBCTransactionFactory
        </property>
        <property name="hibernate.cache.provider_class">
             net.sf.hibernate.cache.HashtableCacheProvider
        </property>
        <property name="hibernate.hbm2ddl.auto">update</property>

        <mapping resource="de/gloegl/road2hibernate/Event.hbm.xml"/>
        <mapping resource="de/gloegl/road2hibernate/User.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

.Una Asociacin Unidireccional basada en Set

Hasta ahora slo hemos visto la utilizacin bsica de Hibernate. Pero ahora vamos a aadir la coleccin de Events a la clase Uset. Para esto podemos utilizar una sencilla collection Java -- un Set en este caso, porque la coleccin no contendr elementos duplicados y la ordenacin no es relevante para nosotros.

Nuestra clase User ahora se debera parecer a esto:

.User.java

package de.gloegl.road2hibernate;

import java.util.Set;
import java.util.HashSet;

public class User {
    private int age;
    private String firstname;
    private String lastname;
    private Long id;
    private Set favouriteEvents = new HashSet();
    
    public Set getFavouriteEvents() {
        return favouriteEvents;
    }
    
    public void setFavouriteEvents(Set newFavouriteEvents) {
        favouriteEvents = newFavouriteEvents;
    }
    
    // mappings for the other properties.
}

Ahora tenemos que comunicarle a Hibernate la asociacin, por eso ajustamos el documento de mapeo:

.User.hbm.xml

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

<hibernate-mapping>

        <class name="de.gloegl.road2hibernate.User" table="USERS">
                <id name="id" column="uid" type="long">
                        <generator class="increment"/>
                </id>
                <property name="age"/>
                <property name="firstname"/>
                <property name="lastname"/>
                
                <set name="favouriteEvents" table="favourite_events">
                        <key column="user_uid"/>
                        <many-to-many 
                                column="event_uid"
                                class="de.gloegl.road2hibernate.Event"/>
                </set>
        </class>

</hibernate-mapping>

Como puedes ver, le hemos comunicado a Hibernate la existencia de una propiedad llamada favouriteEvents. El elemento set le dice a Hibernate que la coleccin de propiedades es un Set. Debemos considerar qu clase de asociacin tenemos: cada User podria tener varios Events favoritos, pero cada Event podra ser un favorito para varios Users. Por eso aqu tenemos una relacin muchos-a-muchos, que le decimos a Hibernate utilizando la etiqueta many-to-many. Para este tipo de asociaciones, necesitamos una tabla de asociaciones donde Hibernate pueda almacenarlas. El nombre de la tabla puede configurarse utilizando el atributo table del elemento set. La tabla de asociaciones necesita dos columnas. La columna name para el lado del User se puede configurar utilizando el elemento key. La columna name para el lado del Event se configura utilizando el atributo column del atributo many-to-many.

Por eso el modelo relacional utilizado por Hibernate ahora se parece a esto:

    _____________        __________________        _____________   
   |             |      |                  |      |             |  
   |   EVENTS    |      | FAVOURITE_EVENTS |      |    USERS    |  
   |_____________|      |__________________|      |_____________|  
   |             |      |                  |      |             |  
   | *UID        | <--> | *EVENT_UID       |      |             |  
   |  DATE       |      | *USER_UID        | <--> | *UID        |  
   |  EVENTTITLE |      |__________________|      |  AGE        |  
   |_____________|                                |  FIRSTNAME  |  
                                                  |  LASTNAME   |  
                                                  |_____________|  
 

.Modificar la Asociacin

Como ya hemos mapeado la asociacin, modificarla es muy sencillo:

.en EventManager.java:

private void addFavouriteEvent(Long userId, Long eventId) {
    try {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        
        User user = (User) session.load(User.class, userId);
        Event theEvent = (Event) session.load(Event.class, eventId);
        
        user.getFavouriteEvents().add(theEvent);
        
        tx.commit();
        session.close();
    } catch (HibernateException e) {
        throw new RuntimeException(e);
    }
}

Despus de cargar un User y un Event con Hibernate, podemos simplemente modificar la coleccin utilizando los mtodos normales. Como puedes ver, no hay una llamada explcita a session.update() o session.save(), Hibernate automticamente detecta que se ha modificado la coleccin y que necesita ser grabada.

Sin embargo, algunas veces, tendremos a un User o a un Event cargados en una sesin diferente. Por supuesto que esto es posible para:

.en EventManager.java:

private void addFavouriteEvent(Long userId, Long eventId) {
    try {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        
        User user = (User) session.load(User.class, userId);
        Event theEvent = (Event) session.load(Event.class, eventId);
        
        tx.commit();
        session.close();
        
        session = sessionFactory.openSession();
        tx = session.beginTransaction();
        
        user.getFavouriteEvents().add(theEvent);
        
        session.update(user);
        
        tx.commit();
        session.close();
    } catch (HibernateException e) {
        throw new RuntimeException(e);
    }
}

Esta vez, necesitamos llamar a update explcitamente -- Hibernate no puede saber si el objeto ha cambiado realmente ya que se carg en la sesin anterior. Por eso, si tenemos un objeto de una sesin anterior, debemos actualizarlo explcitamente. Si el objeto cambia durante el ciclo de vida de la sesin podemos dejar que Hibernate lo chequee automticamente.

Desde Hibernate 2.1 hay una tercera forma -- el objeto se puede reasociar con la nueva sesin utilizando session.lock(object, LockMode.NONE)

.en EventManager.java:

private void addFavouriteEvent(Long userId, Long eventId) {
    try {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        
        User user = (User) session.load(User.class, userId);
        Event theEvent = (Event) session.load(Event.class, eventId);
        
        tx.commit();
        session.close();
        
        session = sessionFactory.openSession();
        tx = session.beginTransaction();
        
        session.lock(user, LockMode.NONE);    
        
        user.getFavouriteEvents().add(theEvent);
        
        tx.commit();
        session.close();
    } catch (HibernateException e) {
        throw new RuntimeException(e);
    }
}

.Colecciones de Valores

Frecuentemene querrs mapear colecciones de valores de tipos simples -- como colecciones de Integers o de Strings. Haremos esto para nuestra clase User con una coleccin de Strings representando una lista de direcciones de e-mail. Aadiremos otro Set a nuestra clase:

.User.java:

package de.gloegl.road2hibernate;

import java.util.Set;
import java.util.HashSet;

public class User {
    private int age;
    private String firstname;
    private String lastname;
    private Long id;
    private Set favouriteEvents = new HashSet();
    private Set emails = new HashSet();
    
    public Set getEmails() {
        return emails;
    }
    
    public void setEmails(Set newEmails) {
        emails = newEmails;
    }
    
    // Other getters and setters ...
}

Luego aadiremos el mapeo del Set al documento de mapeo:

.User.hbm.xml

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

<hibernate-mapping>

        <class name="de.gloegl.road2hibernate.User" table="USERS">
                <id name="id" column="uid" type="long">
                        <generator class="increment"/>
                </id>
                <property name="age"/>
                <property name="firstname"/>
                <property name="lastname"/>
                
                <set name="favouriteEvents" table="favourite_events">
                    <key column="user_uid"/>
                    <many-to-many 
                        column="event_uid" 
                        class="de.gloegl.road2hibernate.Event"/>
                </set>
        
                <set name="emails" table="user_emails">
                    <key column="user_uid"/>
                    <element column="email" type="string"/>
                </set>
        </class>

</hibernate-mapping>

Como puedes ver, el nuevo mapeo de Set se parece al anterior. La diferencia es la parte element que le dice a Hibernate que la coleccin no contiene una asociacin con una clase mapeada, sino una coleccin de elementos del tipo String. De nuevo, el atributo table del elemento set determina el nombre de la tabla. El elemento key determina el nombre de la columna en la tabla user_emails que establece la relacin con la tabla USERS. El atributo column del elemento element determina el nombre de la columna donde realmente se almacenarn los valores String.

Ahora nuestro modelo relacional se parecer a ste:

    _____________        __________________        _____________        _____________   
   |             |      |                  |      |             |      |             |  
   |   EVENTS    |      | FAVOURITE_EVENTS |      |    USERS    |      | USER_EMAILS |  
   |_____________|      |__________________|      |_____________|      |_____________|  
   |             |      |                  |      |             |      |             |  
   | *UID        | <--> | *EVENT_UID       |      |             |      | *ID         |  
   |  DATE       |      | *USER_UID        | <--> | *UID        | <--> |  USER_UID   |  
   |  EVENTTITLE |      |__________________|      |  AGE        |      |  EMAIL      |  
   |_____________|                                |  FIRSTNAME  |      |_____________|  
                                                  |  LASTNAME   |                       
                                                  |_____________|                       

.Utilizar Colecciones de Valores

Usar las colecciones de valores funciona de la misma forma que ya hemos visto:

.en EventManager.java:

private void addEmail(Long userId, String email) {
    try {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        
        User user = (User) session.load(User.class, userId);
        
        user.getEmails().add(email);
        
        tx.commit();
        session.close();        
    } catch (HibernateException e) {
        throw new RuntimeException(e);
    }
}

Como puedes ver, podemos utilizar la coleccin mapeada igual que cualquier otra coleccin Java. La deteccin automtica de Hibernate har el resto del trabajo. Para objetos de otras sesiones -- u objetos desconectados, como los llamaremos a partir de ahora -- se aplica lo que hemos visto antes. Actualizarlos explcitamente, o reasociarlos antes de actualizarlos utilizando session.lock(object, LockMode.NONE).

.Asociaciones Bidireccionales utilizando Sets

Ahora vamos a mapear una asociacin bidireccional -- la clase User contendr una lista de eventos donde el usuario participa, y la clase Event contendr una lista de usuarios participantes. Primero ajustamos nuestras clases.

.User.java:

package de.gloegl.road2hibernate;

import java.util.Set;
import java.util.HashSet;

public class User {
    private int age;
    private String firstname;
    private String lastname;
    private Long id;
    private Set favouriteEvents = new HashSet();
    private Set emails = new HashSet();
    private Set eventsJoined = new HashSet();
    
    public Set getEventsJoined() {
        return eventsJoined;
    }
    
    public void setEventsJoined(Set newEventsJoined) {
        eventsJoined = newEventsJoined;
    }

    // Other getters and setters ...
}

.Event.java:

package de.gloegl.road2hibernate;

import java.util.Date;
import java.util.Set;
import java.util.HashSet;

public class Event {
    private String title;
    private Date date;
    private Long id;
    private Set participatingUsers = new HashSet();
    
    private Set getParticipatingUsers() {
        return participatingUsers;
    }
    
    private void setParticipatingUsers(Set newParticipatingUsers) {
        participatingUsers = newParticipatingUsers;
    }

    // Other getters and setters ...
}

El mapeo para una asociacin bidireccional se parece mucho a los mapeos unidirecionales, excepto en que los elementos set se mapean para las dos clases:

.Event.hbm.xml:

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

<hibernate-mapping>

        <class name="de.gloegl.road2hibernate.Event" table="EVENTS">
                <id name="id" column="uid" type="long">
                        <generator class="increment"/>
                </id>
                <property name="date" type="timestamp"/>
                <property name="title" column="eventtitle"/>
                
        <set name="participatingUsers" table="participations">
            <key column="event_uid"/>
            <many-to-many column="user_uid" class="de.gloegl.road2hibernate.User"/>
        </set>
        </class>

</hibernate-mapping>

.User.hbm.xml:

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

<hibernate-mapping>

        <class name="de.gloegl.road2hibernate.User" table="USERS">
                <id name="id" column="uid" type="long">
                        <generator class="increment"/>
                </id>
                <property name="age"/>
                <property name="firstname"/>
                <property name="lastname"/>
                
        <set name="favouriteEvents" table="favourite_events">
            <key column="user_uid"/>
            <many-to-many column="event_uid" class="de.gloegl.road2hibernate.Event"/>
        </set>
        
        <set name="emails" table="user_emails">
            <key column="user_uid"/>
            <element column="email" type="string"/>
        </set>
        
        <set name="eventsJoined" table="participations" inverse="true">
            <key column="user_uid"/>
            <many-to-many column="event_uid" class="de.gloegl.road2hibernate.Event"/>
        </set>
        </class>

</hibernate-mapping>

Como puedes ver, hay mapeos set normales en ambos documentos de mapeo. Observa que los nombres de columnas en key y many-to-many estn intercambiados en ambos documentos. La adiccin ms importantes es el atributo inverse="true" del elemento set del mapeo de User.

Esto significa que es el otro lado -- la clase Event -- el que manejar la relacin. Por eso cuando slo se modifique el Set en la clase User, no se persistir. Tambin cuando se utilicen actualizaciones explcitas para objetos desconectados, necesitaremos actualizar el que no est marcado como inverse. Veamos un ejemplo:

.Usar Mapeos Bidireccionales

Primero de todo es importante saber que seguimos siendo los responsables de mantener nuestras asociaciones de forma adecuada en el lado Java -- esto significa que si aadimos un Event al Set eventsJoined de un objeto User, tambin tenemos que aadir este objeto User al Set participatingUsers del objeto Event. Por eso aadimos algunos mtodos de coveniencia a la clase Event:

.Event.java:

package de.gloegl.road2hibernate;

import java.util.Date;
import java.util.Set;
import java.util.HashSet;

public class Event {
    private String title;
    private Date date;
    private Long id;
    private Set participatingUsers = new HashSet();
    
    protected Set getParticipatingUsers() {
        return participatingUsers;
    }
    
    protected void setParticipatingUsers(Set newParticipatingUsers) {
        participatingUsers = newParticipatingUsers;
    }
    
    public void addParticipant(User user) {
        participatingUsers.add(user);
        user.getEventsJoined().add(this);
    }
    
    public void removeParticipant(User user) {
        participatingUsers.remove(user);
        user.getEventsJoined().remove(this);
    }

     // Other getters and setters ...
}

Observa que los mtodos get y set para participatingUsers ahora son protected - esto permite a las clases del mismo paquete y sus subclases acceder a estos mtodos, pero evita que nadie ms juegue con la coleccin directamente. Deberamos hacer lo mismo con getEventsJoined() y setEventsJoined() en la clase User.

Ahora usar la asociacin es muy fcil:

.en EventManager.java:

private void addParticipant(Long userId, Long eventId) {
    try {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        
        User user = (User) session.load(User.class, userId);
        Event theEvent = (Event) session.load(Event.class, eventId);
        
        theEvent.addParticipant(user);        
        
        tx.commit();
        session.close();        
    } catch (HibernateException e) {
        throw new RuntimeException(e);
    }
}

COMPARTE ESTE ARTÍCULO

ENVIAR A UN AMIGO
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN GOOGLE +
¡SÉ EL PRIMERO EN COMENTAR!
Conéctate o Regístrate para dejar tu comentario.