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.