Ejemplo de asociacion many-to-many bidireccional con Hibernate

De ChuWiki

Introducción[editar]

Este es el cuarto artículo de esta serie de ejemplos sencillos con Hibernate. Suponemos que ya los has leído y vamos aquí a modificar el Ejemplo sencillo con asociaciones en Hibernate para modificar la relación many-to-many entre dos tablas, que en aquel ejemplo era unidireccional, para que en este ejemplo sea bidireccional. En este enlace tienes todos los fuentes de este ejemplo con Hibernate.


Modificaciones en Person y Event[editar]

En el Ejemplo sencillo con asociaciones en Hibernate teníamos una clase Person con un atributo que era un Set de Event, es decir, una persona podía participar en varios eventos. La clase Event, sin embargo, no tenía nada que la relacionara con una persona. Lo primero que vamos a hacer es añadir a la clase Event un conjunto de Person.

...
public class Event {
   ...
    private Set participants = new HashSet();

    public Set getParticipants() {
        return participants;
    }

    public void setParticipants(Set participants) {
        this.participants = participants;
    }

   ...
}

Por otro lado, al ser ahora la relación bidireccional, cuando a una persona le añadimos un evento, al evento hay que añadirle la persona. Para evitar dejar esto en la mente del que usa el código, modificaremos la clase Person para que se encargue de hacer esta relación bidireccional, añadiendo un par de métodos addToEvent() y removeFromEvent(). Los métodos setEvents() y getEvents() los haremos protegidos para que nadie pueda modificar libremente el conjunto de eventos. En la clase Event podríamos hacer algo parecido.

...
public class Person {
    ...
    /**
     * Se hace este metodo protegido, para que nadie pueda cambiar el conjunto
     * de eventos sin control
     * 
     * @return
     */
    protected Set getEvents() {
        return events;
    }

    /**
     * Se hace este metodo protegido, para que nadie pueda cambiar el conjunto
     * de eventos sin control
     * 
     * @param events
     */
    protected void setEvents(Set events) {
        this.events = events;
    }

    /**
     * Al anadir un evento y ser la asociacion bidireccional, hay que asociar el
     * evento a esta persona y luego asociar esta persona al evento.
     * 
     * @param event
     */
    public void addToEvent(Event event) {
        this.getEvents().add(event);
        event.getParticipants().add(this);
    }

    /**
     * Al borrar un evento y ser la asocacion bidireccional, hay que eliminar el
     * evento de esta persona y luego eliminar esta persona del evento.
     * 
     * @param event
     */
    public void removeFromEvent(Event event) {
        this.getEvents().remove(event);
        event.getParticipants().remove(this);
    }

    ...
}


Fichero de mapeo de Hibernate para Event[editar]

El fichero Person.hbm.xml permanece igual que en el Ejemplo sencillo con asociaciones en Hibernate, ya que no ha cambiado ningún atributo en la clase Person. Sin embargo, para el fichero de mapeo Event.hbm.xml debemos añadir el nuevo atributo participants. El fichero Event.hbm.xml puede quedar así

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="com.chuidiang.ejemplos.hibernate.ejemplo4.Event">
		<id name="id" column="EVENT_ID">
			<generator class="native"></generator>
		</id>
		<property name="date" type="timestamp" column="EVENT_DATE" />
		<property name="title"></property>
		<!--  inverse="true" hace que hibernate ignore esta asociacion
		      y busque la relacion en la parte de Person -->
		<set name="participants" table="PERSON_EVENT" inverse="true">
			<key column="EVENT_ID" />
			<many-to-many column="PERSON_ID"
				class="com.chuidiang.ejemplos.hibernate.ejemplo4.Person" />
		</set>
	</class>
</hibernate-mapping>

Vemos que la asociación <set name="participants".../> es igual que la del Ejemplo sencillo con asociaciones en Hibernate para la clase Person, salvo por un pequeño detalle: el atributo inverse="true". Al estar esta relación mapeada dos veces, una en el fichero Person.hbm.xml y otra aquí, debemos poner inverse="true" en una de ellas, de forma que Hibernate ignorará esta, ya que la relación está físicamente mapeada en base de datos con la otra, la de la clase Person. Cualquier relación bidireccional necesita que uno de los lados sea inverse="true". Al ser la relación many-to-many, podemos elegir cualquiera de los dos lados para el inverse="true". Si la relación fuese one-to-many, debemos poner el inverse="true" en la parte del many.


El código de ejemplo[editar]

En el código de Ejemplo4.java simplemente crearemos una persona, un evento, añadiremos el evento a la persona y luego haremos un listado de personas con sus eventos asociados, así como de eventos con sus personas asociadas. Puesto que los métodos son como en ejemplos anteriores, sólo nos fijaremos en el de la asociación

...
public class Ejemplo4 {
   ...
    private void addPersonToEvent(Long personId, Long eventId) {
        // Sesion
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();

        // Se cargan perons y eventos
        Person aPerson = (Person) session.load(Person.class, personId);
        Event anEvent = (Event) session.load(Event.class, eventId);

        // Se añade el evento a la persona
        aPerson.addToEvent(anEvent);

        // Se termina la transaccion
        session.getTransaction().commit();
    }

   ...
}

Una vez cargados Person y Event de base de datos, una llamada a aPerson.addToEvent() basta para asociarlos y este método, como hemos visto, se encarga también de añadir aPerson en el anEvent. Finalmente session.getTransaction().commit() se encarga de hacer el update en la base de datos.