La Primera Aplicación Hibernate
Para empezar haremos una aplicación Hibernate muy sencilla basada en la consola. Usaremos una base de datos en-memoria, por eso no tendremos que instalar ningún servidor.
Asumamos que queremos tener una pequeña aplicación donde poder almacenar los eventos a los que queremos asistir y quién los patrocina. Hemos decidido que queremos usar Hibernate para su almacenamiento, porque hemos oído que es lo más en persistencia ;-)
Lo primero que tenemos que hacer es configurar nuestro directorio de trabajo y poner en él todos los ficheros jar que necesitamos. Tenemos que descargar la distribución de Hibernate de su página de descarga. Extraer los jars necesarios desde el archivo de Hibernate. Los situaremos en un directorio lib bajo el directorio de trabajo, tu despliegue de directorios debería parecerese a esto:
.
+lib
cglib2.jar
commons-logging.jar
hibernate2.jar
jta.jar
odmg.jar
commons-collections.jar
dom4j.jar
jdbc2_0-stdext.jar
log4j.jar
La Primera Clase
Lo primero que haremos será crear una clase que represente los eventos que queremos almacenar. Esta será un simple Java Bean, que contenga algunas propiedades. Veamos el código:
package de.gloegl.road2hibernate;
public class Event {
private String title;
private Date date;
private Long id;
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;
}
}
Aquí tenemos algunas cosas que merecen la pena:
La propiedad id es una identificador único para el Event -- todos nuestros objetos persistentes necesitarán dicha id. Una buena idea cuando se construyen aplicaciones Hibernate es mantener dichas ids únicas ssparadas de la lógica de la aplicación. Esto significa que no manipularemos la id en ninguna parte de nuestro código, y dejaremos que Hibernate se ocupe de ella. De ahí viene porqué el método set de la id es privado, permitiendo que Hibernate lo utilice (Hibernate podría acceder a los métodos set y get de propiedades para todas las visibilidades), pero lo aislamos de nosotros.
También estámos usando un verdadero Long para la id, no un tipo long primitivo. Esto nos evitará quebraderos de cabeza más tarde -- utiliza siempre Objetos para la propiedad id, nunca tipos primitivos (si es posible).
Situaremos este fichero en un directorio llamado src en nuestro directorio de trabajo. El directorio debería aparecer de esta forma:
.
+lib
<hibernate jars>
+src
+de
+gloegl
+road2hibernate
Event.java
El Fichero de Mapeo
Como ya tenemos nuestra clase para almacenarla en la base de datos, debemos decirle a Hibernate cómo persistirla. Aquí es donde entra en juego el fichero de mapeo. El fichero de mapeo le dice a Hibernate qué debería almacenar en la base de datos - y cómo.
La estructura exterior de un fichero de mapeo se parece a esto:
<?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>
</hibernate-mapping>
Entre las dos etiquetas <hibernate-mapping>, incluiremos un elemento class, donde podemos declarar a que clase se refiere este mapeo y a qué tabla de nuestra base de datos SQL se deberia mapear. El paso 2 de nuestro documento de mapeo se debería parecer a esto:
<?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">
</class>
</hibernate-mapping>
Lo que hemos hecho hasta ahora es decirle a Hibernate que persista nuestra clase Event en la tabla EVENTS. Ahora tendremos que dar a Hibernate la propiedad a utilizar como identificador único -- que es por lo que hemos incluido la propiedad id. Además, como no queremos preocuparnos de manejar este valor de id, tenemos que decirle a Hibernate como generar estos ids. Incluyendo esto, nuestro fichero de mapeo tendrá este aspecto:
<?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>
</class>
</hibernate-mapping>
¿Qué significa todo esto? El elemento <id> es la declaración de la propiedad id. name="id" es el nombre de la propiedad - Hibernate usará los métodos getId y setId para acceder a ella. El atributo column le dice a Hibernate que cólumna de la tabla EVENTS contendrá el id. El atributo type le dice a Hibernate el tipo de la propiedad - en este caso un long.
El elemento <generator> especifica la técnica que se usará para la generación de id -- en este caso usaremos un incremento, que es un método de generación muy simple, pero que será suficiente para este ejemplo tan pequeño.
Finalmente tenemos que incluir las declaraciones para las propiedades persistentes en el fichero de mapeo:
<?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"/>
</class>
</hibernate-mapping>
Tenemos que observar algunas cosas de aquí. Al principio, igual que en el elemento <id>, el atributo name del elemento <property> le dice a Hibernate que métodos get y set utilizar.
Sin embargo, observarás que la propiedad title contiene un atributo column, y el atributo date no lo tiene. Esto es posible porque cuando no se utiliza el atributo column, Hibernate usará por defecto el nombre de la propiedad como el nombre de columna.
La siguiente cosa interesante es que la propiedad title carece de un atributo type. Otra vez, Hibernate intentará determinar el tipo correcto por sí mismo. Sin embargo, algunas veces Hibernate simplemente no puede hacer esto y tenemos que especificar el tipo - como es el caso de la propiedad date. Hibernate no puede saber si la propiedad se mapeará a una columna date, timestamp o time, por eso tenemos que especificarlo.
Situaremos el mapeo en un fichero llamado Event.hbm.xml en el mismo directorio donde tenemos la clase Event. Tu estructura de directorios debería parecerse a esto:
.
+lib
<hibernate jars>
+src
+de
+gloegl
+road2hibernate
Event.java
Event.hbm.xml
Configuración y Base de Datos
Ahora que ya tenemos nuestra clase persistente y el fichero de mapeo es hora de configurar Hibernate. Antes de hacer esto, necesitaremos una base de datos, vamos y obtenemos HSQLDB, una base de datos SQL en-memoria basada en Java. Lo que necesitamos es copiar el fichero hsqldb.jar del directorio lib de la descarga a nuestro directorio lib dentro del directorio de trabajo, que quedará de esta forma:
.
+lib
<hibernate jars>
hsqldb.jar
+src
<Aquí va el fichero fuente y el de mapeo>
Además crearemos un directorio data justo debajo del directorio de trabajo, donde hsqldb almacenará sus ficheros.
Ahora podemos configurar Hibernate utilizando un fichero XML, que llamaremos hibernate.cfg.xml y que situaremos directamente en el directorio src de nuestro directorio de trabajo. Este fichero se parecerá a esto:
<?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/data/Event.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Los primeros cuatro elementos <property> contienen la configuración necesaria para la Conexión JDBC que utilizará Hibernate. La propiedad dialect especifica el SQLdialect que Hibernate deberá generar. Luego especificamos que Hibernate delegará las transaciones a la conexión JDBC subyacente y especificamos un proveedor de caché (esto no tiene efecto porque todavía no utilizamos ningún caché). La siguiente propiedad le dice a Hibernate que ajuste automáticamente las tablas en la base de datos de acuerdo a nuestros mapeos. Finalmente le damos el path a nuestro fichero de mapeo.
Cosntruir
Finalmente empezamos a construir nuestra primera aplicación. Por conveniencia, creamos un fichero batch en nuestro directorio de trabajo que contenga todos los comandos necesarios para la complicación. Bajo windows, se parecería a esto:
javac -classpath .\lib\hibernate2.jar -d bin src\de\gloegl\road2hibernate\*.java
copy /Y src\hibernate.cfg.xml bin
copy /Y src\de\gloegl\road2hibernate\*.xml bin\de\gloegl\road2hibernate
Situamos este fichero llamado build.bat en nuestro directorio de trabajo. Si estás usando Linux, seguro que podrás crear un script equivalente...
Finalmente creamos el subdirectorio bin en nuestro directorio de trabajo para situar ahí las clases compiladas.
Ejecutar
Ahora crearemos un sencilla clase que arrancará Hibernate. Se parecerá a esto:
package de.gloegl.road2hibernate;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.cfg.Configuration;
public class EventManager {
private SessionFactory sessionFactory;
public EventManager() {
try {
System.out.println("Initializing Hibernate");
sessionFactory = new Configuration().configure().buildSessionFactory();
System.out.println("Finished Initializing Hibernate");
} catch (HibernateException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
EventManager instance = new EventManager();
System.exit(0);
}
}
Esta clase simplemente crea un ejemplar de sí mismo, y crea un ejemplar de SessionFactory en su constructor. Situamos el fichero EventManager.java en el diectorio adecuado. Nuestra estructura de directorios se debería parecer a ésta:
.
+lib
cglib2.jar
commons-logging.jar
hibernate2.jar
jta.jar
odmg.jar
commons-collections.jar
dom4j.jar
jdbc2_0-stdext.jar
log4j.jar
hsqldb.jar
+src
+de
+gloegl
+road2hibernate
Event.java
Event.hbm.xml
EventManager.java
hibernate.cfg.xml
+data
build.bat
Para ejecutar nuestra aplicación creamos otro fichero batch en el directorio de trabajo y lo llamamos run.bat, con el siguiente contenido (todo en una línea):
java -classpath .\lib\hibernate2.jar;.\lib\jta.jar;.\lib\commons-logging.jar;.\lib\hsqldb.jar;
.\lib\cglib2.jar;.\lib\commons-collections.jar;.\lib\dom4j.jar;.\lib\odmg.jar;
.\lib\jdbc2_0-stdext.jar;.\bin de.gloegl.road2hibernate.EventManager %1 %2 %3 %4 %5
Ahora compilamos todos los ficheros fuentes ejecutando el fichero build.bat desde el directorio de trabajo y lo ejecutamos utilizando el fichero run.bat. Debería producir esta salida:
Initializing Hibernate
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Finished Initializing Hibernate
Somos felices porque no hemos obtenido ningún error todavía -- pero aún queremos ver los que está haciendo Hibernate durante la arrancada y queremos ver sus avisos, por eso tenemos que configurar log4j. Esto se hace poniendo todo esto en un fichero llamado log4j.properties en nuestro directorio src:
log4j.rootCategory=INFO, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-5p - %m%n
Además tenemos que añadir la siguiente línea al fichero build.bat:
copy /Y src\log4j.properties bin
Todo lo que esto hace es decirle a log4j que escriba toda la salida de log por la consola.
Ahora recompilamos la aplicación ejecutanto de nuevo build.bat, y lo ejecutamos de nuevo -- ahora si que deberíamos ver información más detalladas de lo que está haciendo Hibernate.
Trabajar con Persistencia
Como ya hemos terminado de configurar Hibernate y nuestros mapeos, y nos hemos aprovechado de Hibernate y hemos persistido algunos objetos. Ahora ajustaremos nuestra clase EventManager para realizar algún trabajo con Hibernate.
Primero modificamos el método main:
public static void main(String[] args) throws java.text.ParseException {
EventManager instance = new EventManager();
if (args[0].equals("store")) {
String title = args[1];
Date theDate = new Date();
instance.store(title, theDate);
}
System.exit(0);
}
Ahora leemos algunos argumentos de la línea de comandos, y si el primer argumento de nuestra aplicación es store, tomamos el segundo argumento como un título, creamos un nuevo Date y los pasamos los dos al método store, donde está lo realmente interesante:
private void store(String title, Date theDate) {
try {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
session.save(theEvent);
tx.commit();
session.close();
} catch (HibernateException e) {
e.printStackTrace();
}
}
¿A qué está bien? Simplemente creamos un nuevo objeto Event, para que lo maneje Hibernate. Hibernate ahora se ocupa de crear el SQL, y enviarlo a base de datos. Incluso podemos arrancar y parar las transaciones que Hibernate delegará en la conexión JDBC.
Si ejecutáramos la aplicación con run.bat store Party se creará un objeto Event y se persistirá en la base de datos.
Pero ahora queremos listar nuestros Eventos almacenados, modificamos el método main un poco más:
public static void main(String[] args) throws java.text.ParseException {
EventManager instance = new EventManager();
if (args[0].equals("store")) {
String title = args[1];
Date theDate = new Date();
instance.store(title, theDate);
} else if (args[0].equals("list")) {
List events = instance.listEvents();
for (int i = 0; i<events.size(); i++) {
Event theEvent = (Event) events.get(i);
System.out.println("Event " + theEvent.getTitle() + " Time: " + theEvent.getDate());
}
}
System.exit(0);
}
Cuando el primer argumento sea list, llamamos a listEvents() e imprimimos todos los objetos
Event contenidos en la lista devuelta. listEvents() es donde sucede todo lo interesante:
private List listEvents() {
try {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
List result = session.find("from Event");
tx.commit();
session.close();
return result;
} catch (HibernateException e) {
throw new RuntimeException(e.getMessage());
}
}
Lo que hacemos aquí es utilizar una consulta HQL (Hibernate Query Language) para cargar todos los eventos que existen en la base de datos. Hibernate generará la sentencia SQL apropiada, la enviará a la base de datos y rellenará objetos Event con lo datos. Podemos crear consultas más complejas con HQL, como veremos en las páginas siguiente.