Hibernate.orgCommunity Documentation

Tutorial basico de Hibernate

Traduccion a Español

3.3.2.GA

Este capitulo, pensado para nuevos usuarios, ofrece una introduccion paso a paso a Hibernate, comenzando con una sencilla aplicacion que usa una base de datos en memoria. El tutorial esta basado en un tutorial anterior desarrollado por Michal Gloegl. Todo el codigo fuente puedes encontrarlo en el directorio project/tutorials/web de tu distribucion de Hibernate.

Importante

Este tutorial asume que el usuario tiene conocimientos tanto de Java como de SQL. Si dispones de un conocimiento limitado de Java y SQL es aconsejable que comiences con una buena introduccion a estas tecnologias como paso previo a intentar aprender Hibernate.

Nota

La distribucion de Hibernate contiene otra aplicacion de ejemplo bajo el directorio de proyectos tutorial/eg .

Para este ejemplo, vamos a poner en marcha una pequeña aplicacion de base de datos que puede almacenar eventos que queremos atender, asi como informacion sobre el origen (host) de dichos eventos.

Nota

Aunque puedes usar cualquier base de datos con la que te sientas comodo, nosotros vamos a usar HSQLDB (una base de datos en memoria escrita en Java) para evitar describir la instralacion y puesta en marcha de un servidor de bases de datos en concreto.

Lo primero que necesitamos hacer es poner en marcha el entorno de desarrollo. Usaremos el "esquema estandar" defendido por un monton de herramientas de construccion como Maven. Maven, en particular, dispone de un buen recurso web describiendo este esquema. Como este tutorial es una aplicacion web, crearemos y haremos uso de los directorios src/main/java , src/main/resources y src/main/webapp .

Usaremos Maven en este tutorial, tomando ventaja de sus capacidades de gestion de dependencias asi como la habilidad de muchos IDE's de configurar automaticamente un proyecto basandose en el descriptor Maven.

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>org.hibernate.tutorials</groupId>
    <artifactId>hibernate-tutorial</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>First Hibernate Tutorial</name>

    <build>
         <!-- we dont want the version to be part of the generated war file name -->
         <finalName>${artifactId}</finalName>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
        </dependency>

        <!-- Because this is a web app, we also have a dependency on the servlet api. -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
        </dependency>

        <!-- Hibernate uses slf4j for logging, for our purposes here use the simple backend -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
        </dependency>

        <!-- Hibernate gives you a choice of bytecode providers between cglib and javassist -->
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
        </dependency>
    </dependencies>

</project>

Consejo

Usar Maven no es obligatorio. Si deseas usar cualquier otra herramienta para construir este tutorial (como Ant) el esquema seguira siendo el mismo. El unico cambio es que necesitaras manipular manualmente todas las dependencias requeridas. Si utilizas una herramienta como Ivy ofreciendo mantenimiento transitivo de dependencias, seguiras necesitando las dependencias mencionadas debajo. De cualquier otra manera necesitaras tener todas las dependencias, tanto explicitas como transitivas, y añadirlas al classpath del proyecto. Si estas trabajando con el paquete de distribucion de Hibernate, estas dependencias son hibernate3.jar , todos los archivos del directorio lib/required asi como todos los archivos de los directorios lib/bytecode/cglib o lib/bytecode/javassist ; de manera adicional, necesitaras tanto el archivo jar del servlet API como una de las implementaciones del API para logging slf4j.

Guarda este archivo como pom.xml en el directorio principal del proyecto.

A continuacion creamos una clase que representa el evento que queremos almacenar en la base de datos; es una sencilla clase JavaBean con algunas propiedades:

							package org.hibernate.tutorial.domain;

import java.util.Date;

public class Event {
    private Long id;

    private String title;
    private Date date;

    public Event() {}

    public Long getId() {
        return id;
    }

