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