Programación en castellano
Inicio > Tutoriales > Lenguajes orientados a objeto > J2EE > Persistencia de Objetos Java: El Camino hacia Hibernate
-Tutoriales

Persistencia de Objetos Java: El Camino hacia Hibernate


Mapear Asociaciones

Ya hemos visto como mapear un único objeto, ahora vamos a añadir varias asociaciones de objetos. Para empezar añadiremos usuarios a nuestra aplicación, y almacenaremos una lista de usuarios participantes en cada evento. Además le daremos a cada usuario la posibilidad de ver varios eventos para su actualización. Cada usuario tendrá una lista estándar 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 añadir 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 Asociación Unidireccional basada en Set

Hasta ahora sólo hemos visto la utilización básica de Hibernate. Pero ahora vamos a añadir la colección de Events a la clase Uset. Para esto podemos utilizar una sencilla collection Java -- un Set en este caso, porque la colección no contendrá elementos duplicados y la ordenación no es relevante para nosotros.

Nuestra clase User ahora se debería 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 asociación, 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 colección de propiedades es un Set. Debemos considerar qué clase de asociación tenemos: cada User podria tener varios Events favoritos, pero cada Event podría ser un favorito para varios Users. Por eso aquí tenemos una relación 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 Asociación

Como ya hemos mapeado la asociación, 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);
    }
}

Después de cargar un User y un Event con Hibernate, podemos simplemente modificar la colección utilizando los métodos normales. Como puedes ver, no hay una llamada explícita a session.update() o session.save(), Hibernate automáticamente detecta que se ha modificado la colección y que necesita ser grabada.

Sin embargo, algunas veces, tendremos a un User o a un Event cargados en una sesión 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 explícitamente -- Hibernate no puede saber si el objeto ha cambiado realmente ya que se cargó en la sesión anterior. Por eso, si tenemos un objeto de una sesión anterior, debemos actualizarlo explícitamente. Si el objeto cambia durante el ciclo de vida de la sesión podemos dejar que Hibernate lo chequee automáticamente.

Desde Hibernate 2.1 hay una tercera forma -- el objeto se puede reasociar con la nueva sesión 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 querrás mapear colecciones de valores de tipos simples -- como colecciones de Integers o de Strings. Haremos esto para nuestra clase User con una colección de Strings representando una lista de direcciones de e-mail. Añadiremos 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 añadiremos 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 colección no contiene una asociación con una clase mapeada, sino una colección 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 relación con la tabla USERS. El atributo column del elemento element determina el nombre de la columna donde realmente se almacenarán 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 colección mapeada igual que cualquier otra colección Java. La detección automática 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 explícitamente, o reasociarlos antes de actualizarlos utilizando session.lock(object, LockMode.NONE).

. Asociaciones Bidireccionales utilizando Sets

Ahora vamos a mapear una asociación 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 asociación 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 están intercambiados en ambos documentos. La adicción más 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 relación. Por eso cuando sólo se modifique el Set en la clase User, no se persistirá. También cuando se utilicen actualizaciones explícitas 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 añadimos un Event al Set eventsJoined de un objeto User, también tenemos que añadir este objeto User al Set participatingUsers del objeto Event. Por eso añadimos algunos métodos 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 métodos get y set para participatingUsers ahora son protected - esto permite a las clases del mismo paquete y sus subclases acceder a estos métodos, pero evita que nadie más juegue con la colección directamente. Deberíamos hacer lo mismo con getEventsJoined() y setEventsJoined() en la clase User.

Ahora usar la asociación es muy fácil:

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

 
Patrocinados
 

Copyright © 1999-2007 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad
Mantenida por: Claudio y Dani.

Hospedaje web y servidores dedicados linux por Ferca Network

red internet: jugar gratis | amor | navidad 2009 | registro de dominios | servidores dedicados
más internet: comprar | gratis | posicionamiento en buscadores | decoración libre | gifs animados