    private void setId(Long id) {
        this.id = id;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
						

La clase usa nombres estandar JavaBean para los metodos getter y setter de cada propiedad, asi como visibilidad privada para las variables. Aunque este es el diseño recomendado no es obligatorio, ya que Hibernate puede acceder a las variables directamente. Sin embargo, el uso de metodos de acceso es beneficioso por su robustez a la hora de refactorizar.

La propiedad id almacena un valor que identifica de manera unica cada evento. Todas las clases que representan entidades persistentes (asi como otras clases dependientes menos importantes) necesitaran dicha propiedad identificativa si queremos hacer uso de todo el conjunto de caracteristicas de Hibernate. De hecho, muchas aplicaciones, especialmente aplicaciones web, necesitan distinguir ciertos objetos por su identidad, por lo que debes considerarlo como una caracteristica en lugar de como una limitacion. Sin embargo, normalmente no manipularemos la identidad de un objeto , por lo que su metodo setter debe ser privado. Solo Hibernate asignara la identidad cuando un objeto sea almacenado en la base de datos. Hibernate puede acceder tanto metodos de acceso de tipo publico, privado y protegido, como directamente las variables tambien de tipo publico, privado y protegido. Esta eleccion es tuya y puedes elegir la que mejor se ajuste al diseño de tu aplicacion.

El constructor sin argumentos es necesario para todas las clases persistentes; Hibernate tiene que crear objetos por ti, usando reflexion. El constructor puede ser privado, aunque se requiere que tenga visibilidad publica o de paquete para la generacion de proxys en tiempo de ejecucion, y para recuper datos de forma eficiente sin manipular del codigo de bytes.

Guarda este archivo en el directorio src/main/java/org/hibernate/tutorial/domain.

Hibernate necesita saber como leer y almacenar objetos de una clase persistente. Aqui es donde el archivo de mapeo de Hibernate entra en juego. El archivo de mapeo indica a Hibernate que tabla en la base de datos tiene que ser accedida, y que columnas en dicha tabla deben usarse.

La estructura basica de un archivo de mapeo se parece a esto:

							<?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 package="org.hibernate.tutorial.domain">
[...]
</hibernate-mapping>
						

El DTD de Hibernate es sofisticado. Puedes usarlo para tareas de auto-completado de elementos y atributos XML en tu editor o IDE. Abrir el archivo DTD en tu editor de texto es la manera mas facil de tener una vision general de todos los elementos y atributos, de ver los valores por defecto, asi como ver algunos comentarios. Hibernate no carga el archivo DTD desde la web, si no que primero lo busca en el classpath de la aplicacion. El archivo DTD esta incluido en el archivo hibernate-core.jar (tambien esta incluido en el archivo hibernate3.jar si estas usando el paquete de distribucion).

Entre las dos etiquetas hibernate-mapping incluye un elemento class. Todas las clases de entidades persistentes (de nuevo, podrian haber clases dependientes, las cuales veremos despues, que no son entidades de primera clase) necesitan un mapeo a una tabla en la base de datos SQL:

							<hibernate-mapping package="org.hibernate.tutorial.domain">

    <class name="Event" table="EVENTS">

    </class>

</hibernate-mapping>
						

Hasta ahora hemos indicado a Hibernate como persistir/leer objetos de la clase Event en/desde la tabla EVENTS. Cada instancia esta ahora representada por una fila en dicha tabla. Ahora podemos continuar mapeando la propiedad que representa la identidad de cada instancia. Como no queremos preocuparnos del manejo de este identificador, configuramos una estrategia de generacion de identidad en una columna de claves primarias:

							<hibernate-mapping package="org.hibernate.tutorial.domain">

    <class name="Event" table="EVENTS">
        <id name="id" column="EVENT_ID">
            <generator class="native"/>
        </id>
    </class>

</hibernate-mapping>
						

El elemento id contiene la declaracion de la propiedad de identidad. El atributo de mapeo name="id" declara el nombre de la propiedad JavaBean e informa a Hibernate que debe usar los metodos getId() y setId() para acceder a ella. El atributo column informa a Hibernate que columna de la tabla EVENTS contiene el valor de la clave primaria.

El elemento anidado generator especifica la estrategia de generacion de identidad (¿como son generados los valores de identidad?). En este caso hemos elegido native, que ofrece un nivel de portabilidad dependiendo del dialecto de base de datos configurado. Hibernate soporta que la identidad sea generada por la base de datos, globalmente unica, asi como asignada por la propia aplicacion. La generacion del valor de identidad es ademas una de las muchas extensiones de Hibernate, permitiendote acoplar tu propia estrategia.

Finalmente, necesitamos indicar a Hibernate como mapear las propiedades restantes de la entidad. Por defecto, ninguna propiedad de la entidad es considerada persistente:

<hibernate-mapping package="org.hibernate.tutorial.domain">

    <class name="Event" table="EVENTS">
        <id name="id" column="EVENT_ID">
            <generator class="native"/>
        </id>
        <property name="date" type="timestamp" column="EVENT_DATE"/>
        <property name="title"/>
    </class>

</hibernate-mapping>

De manera similar al elemento id , el atributo name del elemento property informa a Hibernate que metodo setter usar. En este caso, Hibernate buscara los metodos getDate() , setDate() , getTitle() y setTitle() .

Nota

¿Por que el mapeo de la propiedad date incluye el atributo column pero no lo incluye el mapeo de title ? Sin el atributo column , Hibernate usa por defecto el nombre de la propiedad como nombre para la columna. Esto funciona para title , sin embargo date es una palabra reservada en muchas bases de datos por lo que necesitas mapear la propiedad date con un nombre diferente.

El mapeo de title tambien carece del atributo type . Los tipos de datos declarados y usados en los archivos de mapeo no son tipos de datos Java; tampoco son tipos de base de datos SQL. Son tipos llamados tipos de datos Hibernate , conversores que pueden trasladar desde tipos de datos Java a SQL y viceversa. De nuevo, Hibernate intentara determinar la conversion correcta y el tipo de datos por el mismo si el atributo type no se encuentra presente. En algunos casos esta deteccion automatica, que es determinada mediante reflexion, puede no resultar en el tipo que esperabas o necesitabas. Este es el caso con la propiedad date . Hibernate no puede conocer si la propiedad, que es del tipo java.util.Date , debe ser mapeada a una columna SQL del tipo date , timestamp , o time . La informacion completa de fecha y hora es preservada mapeando la propiedad con un convertidor timestamp .

Consejo

Hibernate determina el tipo de datos a usar mediante reflexion en el momento en que los archivos de mapeo son procesados. Esto puede tomar tiempo y recursos, por lo que si el rendimiento en el arranque es importante deberias considerar definir explicitamente todos los tipos a usar.

Guarda este archivo como src/main/resources/org/hibernate/tutorial/domain/Event.hbm.xml .

En este momento, deberias tener la clase persistente y su archivo de mapeo. Es el momento de configurar Hibernate. Primero, configuremos HSQLDB para funcionar en "modo servidor" (server mode)

Utilizaremos el plugin 'exec' de Maven para arrancar el servidor HSQLDB ejecutando: mvn exec:java -Dexec.mainClass="org.hsqldb.Server" -Dexec.args="-database.0 file:target/data/tutorial" Veras como se inicia y asocia a un socket TCP/IP; alli es donde nuestra aplicacion conectara mas tarde. Si quieres comenzar el tutorial con una base de datos limpia, deten HSQLDB, elimina todos los archivos en el directorio target/data y arranca de nuevo HSQLDB.

Hibernate conectara con la base de datos en nombre de tu aplicacion, por lo que necesita conocer como obtener conexiones. Para este tutorial usaremos un pool de conexiones (de manera opuesta a usar javax.sql.DataSource). HIbernate viene con soporte para dos pool de conexiones JDBC de terceros: c3p0 y proxool.Sin embargo, usaremos el pool de conexiones integrado en Hibernate.

Atencion

El pool de conexiones integrado en Hibernate no esta pensado de ninguna manera para su uso en produccion, ya que carece de diversas caracteristicas disponibles en cualquier pool de conexiones decente.

Para la configuracion de Hibernate, podemos usar un sencillo archivo hibernate.properties , un mas sofisticado archivo hibernate.cfg.xml , o incluso una configuracion programatica completa. Muchos usuarios prefieren el archivo de configuracion XML:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
        <property name="connection.url">jdbc:hsqldb:hsql://localhost</property>
        <property name="connection.username">sa</property>
        <property name="connection.password"></property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.HSQLDialect</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">update</property>

        <mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

Nota

Observa que este archivo de configuracion define un DTD diferente

Has configurado un SessionFactory . SessionFactory es una factoria global reponsable de una base de datos en particular. Si dispones de varias bases de datos, deberias usar varias configuraciones <session-factory> en varios archivos de configuracion para simplificar el arranque.

Los cuatro primeros elementos property contienen la configuracion necesaria para la conexion JDBC. El elemento contiene la configuracion necesaria para la conexion JDBC. El elemento property que hace referencia a 'dialect' especifica que variante de SQL tiene que generar Hibernate.

Consejo

En muchos casos, Hibernate es capaz de determinar correctamente el dialecto que debe usar.

El mantenimiento automatico de sesiones para contextos de persistencia de Hibernate es particularmente util en este contexto. La opcion hbm2ddl.auto activa la generacion automatica de esquemas en la base de datos. Esto puede ser desactivado eliminando la citada opcion de de configuracion, o redireccionado a un archivo con la ayuda de la tarea de Ant SchemaExport . Finalmente, añade el/los archivo(s) de mapeo para clases persistentes a la configuracion.

Guarda este archivo como hibernate.cfg.xml dentro del directorio src/main/resources .

Ahora construiremos el tutorial con Maven. Por supuesto, necesitaras tener Maven instalado; esta disponible desde la pagina de descargas de Maven. Maven leera el archivo /pom.xml que hemos creado antes y sabra como realizar algunas tareas basicas del proyecto. Primero, ejecutemos la opcion compile para asegurarnos que podemos compilar todo lo hecho hasta ahora:

[hibernateTutorial]$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building First Hibernate Tutorial
[INFO]    task-segment: [compile]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to /home/steve/projects/sandbox/hibernateTutorial/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Tue Jun 09 12:25:25 CDT 2009
[INFO] Final Memory: 5M/547M
[INFO] ------------------------------------------------------------------------

Es el momento de leer y almacenar algunos objetos Event, pero primero tienes que completar la puesta en marcha con algo de codigo de infraestructura. Tienes que iniciar Hibernate construyendo un objeto global org.hibernate.SessionFactory y almacenandolo en un lugar que sea de facil acceso para el codigo de la aplicacion. org.hibernate.SessionFactory es usado para obtener instancias de org.hibernate.Session. org.hibernate.Session representa una unidad de trabajo para un unico hilo de ejecucion, o thread. org.hibernate.SessionFactory es un objeto global de tipo thread-safe (varios hilos de ejecucion accediendo a el al mismo tiempo son ejecutados en serie, nunca en paralelo) que es instanciado una sola vez.

A continuacion crearemos una clase de ayuda HibernateUtil que se encargara de iniciar y hacer accesible org.hibernate.SessionFactory de manera conveniente.

							package org.hibernate.tutorial.util;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

    private static final SessionFactory sessionFactory = buildSessionFactory();

    private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return new Configuration().configure().buildSessionFactory();
        }
        catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

}
						

