Comienzo rápido con Tomcat
Empezando con Hibernate
Este tutorial explica una instalación de Hibernate con el
contenedor de servlets Apache Tomcat (hemos usado la versión 4.1,
las diferencias con la 5.0 deben ser mínimas) para una aplicación
basada en web. Hibernate trabaja bien en un entorno manejado con
todos los servidores de aplicaciones J2EE importantes, o incluso en aplicaciones
Java independientes. El sistema de base de datos es sólo una cuestión
de cambiar la configuración del dialecto SQL de Hibernate y las
propiedades de conexión.
Primero, tenemos que copiar todas las bibliotecas requeridas a la instalación
de Tomcat. Usamos un contexto web separado (webapps/quickstart)
para este tutorial, de modo que tenemos que considerar tanto la ruta de búsqueda
de bibliotecas global (TOMCAT/common/lib) como también
el cargador de clases a nivel de contexto en webapps/quickstart/WEB-INF/lib
(para ficheros JAR) y webapps/quickstart/WEB-INF/classes.
Nos referiremos a ambos niveles de cargador de clases como el classpath global y el classpath
de contexto, respectivamente.
Ahora, copia las bibliotecas a los dos classpaths:
Copia el driver JDBC para la base de datos al classpath global. Esto se
requiere para el software de pool de conexiones DBCP que se distribuye
con Tomcat. Hibernate usa conexiones JDBC para ejecutar SQL sobre la base de
datos, de modo que, o bien tienes que proveer conexiones JDBC en pool,
o bien configurar Hibernate para que use uno de los pools soportados
directamente (C3P0, Proxool). Para este tutorial, copia la biblioteca
pg74jdbc3.jar (para PostgreSQL 7.4 y JDK 1.4) al
classpath del cargador global. Si quisieras usar una base de datos diferente,
simplemente copia su apropiado driver JDBC.
Nunca copies nada más dentro de la ruta del cargador de clases global
en Tomcat, o tendrás problemas con varias herramientas, incluyendo
Log4J, commons-logging y otras. Siempre usa el classpath de contexto para
cada aplicación web, esto es, copia las bibliotecas a
WEB-INF/lib y tus propias clases y ficheros de
configuración/propiedades a WEB-INF/classes.
Ambos directorios están a nivel del classpath de contexto por defecto.
Hibernate está empaquetado como una biblioteca JAR. El fichero
hibernate3.jar debe ser copiado en el classpath de contexto
junto a las otras clases de la aplicación. Hibernate requiere algunas
bibliotecas de terceros en tiempo de ejecución; éstas vienen
incluídas con la distribución de Hibernate en el directorio
lib/. Ver . Copia las
bibliotecas de terceros requeridas al classpath de contexto.
Bibliotecas de terceros de Hibernate
Biblioteca
Descripción
antlr (requerida)
Hibernate usa ANTLR para producir analizadores de consultas,
esta biblioteca también se necesita en tiempo de ejecución.
dom4j (requerida)
Hibernate usa dom4j para analizar ficheros de configuración
XML y ficheros de metadatos de mapeo XML.
CGLIB, asm (requerida)
Hibernate usa la biblioteca de generación de código
para aumentar las clases en tiempo de ejecución
(en combinación con reflección Java).
Commons Collections, Commons Logging (requeridas)
Hibernate usa varias bibliotecas de utilidad del proyecto
Jakarta Commons de Apache.
EHCache (requerida)
Hibernate puede usar varios provedores de caché para
el caché de segundo nivel. EHCache es el provedor de
caché por defecto si no se cambia en la configuración.
Log4j (opcional)
Hibernate usa la API de Commons Logging, que a su vez puede
usar Log4J como el mecanismo de logging subyacente. Si la
biblioteca Log4J está disponible en el directorio de
bibliotecas del contexto, Commons Logging usará Log4J
y la configuración log4j.properties
en el classpath de contexto. Un fichero de propiedades de ejemplo
para Log4J se incluye con la distribución de Hibernate.
Así que copia log4j.jar y el fichero de configuración
(de src/) a tu classpath de contexto si quieres
ver que ocurre tras escénas.
¿Requerida o no?
Echa una mirada al fichero lib/README.txt en la
distribución de Hibernate. Esta es una lista actualizada
de bibliotecas de terceros distribuídas con Hibernate.
Encontrarás listadas ahí todas las bibliotecas
requeridas y opcionales (Observa que "buildtame required" significa
aquí para la construcción de Hibernate, no de tu
aplicación).
Ahora instalamos el pooling y modo compartido de conexiones de base de datos
tanto en Tomcat como Hibernate. Esto significa que Tomcat proveerá
conexiones JDBC en pool (usando su funcionalidad prefabricada de pooling DBCP).
Hibernate pide esas conexiones a través de JNDI. Alternativamente,
puedes dejar que Hibernate maneje el pool de conexiones. Tomcat liga su pool
de conexiones a JNDI; agregamos una declaración de recurso al fichero
de configuración principal de Tomcat, TOMCAT/conf/server.xml:
factory
org.apache.commons.dbcp.BasicDataSourceFactory
url
jdbc:postgresql://localhost/quickstart
driverClassNameorg.postgresql.Driver
username
quickstart
password
secret
maxWait
3000
maxIdle
100
maxActive
10
]]>
El contexto que configuramos en este ejemplo se llama quickstart,
su base es el directorio TOMCAT/webapp/quickstart. Para acceder
a cualquier servlet, llama a la ruta http://localhost:8080/quickstart
en tu navegador (por supuesto, agregando el nombre del servlet como se mapee en tu
web.xml). Puedes también ir más allá y crear
ahora un servlet simple que tenga un método process()
vacío.
Tomcat provee ahora conexiones a través de JNDI en
java:comp/env/jdbc/quickstart. Si tienes problemas obteniendo
el pool de conexiones en ejecución, refiérete a la documentación
de Tomcat. Si obtienes mensajes de excepción del driver JDBC, intenta instalar
primero el pool de conexiones JDBC sin Hibernate. Hay disponibles en la Web
tutoriales de Tomcat y JDBC.
Tu próximo paso es configurar Hibernate. Hibernate tiene que saber cómo
debe obtener conexiones JDBC. Usamos la configuración de Hibernate basada en XML.
El otro enfoque, usando un ficheros de propiedad, es casi equivalente pero pierde unas
pocas funcionalidades que sí permite la sintaxis XML. El fichero de configuración
XML se ubica en el classpath de contexto (WEB-INF/classes), como
hibernate.cfg.xml:
java:comp/env/jdbc/quickstart
false
org.hibernate.dialect.PostgreSQLDialect
]]>
Desactivamos el registro (logging) de comandos SQL y decimos a Hibernate
qué dialecto SQL de base de datos se usa y dónde obtener
conexiones JDBC (declarando la dirección JNDI del pool ligado a
Tomcat). El dialecto es una configuración requerida, las bases de
datos difieren en su interpretación del "estándar" de SQL.
Hibernate cuidará de las diferencias y viene con dialectos incluídos
para todas las principales bases de datos comerciales y de código
abierto.
Una SessionFactory es el concepto de Hibernate
de un almacén de datos solo. Pueden usarse múltiples
bases de datos creando múltiples ficheros de configuración
XML y creando múltiples objetos Configuration
y SessionFactory en tu aplicación.
El último elemento del hibernate.cfg.xml
declara Cat.hbm.xml como el nombre de un fichero
de mapeo XML para la clase persistente Cat. Este
fichero contiene los metadatos para el mapeo de la clase POJO
Cat a una tabla (o tablas) de base de datos.
Volveremos a este fichero pronto. Escribamos primero la clase POJO
y luego declaremos los metadatos de mapeo para ella.
Primera clase persistente
Hibernate trabaja mejor con el modelo de programación de los
Viejos Objetos Planos de Java (POJOs, a veces llamados Ordinarios Objetos Planos de Java)
para clases persistentes. Un POJO es como un JavaBean, con las propiedades
de la clase accesible vía métodos getter y setter,
encapsulando la representación interna de la interfaz publicamente
visible (Hibernate puede también acceder a los campos directamente, si se
necesita):
Hibernate no está restringido en su uso de tipos de propiedad, todos
los tipos y tipos primitivos del JDK de Java (como String,
char y Date) pueden ser mapeados, incluyendo
clases del framework de colecciones de Java. Puedes mapearlos como valores,
colecciones de valores, o asociaciones a otras entidades. El id
es una propiedad especial que representa el identificador de base de datos (clave
primaria) de la clase. Es altamente recomendado para entidades como un
Cat. Hibernate puede usar identificadores sólo
internamente, pero perderíamos algo de la flexibilidad en nuestra
arquitectura de aplicación.
No tiene que implementarse ninguna interface especial para las clases persistentes
ni tienes que subclasear de una clase persistente raíz en especial. Hibernate
tampoco requiere ningún procesamiento en tiempo de construcción,
como manipulación del byte-code. Se basa solamente en reflección de Java
y aumentación de clases en tiempo de ejecución (a través de CGLIB).
De modo que, sin ninguna dependencia de la clase POJO en Hibernate, podemos mapearla
a una tabla de base de datos.
Mapeando el gato
El fichero de mapeo Cat.hbm.xml contiene los
metadatos requeridos para el mapeo objeto/relacional. Los metadatos
incluyen la declaración de clases persistentes y el mapeo de
propiedades (a columnas y relaciones de claves foráneas a otras
entidades) a tablas de base de datos.
]]>
Cada clase persistente debe tener un atributo identificador (realmente,
sólo las clases que representen entidades, no las clases dependientes
de tipo-valor, que son mapeadas como componentes de una entidad). Esta propiedad
es usada para distinguir los objetos persistentes: Dos gatos son iguales si
catA.getId().equals(catB.getId()) es verdadero. Este concepto
se llama identidad de base de datos (database identity).
Hibernate viene empaquetado con varios generadores de identificador para diferentes
escenarios (incluyendo generadores nativos para secuencias de base de datos, tablas
de identificadores alto/bajo, e identificadores asignados por aplicación).
Usamos el generador UUID (recomendado sólo para pruebas, pues deben
preferirse las claves enteras delegadas generadas por la base de datos) y
también especificamos la columna CAT_ID de la tabla
CAT para el valor identificador generado por Hibernate
(como una clave primaria de la tabla).
Todas las demás propiedades de Cat son mapeadas a la
misma tabla. En el caso de la propiedad name, la hemos mapeado
con una declaración explícita de columna de base de datos. Esto es
especialmente útil cuando el esquema de base de datos es generado
automáticamente (como sentencias DDL de SQL) desde la declaración
de mapeo con la herramienta SchemaExport de Hibernate.
Todas las demás propiedades son mapeadas usando la configuración
por defecto de Hibernate, que es lo que necesitas la mayoría del tiempo.
La tabla CAT en la base de datos se ve así como:
Ahora debes crear esta tabla manualmente en tu base de datos, y luego leer el
si quieres automatizar este paso con la
herramienta hbm2ddl. Esta herramienta puede crear un
DDL SQL completo, incluyendo definición de tablas, restricciones
personalizadas de tipo de columnas, restricciones de unicidad e índices.
Jugando con gatos
Ahora estamos listos para comenzar la Session de Hibernate.
Es el manejador de persistencia que usamos para almacenar
y traer Cats hacia y desde la base de datos. Pero primero,
tenemos que obtener una Session (unidad de trabajo de Hibernate)
de la SessionFactory:
La llamada a configure() carga el fichero de
configuración hibernate.cfg.xml e
inicializa la instancia de Configuration.
Puedes establecer otras propiedades (e incluso cambiar los metadatos de mapeo)
accediendo a la Configuration antes
que construyas la SessionFactory (que es inmutable).
¿Dónde creamos la SessionFactory y cómo
accedemos a ella en nuestra aplicación?
Una SessionFactory usualmente se construye una vez,
por ejemplo, al arrancar con un servlet load-on-startup.
Esto significa también que no debes mantenerla en una variable de instancia
en tus servlets, sino en alguna otro sitio. Además, necesitamos algún
tipo de Singleton, de modo que podamos acceder a la
SessionFactory fácilmente en el código de
aplicación. El siguiente enfoque mostrado resuelve ambos problemas:
configuración de arranque y fácil acceso a una
SessionFactory.
Implementamos una clase de ayuda HibernateUtil:
Esta clase no sólo cuida de la SessionFactory
con su inicializador static, sino que además tiene una variable
ThreadLocal que tiene la Session
para la hebra actual. Asegúrate de entender el concepto Java de una
variable local a una hebra antes de intentar usar esta ayuda. Una clase
HibernateUtil más compleja y potente puede
encontrarse en CaveatEmptor, http://caveatemptor.hibernate.org/
Una SessionFactory es segura entre hebras, muchas hebras pueden
acceder a ella concurrentemente y pedirle Sessions. Una
Session no es un objeto seguro entre hebras que representa
una sola unidad-de-trabajo con la base de datos. Las Sessions
se abren desde una SessionFactory y son cerradas cuando
todo el trabajo está completo. Un ejemplo en el método
process() de tu servlet podría parecerse a esto
(sin manejo de excepciones):
En una Session, cada operación de base de datos
ocurre dentro de una transacción que aísla las operaciones
de base de datos (incluso operaciones de sólo lectura).
Usamos la API de Transaction de Hibernate para
abstraer de la estrategia de transacciones subyacente (en nuestro caso,
transacciones JDBC). Esto permite que nuestro código sea desplegado
con transacciones manejadas por contenedor (usando JTA) sin cambio alguno.
Observa que puedes llamar HibernateUtil.currentSession();
tantas veces como quieras, siempre obtendrás la Session
actual de esta hebra. Tienes que asegurarte que la Session
sea cerrada después que se complete tu unidad-de-trabajo, ya sea en
código de tu servlet o en un filtro de servlet antes que la respuesta HTTP
sea enviada. El bonito efecto colateral de la segunda opción es la
fácil inicialización perezosa: la Session todavía
está abierta cuando se dibuja la vista, de modo que Hibernate puede cargar
objetos no inicializados mientras navegas tu actual grafo de objetos.
Hibernate tiene varios métodos que pueden ser usados para traer
objetos desde la base de datos. La forma más flexible es usando
el Lenguaje de Consulta de Hibernate (Hibernate Query Language o HQL),
que es una extensión orientada a objetos de SQL fácil de
aprender:
Hibernate también ofrece una API consulta por criterios
orientada a objetos que puede ser usada para formular consultas de tipo seguro.
Por supuesto, Hibernate usa PreparedStatements y ligado de
parámetros para toda la comunicación SQL con la base de datos.
También puedes usar la funcionalidad de consulta SQL directa de Hibernate
u obtener una conexión plana de JDBC de una Session
en casos raros.
Finalmente
Rasguñamos solamente la superficie de Hibernate en este pequeño
tutorial. Por favor, observa que no incluimos ningún código
específico de servlet en nuestros ejemplos. Tienes que crear un servlet
por tí mismo e insertar el código de Hibernate como lo veas
ubicado.
Ten en mente que Hibernate, como capa de acceso a datos, está firmemente
integrado dentro de tu aplicación. Usualmente, todas las otras capas dependen
del mecanismo de persistencia. Asegúrate de entender las implicaciones
de este diseño.
Para un ejemplo de aplicación más compleja, ver
http://caveatemptor.hibernate.org/ y echa una mirada a los
otros tutoriales con links en http://www.hibernate.org/Documentation