Guarda este codigo como src/main/java/org/hibernate/tutorial/util/HibernateUtil.java

Esta clase no solo produce una referencia global org.hibernate.SessionFactory en su inicializador estatico; tambien oculta el hecho de que solo usa un singleton (objeto que es unico, no pueden generarse nunca dos instancias diferentes de el) estatico. Tambien podriamos haber buscado la referencia a org.hibernate.SessionFactory mediante JNDI en un servidor de aplicaciones.

Si le das un nombre a org.hibernate.SessionFactory en tu configuracion, Hibernate intentara asociarlo a JNDI bajo ese nombre despues de haber sido construido. Una opcion mejor es usar un despliegue JMX y permitir a un contenedor con soporte JMX instanciar y asociar HibernateService a JNDI. Tales opciones avanzadas seran discutidas mas tarde.

Ahora necesitas configurar un sistema de log. Hibernate usa 'commons logging' y ofrece dos opciones: log mediante Log4j y mediante JDK 1.4. Muchos desarrolladores prefieren Log4j: copia el archivo log4j.properties desde el directorio de la distribucion de Hibernate etc/ a tu directorio src, de manera que quede junto al archivo hibernate.cfg.xml que hemos creado previamente. Si prefieres tener una salida de log con informacion mas completa que la ofrecida en el ejemplo de configuracion, puedes cambiar los valores. Por defecto, solo los mensajes de inicio de Hibernate se muestran en stdout.

La infraestructura del tutorial esta completa y ahora ya puedes comenzar a hacer trabajo real con Hibernate.

Ahora estamos listos para comenzar a hacer trabajo real con Hibernate. Comencemos escribiendo una clase EventManager con un metodo main():

							package org.hibernate.tutorial;

import org.hibernate.Session;

import java.util.*;

import org.hibernate.tutorial.domain.Event;
import org.hibernate.tutorial.util.HibernateUtil;

public class EventManager {

    public static void main(String[] args) {
        EventManager mgr = new EventManager();

        if (args[0].equals("store")) {
            mgr.createAndStoreEvent("My Event", new Date());
        }

        HibernateUtil.getSessionFactory().close();
    }

    private void createAndStoreEvent(String title, Date theDate) {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();

        Event theEvent = new Event();
        theEvent.setTitle(title);
        theEvent.setDate(theDate);
        session.save(theEvent);

        session.getTransaction().commit();
    }

}
						

En el metodo createAndStoreEvent() hemos creado un objeto Event y lo hemos manejado con Hibernate. En este punto, Hibernate se preocupa de las operaciones SQL por nosotros y ejecuta una operacion INSERT en la base de datos.

La clase org.hibernate.Session esta diseñada para representar una unica unidad de trabajo a ser realizada. Por ahora mantendremos las cosas simples y asumiremos una granularidad uno-a-uno entre el objeto org.hibernate.Session de Hibernate y una transaccion de base de datos. Para proteger a nuestro codigo del sistema subyacente de transacciones usaremos la API org.hibernate.Transaction. En este caso en particular estamos usando semantica transaccional basada en JDBC, pero tambien podria funcionar mediante JTA.

¿Que es lo que hace sessionFactory.getCurrentSession()? Para empezar, puedes llamarlo cuantas veces quieras y desde donde quieras una vez que consigas un objeto org.hibernate.SessionFactory. El metodo getCurrentSession() siempre devuelve la unidad de trabajo "actual". ¿Recuerda que cambiamos la opcion de configuracion para este mecanismo a "thread" en el archivo src/main/resources/hibernate.cfg.xml? Debido a este ajuste, el contexto de la unidad de trabajo actual esta asociado al thread actual , que es el que ejecuta la aplicacion.

Una sesion org.hibernate.Session comienza cuando la primera llamada a getCurrentSession() es hecha por el thread actual. Entonces, es asociada por Hibernate al thread actual. Cuando la transaccion termina, ya sea aceptada (commit) o cancelada (rollback), Hibernate desasocia automaticamente la sesion org.hibernate.Session del thread y la cierra por ti. Si llamas de nuevo a getCurrentSession() , obtienes una nueva sesion org.hibernate.Session y puedes comenzar una nueva unidad de trabajo.

Hablando del alcance de la unidad de trabajo, ¿deberia org.hibernate.Session usarse para ejecutar una o varias operaciones de base de datos? El ejemplo anterior usa un objeto org.hibernate.Session para una unica operacion. Sin embargo, esto es pura coincidencia; el ejemplo no es lo suficientemente complejo para mostrar otra opcion. El alcance de org.hibernate.Session puede es flexible, pero nunca debes diseñar tu aplicacion para usar un nuevo objeto org.hibernate.Session por cada operacion de base de datos. Aunque esto es lo que ocurre en los siguientes ejemplos, considera que sesion-por-operacion es un anti-pattern (un diseño contrario a las buenas practicas de programacion). Una aplicacion web real es mostrada mas tarde en el tutorial, la cual te ayudara a ilustrar mejor este concepto.

El ejemplo anterior tambien ha evitado cualquier manejo de errores asi como cancelaciones de la transaccion en caso de que los primeros hubiera ocurrido.

Para ejecutar el codigo anterior, tenemos que hacer uso de la opcion 'exec' de Maven para llamar a nuestra clase con los ajustes de classpath necesarios: mvn exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="store"

Nota

Tal vez necesites realizar primero una llamada a mvn compile .

Deberias ver a Hibernate arrancando y, dependiendo de tu configuracion, un monton de salida de log. Cerca del final, se mostrara la siguiente linea:

[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)

Esta es la secuencia SQL INSERT ejecutada por Hibernate.

Para listar eventos, añadimos el siguiente codigo al metodo 'main':

        if (args[0].equals("store")) {
            mgr.createAndStoreEvent("My Event", new Date());
        }
        else if (args[0].equals("list")) {
            List events = mgr.listEvents();
            for (int i = 0; i < events.size(); i++) {
                Event theEvent = (Event) events.get(i);
                System.out.println(
                        "Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()
                );
            }
        }

Asi mismo, un nuevo metodo listEvents() es tambien añadido:

    private List listEvents() {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        List result = session.createQuery("from Event").list();
        session.getTransaction().commit();
        return result;
    }

Aqui estamos usando una solicitud Hibernate Query Language (HQL) para leer todos los objetos Event desde la base de datos. Hibernate generara las sentencias SQL adecuadas, las enviara a la base de datos y creara objetos Event con los datos obtenidos. HQL te permite crear consultas mucho mas complejas.

Ahora podemos probar nuestra nueva funcionalidad, de nuevo usando la opcion 'exec' de Maven: mvn exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="list"

Hasta ahora hemos mapeado una unica clase persistente en isolacion a la base de datos. Ampliemos un poco el mapeo y añadamos algunas asociaciones entre clases. Añadiremos personas a la aplicacion y almacenaremos una lista de eventos en los cuales cada persona puede participar.

Añadiendo una coleccion de eventos a la clase Person, puedes navegar facilmente por todos los eventos de una persona en particular, sin ejecutar ninguna consulta explicita - llamando a Person#getEvents. Asociaciones que representan multiples valores son representadas en Hibernate por uno de los contratos del Java Collection Framework; aqui hemos elegido java.util.Set porque no queremos que la coleccion contenga elementos duplicados y porque el orden en que esten ordenados no es relevante para nuestros ejemplos:

							public class Person {

    private Set events = new HashSet();

    public Set getEvents() {
        return events;
    }

    public void setEvents(Set events) {
        this.events = events;
    }
}
						

Antes de mapear esta asociacion, consideremos como sera desde el otro lado. Podemos mantener esta asociacion unidireccional o crear otra coleccion en la clase Event, si es que queremos ser capaces de navegar en ambas direcciones. Esto no es necesario desde una perspectiva funcional. Siempre puedes ejecutar una consulta explicita para obtener los participantes para un evento en particular. Esta es una decision de diseño que te dejamos a ti, pero lo que queda claro de esta discursion es la multiplicidad de la asociacion: "muchos" valores en ambos lados forman una asociacion de tipo many-to-many (muchos-a-muchos). Por lo tanto, usamos un mapeo many-to-many de Hibernate:

							<class name="Person" table="PERSON">
    <id name="id" column="PERSON_ID">
        <generator class="native"/>
    </id>
    <property name="age"/>
    <property name="firstname"/>
    <property name="lastname"/>

    <set name="events" table="PERSON_EVENT">
        <key column="PERSON_ID"/>
        <many-to-many column="EVENT_ID" class="Event"/>
    </set>

</class>
						

Hibernate soporta un amplio rango de mapeo de colecciones, siendo set el mas comun. Para una asociacion de tipo many-to-many, o relacion entre entidades n:m, se requiere una tabla de asociacion. Cada fila en esta tabla representa un enlace entre una persona y un evento. El nombre de la tabla es declarado usando el atributo table del elemento set. El nombre de la columna identificadora de la asociacion para el lado las personas, es definido con el elemento key; el nombre de la columna identificadora de la asociacion para el lado de los eventos con el atributo column del elemento many-to-many. Tambien tienes que informar a Hibernate de la clase de objetos que almacena tu coleccion.

El esquema de base de datos para este mapeo es el siguiente:

							
    _____________        __________________
   |             |      |                  |       _____________
   |   EVENTS    |      |   PERSON_EVENT   |      |             |
   |_____________|      |__________________|      |    PERSON   |
   |             |      |                  |      |_____________|
   | *EVENT_ID   |<---->| *EVENT_ID        |      |             |
   |  EVENT_DATE |      | *PERSON_ID       |<---->| *PERSON_ID  |
   |  TITLE      |      |__________________|      |  AGE        |
   |_____________|                                |  FIRSTNAME  |
                                                  |  LASTNAME   |
                                                  |_____________|
 
						

Ahora vamos a traer varias personas y eventos, y vamos a juntarlos en un nuevo metodo que crearemos en EventManager:

							    private void addPersonToEvent(Long personId, Long eventId) {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();

        Person aPerson = (Person) session.load(Person.class, personId);
        Event anEvent = (Event) session.load(Event.class, eventId);
        aPerson.getEvents().add(anEvent);

        session.getTransaction().commit();
    }
						

Despues de obtener un objeto Person y un objeto Event, modificamos la colecion utilizando sus metodos tipicos. No hay una llamada explicita a update() or save(); Hibernate detecta automaticamente que la coleccion ha sido modificada y necesita ser actualizada. Esto se conoce como comprobacion automatica de datos obsoletos (en el original ingles se utiliza el termino 'dirty', que significa suciedad, en lugar de 'obsoleto'. En español la traduccion literal de la frase original no seria aclaratoria). Tambien puedes intentar modificar las propiedades correspondientes al nombre o a la fecha de cualquiera de estos objetos. Mientras esten en estado persistente, osea, ligados a una instancia activa de org.hibernate.Session, Hibernate monitorea cualquier cambio y ejecuta instancias SQL de manera totalmente transparente para el usuario. El proceso de sincronizacion entre es estado de la memoria y la base de datos, realizado normalmente al final de la unidad de trabajo, es llamado flushing (descargar). En nuestro codigo, la unidad de trabajo termina aceptando (commit) o cancelando (rollback) la transaccion en la base de datos.

Puedes obtener personas y eventos en diferentes unidades de trabajo. O puedes modificar un objeto fuera del alcance de org.hibernate.Session, cuando no esta en estado persistente (si ya fue persistido anteriormente, este estado es llamado separado, o 'detached'). Incluso puede modificar una coleccion cuando esta separada:

							    private void addPersonToEvent(Long personId, Long eventId) {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();

        Person aPerson = (Person) session
                .createQuery("select p from Person p left join fetch p.events where p.id = :pid")
                .setParameter("pid", personId)
                .uniqueResult(); // Eager fetch the collection so we can use it detached
        Event anEvent = (Event) session.load(Event.class, eventId);

        session.getTransaction().commit();

        // End of first unit of work

        aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached

        // Begin second unit of work

        Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();
        session2.beginTransaction();
        session2.update(aPerson); // Reattachment of aPerson

        session2.getTransaction().commit();
    }
						

La llamada a update convierte de nuevo la entidad separado en persistida, asociandola con una nueva unidad de trabajo, por lo que cualquier modificacion que hagas en ella mientras estaba separada puede ser guardada en la base de datos. Esto incluye cualquier modificacion (adiciones/eliminaciones) que hagas en una coleccion de dicha entidad.

No se ha usado mucho en nuestro ejemplo, pero es un concepto importante que puedes incorporar en tu propia aplicacion. Completa este ejercicio añadiendo una nueva accion al metodo main de EventManager e invocalo desde la linea de comandos. Si necesitas los identificadores de una persona y un evento, el metodo save() los devuelve (tal vez tengas que modificar algunos de los metodos anteriores para devolver dicho identificador):

							        else if (args[0].equals("addpersontoevent")) {
            Long eventId = mgr.createAndStoreEvent("My Event", new Date());
            Long personId = mgr.createAndStorePerson("Foo", "Bar");
            mgr.addPersonToEvent(personId, eventId);
            System.out.println("Added person " + personId + " to event " + eventId);
        }
						

El codigo anterior es un ejemplo de una asociacion entre dos clases igualmente importantes: dos entidades. Como se ha mencionado anteriormente, hay otras clases y tipos en un modelo tipico, que son considerados "menos importantes". Algunos ya los has visto, como int o java.lang.String. Llamamos a esas clases tipos con valor, y sus instancias dependen de una entidad en concreto. Las instancias de tipos con valor no tienen identidad, ni tampoco son compartidos entre entidades. Dos personas no pueden hacer referencia al mismo objeto firstname, incluso aunque tengan el mismo nombre. Tipos con valor no solo pueden ser encontrados en el JDK , si no que tambien puedes escribir tus propias clases dependientes como Address o MonetaryAmount. De hecho, en una aplicacion Hibernate todas las clases del JDK son consideradas tipos con valor.

Tambien puedes diseñar una coleccion de tipos con valor. Esto es conceptualmente diferente de una coleccion de referencias a otras entidades, pero parecen casi iguales cuando las vemos en Java.

Añadamos una coleccion de direcciones de correo electronico a la entidad Person. Esta sera representada como una instancia de java.util.Set que contendra instancias de java.lang.String:

							    private Set emailAddresses = new HashSet();

    public Set getEmailAddresses() {
        return emailAddresses;
    }

    public void setEmailAddresses(Set emailAddresses) {
        this.emailAddresses = emailAddresses;
    }
						

El mapeo para este Set es el siguiente:

							        <set name="emailAddresses" table="PERSON_EMAIL_ADDR">
            <key column="PERSON_ID"/>
            <element type="string" column="EMAIL_ADDR"/>
        </set>
						

La diferencia en comparacion con el mapeo anterior es el uso de element, el cual indica a Hibernate que la coleccion no contiene referencias a otras entidades, si no una coleccion cuyos elementos son tipos con valor, especificamente del tipo string. El nombre con la primera letra en minusculas indica el mapeo de un tipo/conversor de Hibernate. De nuevo, el atributo table dentro del elemento set determina el nombre de la tabla donde almacenar la coleccion. El elemento key define el nombre de la columna de claves foraneas en la tabla que contiene la coleccion. El atributo column en el elemento element define el nombre de la columna donde los valores de las direcciones de correo electronico seran almacenadas.

Aqui esta el esquema actualizado:

							  
  _____________       __________________
 |             |     |                  |      _____________       ___________________
 |   EVENTS    |     |   PERSON_EVENT   |     |             |     |                   |
 |_____________|     |__________________|     |    PERSON   |     | PERSON_EMAIL_ADDR |
 |             |     |                  |     |_____________|     |___________________|
 | *EVENT_ID   |<--->| *EVENT_ID        |     |             |     |                   |
 |  EVENT_DATE |     | *PERSON_ID       |<--->| *PERSON_ID  |<--->|  *PERSON_ID       |
 |  TITLE      |     |__________________|     |  AGE        |     |  *EMAIL_ADDR      |
 |_____________|                              |  FIRSTNAME  |     |___________________|
                                              |  LASTNAME   |
                                              |_____________|
 
						

Puedes ver que la clave primaria de la tabla que almacena la coleccion es de hecho una clave compuesta que utiliza ambas columnas. Esto tambien implica que no pueden existir direcciones de correo electronico duplicadas por persona, que es exactamente la semantica que necesitamos para una coleccion de tipo set en Java.

Ahora puedes intentar añadir elecmentos a esta coleccion, exactamente como lo hicimos antes asociando personas y eventos. Es el mismo tipo de codigo en Java, utilizando metodos getter y setter:

							    private void addEmailToPerson(Long personId, String emailAddress) {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();

        Person aPerson = (Person) session.load(Person.class, personId);
        // adding to the emailAddress collection might trigger a lazy load of the collection
        aPerson.getEmailAddresses().add(emailAddress);

        session.getTransaction().commit();
    }
						

Esta vez no utilizamos una consulta de tipo fetch para inicializar la coleccion. Monitorea el log de SQL e intenta optimizarla con una lectura temprana de datos (eager fetch).

A continuacion mapearemos una asociacion bidireccional. Haremos que la asociacion sea manejable entre personas y eventos en ambos sentidos. El esquema de la base de datos no cambia, por lo que seguiras teniendo una multiplicidad muchos-a-muchos.

Primero, añade una coleccion de participantes en la clase Event:

							    private Set participants = new HashSet();

    public Set getParticipants() {
        return participants;
    }

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

Ahora mapea este lado de la asociacion en Event.hbm.xml.

							        <set name="participants" table="PERSON_EVENT" inverse="true">
            <key column="EVENT_ID"/>
            <many-to-many column="PERSON_ID" class="events.Person"/>
        </set>
						

Estos son mapeos normales de tipo set en ambos archivos. Fijate que los nombres de las columnas en key y many-to-many se intercambian en ambos archivos de mapeo. El añadido mas importante es el atributo inverse="true" en el elemento set del mapeo de la coleccion Event.

El significado de esto es que Hibernate deberia utilizar el otro lado, en este caso la clase Person , cuando necesita buscar informacion acerca de un enlace entre las dos clases. Esto es muy sencillo de comprender una vez que ves como es creado el enlace bidireccional entre nuestras dos entidades.

Primero, ten en mente que Hibernate no afecta a la semantica normal de Java. ¿Como hemos creado anteriormente un enlace entre Person y Event en el ejemplo unidireccional? Añadiste una instancia de Event a la coleccion que almacena eventos en Person. Si quieres hacer este enlace bidireccional, tienes que hacer lo mismo en el otro lado de la asociacion añadiendo una referencia de tipo Person a la coleccion que tenemos en Event. Este proceso de "ajustar los enlaces en ambos lados" es absolutamente necesario con enlaces bidireccionales.

Muchos desarrolladores programan defensivamente y crean metodos de mantenimiento de enlaces para ajustar correctamente ambos lados (por ejemplo, en Person):

							    protected Set getEvents() {
        return events;
    }

    protected void setEvents(Set events) {
        this.events = events;
    }

    public void addToEvent(Event event) {
        this.getEvents().add(event);
        event.getParticipants().add(this);
    }

    public void removeFromEvent(Event event) {
        this.getEvents().remove(event);
        event.getParticipants().remove(this);
    }
						

Los metodos get y set de la coleccion son ahora de tipo 'protected'. Esto permite a las clases en el mismo paquete y las subclases seguir accediendo a dichos metodos, pero previene que todo el mundo pueda alterar la coleccion directamente. Repite este paso en la coleccion del otro lado.

¿Y que hay del atributo de mapeo inverse? Para ti, y para Java, un enlace bidireccional es simplemente una manera de ajustar las referencias en ambos lados de forma correcta. Hibernate, sin embargo, no tiene la informacion suficiente para organizar correctamente las sentencias SQL INSERT y UPDATE (para evitar violaciones de uso). Haciendo uno de los lados de la asociacion inverse informa a Hibernate que considere ese lado como un espejo del otro lado. Esto es todo lo necesario para que Hibernate pueda resolver cualquier problema que surja en el momento de transformar un modelo de navegacion direccional en un esquema SQL para la base de datos. Las reglas son sencillas: todas las asociaciones bidireccionales necesitan que un lado sea declarado inverse. En una asociacion uno-a-muchos este tiene que ser el lado 'muchos', y en una asociacion muchos-a-muchos puedes elegir cualquiera de los lados.

Una aplicacion web Hibernate utiliza objetos Session y Transaction casi igual que una aplicacion independiente. Sin embargo, algunos patrones de diseño son utiles. Por ejemplo, puedes escribir un EventManagerServlet. Este servlet puede listar todos los eventos almacenados en la base de datos, y ofrece un formulario HTML para introducir nuevos eventos.

Primero necesitamos crear nuestro servlet de procesamiento basico. Puesto que este servlet solo maneja solicitudes HTTP de tipo GET, solo implementaremos el metodo doGet():

							package org.hibernate.tutorial.web;

// Imports

public class EventManagerServlet extends HttpServlet {

    protected void doGet(
            HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

        SimpleDateFormat dateFormatter = new SimpleDateFormat( "dd.MM.yyyy" );

        try {
            // Begin unit of work
            HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();

            // Process request and render page...

            // End unit of work
            HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit();
        }
        catch (Exception ex) {
            HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback();
            if ( ServletException.class.isInstance( ex ) ) {
                throw ( ServletException ) ex;
            }
            else {
                throw new ServletException( ex );
            }
        }
    }

}
						

Guarda este servlet como src/main/java/org/hibernate/tutorial/web/EventManagerServlet.java

El patron de diseño aplicado aqui se llama una-sesion-por-solicitud. Cuando una solicitud alcanza el servlet, un nueva sesion de tipo Session es abierta a traves de la primera llamada al metodo getCurrentSession() de SessionFactory. Una transaccion de base de datos es entonces iniciada. Todos los accesos a datos ocurren dentro de dicha transaccion independientemente de si los datos son leidos o escritos. no use el modo 'auto-commit' en tus aplicaciones.

No uses un objeto Session para cada operacion de base de datos. Usa solamente una que alcance a la solicutud HTTP completa. Usa getCurrentSession(), de manera que sea automaticamente asociada al hilo de ejecucion actual.

A continuacion, las posibles acciones de la solicitud son procesadas y la respuesta HTML es formada. En breve iremos a esa parte.

Finalmente, la unidad de trabajo termina cuando el procesamiento y la formacion de la respuesta estan completadas. Si ocurriera cualquier problema durante estas dos fases, se lanzaria una excepcion y la transaccion de base de datos seria cancelada y sus cambios desechos. Esto completa el patron una-sesion-por-solicitud. En lugar de escribir codigo para demarcar la transaccion en cada servlet, tambien puedes escribir un filtro servlet. Mira en el sitio web de Hibernate y en su Wiki si quieres mas informacion sobre este patro, llamado Abrir Sesion en la Vista. Lo necesitaras tan pronto como consideres formar la vista mediante JSP, en lugar de mediante servlets.

Ahora puedes implementar el procesamiento de la solicitud y formar la pagina de respuesta.

							        // Write HTML header
        PrintWriter out = response.getWriter();
        out.println("<html><head><title>Event Manager</title></head><body>");

        // Handle actions
        if ( "store".equals(request.getParameter("action")) ) {

            String eventTitle = request.getParameter("eventTitle");
            String eventDate = request.getParameter("eventDate");

            if ( "".equals(eventTitle) || "".equals(eventDate) ) {
                out.println("<b><i>Please enter event title and date.</i></b>");
            }
            else {
                createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate));
                out.println("<b><i>Added event.</i></b>");
            }
        }

        // Print page
       printEventForm(out);
       listEvents(out, dateFormatter);

       // Write HTML footer
       out.println("</body></html>");
       out.flush();
       out.close();
						

Este estilo de escribir codigo, mezclando Java y HTML, no es nada escalable en una aplicacion mas compleja (ten en cuenta que solo se estan ilustrando conceptos basicos de Hibernate en este tutorial). El codigo imprime una tanto una cabecera como un pie de pagina HTML. Dentro de esta pagina, se muestran un formulario HTML para introducir eventos y una lista de todos los eventos almacenados en la base de datos. El primer metodo es muy trivial, y solo produce salida HTML:

							    private void printEventForm(PrintWriter out) {
        out.println("<h2>Add new event:</h2>");
        out.println("<form>");
        out.println("Title: <input name='eventTitle' length='50'/><br/>");
        out.println("Date (e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>");
        out.println("<input type='submit' name='action' value='store'/>");
        out.println("</form>");
    }
						

El metodo listEvents() usa un objeto Session asociado al hilo de ejecucion actual para ejecutar una consulta a la base de datos:

							    private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) {

        List result = HibernateUtil.getSessionFactory()
                .getCurrentSession().createCriteria(Event.class).list();
        if (result.size() > 0) {
            out.println("<h2>Events in database:</h2>");
            out.println("<table border='1'>");
            out.println("<tr>");
            out.println("<th>Event title</th>");
            out.println("<th>Event date</th>");
            out.println("</tr>");
            Iterator it = result.iterator();
            while (it.hasNext()) {
                Event event = (Event) it.next();
                out.println("<tr>");
                out.println("<td>" + event.getTitle() + "</td>");
                out.println("<td>" + dateFormatter.format(event.getDate()) + "</td>");
                out.println("</tr>");
            }
            out.println("</table>");
        }
    }
						

Por ultimo, la accion store es redirigida al metodo createAndStoreEvent(), el cual tambien usa el objeto Session del hilo de ejecucion actual:

							    protected void createAndStoreEvent(String title, Date theDate) {
        Event theEvent = new Event();
        theEvent.setTitle(title);
        theEvent.setDate(theDate);

        HibernateUtil.getSessionFactory()
                .getCurrentSession().save(theEvent);
    }
						

The servlet is now complete. A request to the servlet will be processed in a single Session and Transaction. As earlier in the standalone application, Hibernate can automatically bind these objects to the current thread of execution. This gives you the freedom to layer your code and access the SessionFactory in any way you like. Usually you would use a more sophisticated design and move the data access code into data access objects (the DAO pattern). See the Hibernate Wiki for more examples.

de separar en capas tu codigo y acceder a SessionFactory de la manera que quieras. Normalmente usarias un diseño mas sofisticado y moverias el codigo de acceso al interior de un objeto de acceso a datos (patron de diseño DAO). En el Wiki de Hibernate puedes encontrar mas ejemplos.

Para desplegar esta aplicacion y probarla, necesitamos crear un ARchivo Web (WAR). Primero debemos definir un descriptor WAR en el archivo src/main/webapp/WEB-INF/web.xml:

							<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <servlet>
        <servlet-name>Event Manager</servlet-name>
        <servlet-class>org.hibernate.tutorial.web.EventManagerServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Event Manager</servlet-name>
        <url-pattern>/eventmanager</url-pattern>
    </servlet-mapping>
</web-app>
						

Para construir y desplegar la aplicacion ejecuta el comando mvn package en el directorio del proyecto, y copia el archivo resultante hibernate-tutorial.war en el directorio webapps de tu instalacion de Tomcat.

Nota

Si no tienes Tomcat instalado, descargalo desde http://tomcat.apache.org/ y sigue las instrucciones de instalacion que encontraras en la misma web. Nuestra aplicacion no requiere cambios en la configuracion estandar de Tomcat.

Con la aplicacion desplegada y Tomcat ejecutandose, accede a la aplicacion mediante la direccion http://localhost:8080/hibernate-tutorial/eventmanager . Asegurate de mirar el log de Tomcat para ver que Hibernate se inicia cuando la primera solicitud alcanza tu servlet (el inicializador estatico en HibernateUtil es llamado) y de obtener una salida detallada si cualquier excepcion ocurriera.