CriteriaTrabajar con software orientado a objetos y bases de datos relacionales, puede ser embarazoso y demandar mucho tiempo, en los entornos corporativos actuales. Hibernate es una herramienta de mapeo objeto/relacional para ambientes Java. El término "mapeo objeto/relacional" (ORM por sus siglas en inglés) se refiere a esta técnica de "mapear" la representación de los datos desde un modelo de objetos hacia un modelo de datos relacional, con un esquema de base de datos basado en SQL.
Hibernate no sólo se hace cargo del mapeo de clases Java a las tablas de una base de datos (y de los tipos Java a los tipos de la base de datos), sino que también provee utilidades para consulta y captura de datos, y puede reducir considerablemente el tiempo que, de otra manera, habría que invertir con el manejo manual de datos mediante SQL y JDBC.
La meta de Hibernate es aliviar al programador del 95% de las tareas más comunes relacionadas con persistencia. Probablemente, Hibernate no sea la mejor solución para aplicaciones data-céntricas que tengan casi toda su lógica de negocios en procedimientos almacenados (stored procedures) en la base de datos; es más útil con modelos orientados a objetos cuya lógica de negocio reside en la capa intermedia. Sin embargo, Hibernate puede ayudarlo a encapsular o eliminar código SQL que sea específico de un proveedor de BD, y ayudará en la tarea usual de traducir desde una representación tabular a un gráfico de objetos.
Si usted es nuevo en Hibernate y en lo que respecta al Mapeo objeto/relacional, o incluso nuevo en Java, por favor siga los siguientes pasos:
Lea el siguiente instructivo: Capítulo 1, Introducción
a Hibernate, el cual es una especie de manual con instrucciones
paso a paso. El código fuente del instructivo está incluido en la
distribución descargable, en el directorio doc/reference/tutorial/
Lea Capítulo 2, Arquitectura para entender en qué entornos Hibernate puede ser usado.
Échele un vistazo al directorio eg/
en la distribución de Hibernate. Contiene una simple aplicación
autosuficiente. Copie su driver de JDBC al directorio lib/ y edite etc/hibernate.properties, especificando valores
correctos para su base de datos. Desde la consola, situado en el
directorio de distribución, tipee ant eg
(usando Ant), o desde Windows, tipee build
eg.
Use esta documentación de referencia como su fuente primaria de información. Considere leer Java Persistence with Hibernate (http://www.manning.com/bauer2) si necesita más ayuda con el diseño de aplicaciones o si prefiere un instructivo paso a paso. También visite http://caveatemptor.hibernate.org y descargue la aplicación de ejemplo para Persistencia de Java con Hibernate.
Las preguntas frecuentes (FAQ, por sus siglas en inglés), son contestadas en el sitio de web de Hibernate.
En el sitio de web de Hibernate hay vínculos a demostraciones de terceros, ejemplos e instructivos.
El Área Comunitaria del sitio de web de Hibernate es un buen recurso acerca de patrones de diseño y varias soluciones de integración (Tomcat, JBoss AS, Struts, EJB, etc).
Si tiene preguntas, utilice el foro en el sitio de Hibernate. También proveemos un sistema JIRA de seguimiento de problemas, para reportes de defectos (bugs) y pedidos de mejoras. Si a usted le interesa la programación de Hibernate, únase a la lista de correo de programación de Hibernate. Si le interesa traducir este documento, póngase en contacto con nosotros en la lista de correo de programación.
A través de JBoss Inc, hay disponible soporte para desarrollo comercial y de producción, y entrenamiento para Hibernate. (véase http://www.hibernate.org/SupportTraining/). Hibernate es un componente vital del paquete de productos conocido como "Sistema Empresarial JBoss de Middleware" (JEMS, por sus siglas en inglés).
Este
capítulo es una introducción a Hibernate, con el tono de un instructivo,
destinado a usuarios nuevos de Hibernate. Empezamos con una simple
aplicación que usa una base de datos residente en memoria. Construimos la
aplicación de a pasos pequeños, fáciles de comprender. Este instructivo se
basa en otro anterior, escrito por Michael Gloegl. Todo el código está
contenido en el directorio tutorials/web del
código fuente del proyecto.
Este instructivo sobreentiende que el usuario ya tiene conocimiento de Java y de SQL. Si cualquiera de ambos es nuevo para usted, o no se maneja bien ellos, le recomendamos que comience con una buena introducción a estas tecnologías, antes de adentrarse en Hibernate. A la larga, le ahorrará tiempo y esfuerzo.
Hay otra aplicación a modo de
ejemplo/instructivo en el directorio del código fuente /tutorials/eg. Dicho ejemplo se basa en línea de
comandos (consola), y como tal no depende de un contenedor de servlets para
poder ser ejecutado. El montaje (setup) básico es el mismo que para las
instrucciones a continuación.
Supongamos que tenemos una pequeña aplicación de base de datos que puede almacenar eventos a los que queremos asistir, e información acerca del anfitrión (o anfitriones) de dichos eventos. Vamos a usar una base de datos residente en memoria, llamada HSQLDB, para evitar describir la instalación y configuración de cualquier base de datos en particular. Siéntase en libertad de usar cualquier base de datos con la que esté familiarizado.
Lo primero que tenemos que hacer es montar
nuestro entorno de desarrollo, y, específicamente, instalar todas las
dependencias que Hibernate necesita, así como otras bibliotecas (libraries).
Hibernate se construye usando Maven, el cual, entre otras cosas, provee
manejo de dependencias. Más aún: provee un
manejo transitivo de dependencias, lo
cual simplemente significa que para usar Hibernate podemos definir nuestras
dependencias dentro de él: Hibernate mismo define las dependencias que
necesita, las cuales se convierten en "transitivas".
.
<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">
...
<dependencies>
<dependency>
<groupId>${groupId}</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<!-- Como ésta es una aplicación de web, también necesitamos una dependencia a la API de servlets (servle-api). -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
</dependencies>
</project> Básicamente, aquí estamos describiendo el
archivo /tutorials/web/pom.xml. Vea el sitio
de Maven para más información.
Aunque no es estrictamente necesario, muchos entornos visuales de progrmación (IDEs) ya cuentan con integración con Maven para leer estos archivos POM, y automáticamente generar el proyecto por usted (lo cual puede ahorrar mucho tiempo y esfuerzo).
Luego creamos una clase que representa el evento que queremos almacenar en la base de datos.
Nuestra primera clase persistente es un simple 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;
}
}Se puede ver que esta clase usa la convención estándar de JavBeans para nombrar a sus métodos "setter" y "getter" (escritura y lectura de propiedades, respectivamente). Este diseño es el recomendado - pero no es obligatorio. Hibernate puede aceder a los campos directamente; la ventaja de los métodos de acceso es proveer mayor solidez a la hora de refinar ("refactoring") el código. El constructor sin argumentos sí es obligatorio, para poder instanciar el objeto mediante reflexión.
La propiedad
identificadora o id contiene un identificador
único para un evento en particular. Todas las clases de entidad persistentes
(las hay menos importantes también) necesitarán dicho id, si queremos usar a
pleno las capacides de Hibernate. De hecho, la mayoría de las aplicaciones
(especialmente aplicaciones de web), ya necesitan distinguir objetos por id,
así que esta característica debería considerarse una ventaja, más que una
limitación. De todos modos, usualmente no manipulamos directamente la
identidad de un objeto, así que el método "setter" del id debería ser
privado. Sólo Hibernate asigna ids, cuando el objeto es grabado. Hibernate
puede acceder a métodos en cualquier nivel de acceso (protected, public,
private, etc) directamente. La opción de qué nivel de acceso utilizar es
suya, según el diseño de su aplicación.
El constructor sin argumentos es obligatorio para todas las clases persistentes; Hibernate tiene que crear los objetos para usted utilizando Java Reflection. El constructor puede ser privado; sin embargo, para poder generar "proxies" en tiempo de ejecución, y para la captura de datos sin la construcción de bytecode, se requiere al menos el nivel de acceso "package" o por defecto.
Ponga este archivo de código fuente Java en un directorio llamado
src en el directorio de desarrollo, y dentro
del paquete correspondiente. El directorio debería verse así:
.
+lib
<bibliotecas de Hibernate y de terceros>
+src
+events
Event.javaEn el paso siguiente, le vamos a informar a Hibernate acerca de esta clase persistente.
Hibernate necesita saber cómo cargar y almacenar objetos de la clase persistente. Aquí es donde entra en juego el archivo de mapeo. Éste le dice a Hibernate a qué tabla en qué base de datos tiene que acceder, y qué columnas de dicha tabla tiene que utilizar.
La estructura básica del archivo de mapeo se ve así:
<?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>
[...]
</hibernate-mapping> Nótese que la DTD de Hibernate es bastante
sofisticada. La puede usar para autocompletar elementos de mapeo y atributos
de XML en su indetfaz gráfica (IDE). También puede abrir la el archivo de la
DTD en su procesador de texto - es la forma más fácil de tener una visión
general de todos los elementos, y de ver los valores por defecto, junto con
algunos comentatios. Sepa que Hibernate no buscará la DTD en la red, sino en
el classpath. La DTD está incluida en el archivo, hibernate3.jar, así como en el directorio src/ de la distribución de Hibernate.
En los ejemplos sucesivos, omitiremos la declaración de la DTD, por brevedad. Por supuesto, ésta no es optativa.
Entre las dos tags hibernate-mapping, incluya un elemento class. Todas las clases de entidad persistente (de
nuevo: hay clases dependientes, como veremos luego, que no son entidades de
primer nivel) necesitan dicho mapeo, a una tabla en la base de datos SQL.
<hibernate-mapping>
<class name="events.Event" table="EVENTS">
</class>
</hibernate-mapping> Hasta el momento, le hemos dicho a
Hibernate cómo persistir y cargar un objeto de la clase Event en la tabla EVENTS, cada instancia representando un registro de
la tabla. Ahora continuamos con el mapeo del identificador único a la clave
primaria de la tabla. Por añadidura, como no queremos preocuparnos por
manipular dicho identificador, configuramos una "estrategia de generación de
identificador" de Hibernate para que use una clave primaria "sustituta"
(surrogate key).
<hibernate-mapping>
<class name="events.Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
</class>
</hibernate-mapping> El elemento id es la declaracuión de la propiedad
indentificadora. El atributo name="id" declara
el nombre de la propiedad Java - Hibernate usará los métodos getter y setter
para acceder a ella. El atributo "column" le dice a Hibernate qué columna de
la tabla EVENTS usamos como clave primaria. El
elemento anidado generator especifica la
estrategia para la generación de identificador. En este caso, usamos native, la cual elige la mejor estrategia dependiendo
de la BD y dialecto configurados. Hibernate soporta tanto identificadores
generados por la base de datos, como globalmente únicos, o asignados por la
aplicación (o generados por cualquier estrategia para la cual usted haya
escrito una extensión).
Finalmente, incluimos declaraciones para las propiedades persistentes en el archivo de mapeo. Por defecto, ninguna de las propiedades de la clase se considera persistente.
<hibernate-mapping>
<class name="events.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> Igual que con el elemento id, el atributo name del
elemento property le dice a Hibernate qué
métodos getter y setter usar. Así que, en este caso, Hibernate buscará getDate()/setDate(), así como getTitle()/setTitle().
¿Por qué el mapeo de
la propiedad date incluye el atributo column, pero el title
no? A falta del atributo column, Hibernate por
defecto usa el nombre de la propiedad como nombre de columna. Esto funciona
para title. Pero date es una palabra reservada en la mayoría de las
base de datoss, así que mejor la mapeamos con un nombre diferente.
Otra cosa interesante, es que el mapeo title
también carece del atributo type. Los tipos que
usamos en los archivos de mapeo no son, como es de esperarse, tipos Java.
Tampoco son tipos SQL. A estos tipos se los llama Tipos de mapeo de Hibernate, conversores
que podemos traducir de tipos Java a SQL y viceversa. De nuevo, Hibernate
tratará de determinar la conversión adecuada e incluso el tipo mismo, si el
atributo type no se especifica en el mapeo. En
algunos casos, esta detección automática (que usa Java Reflection) puede no
generar el valor por defecto que usted esperaba o necesita. Ése es el caso
con la propiedad date. Hibernate no puede saber
si la propiedad, que es del tipo java.util.Date, debería ser mapeada a una columna
timestamp, o a una columna time. En este caso, mantengamos la información
completa (de día y hora) mapeando la propiedad al conversor timestamp.
Este achivo de mapeo debería ser
grabado como Event.hbm.xml, justo en el mismo
directorio que el archivo Java de la clase Event. El nombre de los archivos de mapeo puede ser
arbitrario, pero los sufijos hbm.xml son una
convención en la comunidad de programadores de Hibernate. Ahora la
estructura de directorios debería verse así:
.
+lib
<bibliotecase de Hibernate y de terceros>
+src
+events
Event.java
Event.hbm.xmlContinuamos con la configuración principal de Hibernate
Ahora tenemos ubicados una clase persistente
y su archivo de mapeo. Es el momento de configurar Hibernate mismo. Antes de
hacerlo, necesitamos una base de datos. HSQLBD es una base de datos basada
en Java; puede ser descargada del sitio de web de HSQL DB
(http://hsqldb.org/). En ralidad, usted sólo necesita hsqldb.jar de dicha descarga. Coloque este archivo en
el directorio lib/ del directorio de
desarrollo.
Cree un directorio llamado data en la raíz del directorio de desarrollo - es ahí
en donde HSQL DB almacenará sus archivos de datos. Ahora, haga arrancar la
base de datos ejecutando: java -classpath
../lib/hsqldb.jar org.hsqldb.Server en este directorio de datos.
Usted podrá ver que arranca y está ligada a un socket TPC/IP. Ahí es donde
nuestra aplicacíón se conectará luego. Si quiere comenzar con una base de
datos nueva en el transcurso de este instructivo, cierre la base de datos
HSQL DB pulsando CTRL + C en la ventana, borre
todos los archivos en el subdirectorio data/, y
arranque HSQL DB nuevamente.
Hibernate es la capa de su aplicación que se conecta con esta base de datos, así que necesita información de conexión. Las conexiones se hacen mediante un pool de conexiones JDBC, el cual también tenemos que configurar. La distribución de Hibernate contiene varias herramientas de código abierto (open source) que generan pool de conexiones , pero para este instructivo usaremos el pool que ya viene incorporado en Hibernate. Dése cuenta de que, si quiere usar un pool de conexiones JDBC de mayor calidad (para aplicaciones ya instaladas en producción) hecho por terceros, deberá copiar las bibliotecas que hagan falta en el classpath, y usar propiedades de conexión diferentes.
Para la configuración de Hibernate, podemos usar un simple archvo hibernate.properties, un archivo hibernate.cfg.xml un tanto más sofisticado, o incluso
una configuración totalmente programática. La mayoría prefiera el archivo de
configuración 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>
<!-- datos de conexión de la BD -->
<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>
<!-- pool de conexiones JDBC (usamos el que ya viene incorporado) -->
<property name="connection.pool_size">1</property>
<!-- dialecto SQL -->
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- habilita el manejo automátio de contexto de sesión por parte de Hibernate -->
<property name="current_session_context_class">thread</property>
<!-- inhabilita el caché de 2do nivel -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- imprime todo el SQL ejecutado en la salida estándar -->
<property name="show_sql">true</property>
<!-- borra y recrea la BD en cada arranque -->
<property name="hbm2ddl.auto">create</property>
<mapping resource="events/Event.hbm.xml"/>
</session-factory>
</hibernate-configuration> Fíjese en que este archivo de
configuración XML usa una DTD distinta. Configuramos la SessionFactory de Hibernate - una fabrica global
responsable de una base de datos en particular. Si tiene varias base de
datos, use varias configuraciones de <session-factory> por lo común en otros tantos
archivos de configuración (para un arranque más fácil).
Los
primeros 4 elementos property contienen la
configuración necesaria para la conexíón JDBC. La propiedad "dialect"
especifica la variante de SQL en particular que Hibernate genera. El manejo
automático de sesiones por contextos de persistencia será muy útil, como
pronto veremos. La opción hbm2ddl.auto activa
la generación automática de esquemas - directamente en la BD. Esto por
supuesto puede ser desactivado (quitando esta opción) o ser redirigido a un
archivo, con la ayuda de la tarea de Ant SchemaExport. Fnalmente, agregamos el o los archivo
de mapeo para las clases persistentes a la configuración.
Copie
este archivo en el directorio de fuentes (src), de manera que quede en la
raíz del classpath. Hibernate busca automáticamente un arhivo llamado hibernate.cfg.xml en la raíz del classpath, al
arrancar.
Ahora vamos a construir (build) esta aplicación
instructiva usando Ant. Deberá tener Ant ya instalado - obténgalo de la Página de
descarga de Ant. Aquí no vamos a discutir cómo instalar Ant. Por favor
refiérase al Manual de Ant. Después de haber instalado Ant, podemos
crear el archivo de construcción de Ant (build file), que se llamará build.xml, y estará situado directamente en el
directorio de desarrollo.
Un archivo de construcción Ant básico se ve así:
<project name="hibernate-tutorial" default="compile">
<property name="sourcedir" value="${basedir}/src"/>
<property name="targetdir" value="${basedir}/bin"/>
<property name="librarydir" value="${basedir}/lib"/>
<path id="libraries">
<fileset dir="${librarydir}">
<include name="*.jar"/>
</fileset>
</path>
<target name="clean">
<delete dir="${targetdir}"/>
<mkdir dir="${targetdir}"/>
</target>
<target name="compile" depends="clean, copy-resources">
<javac srcdir="${sourcedir}"
destdir="${targetdir}"
classpathref="libraries"/>
</target>
<target name="copy-resources">
<copy todir="${targetdir}">
<fileset dir="${sourcedir}">
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>
</project> Esto le dice a Ant que agregue todos los archivos
del el directorio lib que terminen en .jar al
classpath que usemos para la compilación. También copiará todos los archivos
fuente no-Java al directorio de destino (target), por ejemplo, los archivos
de configuración y mapeo.. Si ejecuta Ant ahora, debería obtener la
siguiente salida:
C:\hibernateTutorial\>ant
Buildfile: build.xml
copy-resources:
[copy] Copying 2 files to C:\hibernateTutorial\bin
compile:
[javac] Compiling 1 source file to C:\hibernateTutorial\bin
BUILD SUCCESSFUL
Total time: 1 second Es hora de cargar y grabar algunos objetos Event,pero primero debemos completar la instalación
(setup) con algo de código "infraestructural". Tenemos que hacer arrancar
Hibernate. Dicho arranque incluye construir un objeto SessionFactory global, y almacenarlo en algún lugar
de fácil acceso para el código de la aplicación. Una SessionFactory puede abrir nuevos objetos Session. Cada objeto Session representa una "unidad de trabajo" de un solo
Thread. El código de SessionFactory es
thread-safe, y es instanciado sólo una vez.
Vamos a crear una clase
de ayuda llamada HibernateUtil, que se
encargará del arranque y hará que el acceso a SessionFactory sea más conveniente. Veamos cómo
implementarla:
package util;
import org.hibernate.*;
import org.hibernate.cfg.*;
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
// Cree la SessionFactory para hibernate.cfg.xml
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
// Asegúrese de loguear la excepción, dado que puede ser "tragada"
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
} Esta clase no sólo produce la SessionFactory global, en un inicializador estático
(invocado una sola vez por la JVM cuando la clase se carga), sino que oculta
el hecho de que se emplea un singleton estático. Podría haber estado
buscando la SessionFactory en el JNDI de un
servidor de aplicaciones, , por ejemplo,.
Si usted le da un nombre
a la SessionFactory en su archivo de
configuración, Hibernate en realidad intentará vincularla a JNDI tras haber
sido creada. Para omitir este código completamente, usted también podría
usar "despliegue JMX" y dejar que el un contenedor habilitado para JMX
instancie y construya un HibernateService en la
JNDI. Estas opciones avanzadas se discuten en la documentación de referencia
de Hibernate.
Coloque HibernateUtil.java en el directorio de código fuente,
en un paquete al lado de events:
.
+lib
<Hibernate y las bibliotecas de terceros>
+src
+events
Event.java
Event.hbm.xml
+util
HibernateUtil.java
hibernate.cfg.xml
+data
build.xml Esto debería poder compilarse sin problemas. Finalmente,
necesitamos configurar un sistema de logueo (bitácora, logging) - Hibernate
usa commons logging y le deja a usted la opción entre log4j y el logging
específico de Java. La mayoría de los programadores prefiere Log4j. Copie
log4j.properties de la distribución de
Hibernate (está en el directorio etc/) a su
directorio src, al lado de hibernate.cfg.xml. Dele un vistazo a la configuración
de ejemplo, y cambie los valores si desea una salida más locuaz. Por
defecto, sólo los mensajes de arranque de Hibernate se muestran en la salida
estándar.
La parte infraestructural del instructivo ha finalizado. Ahora estamos listos para efectuar verdadero trabajo con Hibernate.
Finalmente podemos usar Hibernate
para cargar y grabar objetos . Escribimos una clase EventManager con un método main().
package events;
import org.hibernate.Session;
import java.util.Date;
import 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();
}
} Creamos un objeto Event, y se los
pasamos a Hibernate. Ahora Hibernate se encarga del SQL, y ejecuta INSERTs en la base de datos. Echémosle un vistazo a
la sesión, y al código de manejo de transacciones antes de ejecutarlo.
Usa sesión (Session) es una unidad de
trabajo. Por ahora mantendremos todo simple y asumiremos una correspondencia
uno-a-uno entre una sesión de Hibernate y una transacción de BD. Para
"escudar" nuestro codigo respecto del sistema subyacente de transacciones
(en este caso, sólo JDBC, pero podría haber sido JTA), usamos la API para
transacciones que está disponible en la clase Session.
¿Qué hace sessionFactory.getCurrentSession()? Primero, a este
método se lo puede llamar desde dondequiera, y cuantas veces se desee, una
vez que obtenemos una SessionFactory.
(fácilmente, gracias a la HibernateUtil). El código getCurrentSession() siempre devuelve la unidad
"actual" de trabajo. ¿Recuerda que configuramos una opción con valor
"thread" en hibernate.cfg.xml? Debido a esto,
la unidad actual de trabajo está ligada al thread de Java que se esté
ejecutando en ese momento en su aplicación. De todos modos, ésta no es toda
la historia: también hay que considerar el alcance (scope), cuándo una
unidad de trabajo empieza y cuándo termina.
Una sesión comienza
cuando se la necesita por primera vez, cuando se hace la primera llamada a
getCurrentSession(). Entonces, es ligada al
thread actual por Hibernate. Cuando la transacción termina, Hibernate
desliga la sesión del thread, y la cierra por usted. Si usted llama getCurrentSession() de nuevo, obtiene una nueva
sesión y comienza una nueva unidad de trabajo. El modo de programación
"ligado a threads" (thread-bound), es la forma más difundida de usar
Hibernate, dado que permite una distribución en capas muy flexible: el
código de delimitación de transacciones puede separarse del código de acceso
a datos, como veremos más adelante.
En relación al alcance de la unidad de trabajo: Una sesión ¿debería usarse para ejecutar una sola operación de base de datos, o varias? El ejemplo precedente usa una sesión para una operación. Esto es simple casualidad, el ejemplo no es lo suficientemente complejo como para demostrar ningún otro enfoque. El alcance de una sesión de Hibernate es flexible, pero nunca se debe designar una aplicación de maneera que utilice una sesión para cada operación de base de datos. Así que, incluso si usted lo ve en algunos pocos de los ejemplos siguientes, considere la práctica de "una sesión por operación" como algo a evitar (un "anti-pattern"). Una aplicación real (de web) se analiza más adelante en este instructivo.
Échele un vistazo al capítulo Capítulo 11, Transacciones y Concurrencia acerca del manejo de transacciones y su delimitación. También hemos salteado cualquier manejo de errores en el ejemplo precedente.
Para ejecutar esta primera rutina, tenemos que agregar una "target" invocable al archivo de construcción de Ant.
<target name="run" depends="compile">
<java fork="true" classname="events.EventManager" classpathref="libraries">
<classpath path="${targetdir}"/>
<arg value="${action}"/>
</java>
</target>El valor del argumento "action" se asigna en la línea de comandos cuando esta target se invoca.
C:\hibernateTutorial\>ant run -Daction=store
Después de la compilación, usted debería ver que que Hibernate arracna, y, dependiendo de su configuración, un montón de salida de logueo. Al final encontrará la siguiente línea:
[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)
Ése es el código INSERT ejecutado por
Hibernate. Los signos de interrogación representan parámetros JDBC ligados.
Para ver los valores de dichos parámetros, o para reducir la locuacidad del
archivo de log, revise su archivo log4j.properties.
Ahora, querríamos tanbién listar los eventos almacenados, así que agregamos una opción el el método principal:
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());
}
} También agregamos un nuevo método listEvents() method:
private List listEvents() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
List result = session.createQuery("from Event").list();
session.getTransaction().commit();
return result;
}Lo que hicimos aquí, es usar el lenguaje de consultas de Hibernate (HQL, por sus siglas en inglés) para cargar todos los objetos Event que existen en la base de datos. Hibernate generará el código SQL que haga falta, lo enviará la base de datos, y poblará los objetos Event con los datos que sean devueltos. Se pueden crear consultas SQL mucho más complejas con HQL, por supuesto.
Ahora, para ejecutar y chequear todo esto, siga estos pasos:
Ejecute ant run -Daction=store para
almacenar algo en la base de datos y, por supuesto, para previamente
generar el esquema de base de datos mediante hbm2ddl.
Ahora inhabilite hbm2ddl (convirtiendo la propiedad en un
comentario) en el archivo hibernate.cfg.xml. Por lo general, sólo se la
deja habilitada cuando se efectúa un "unit testing continuo", pero en
este caso, dejarla habilitada borraría todo lo que usted haya almacenado
hasta ese momento. (el valor de hbm2ddl="create" se traduce como "haga
un DROP de todas las tablas del esquema, y recree todas las tablas
cuando la SessionFactory sea construida")
Si usted ahora invocara Ant con -Daction=list, debería ver los eventos que haya
almacenado hasta ese moento. Por supuesto, puede también invocar la acción
store un par de veces más.
Nota: A esta altura, la mayoría de los usuarios de Hibernate experimenta problemas, y aparecen seguido mensajes del tipo Table not found . De todos modos, si usted sigue los pasos que acabamos de describir cuidadosamente, no tendrá este problema, ya que hbm2ddl crea el esquema de base de datos la primera vez, y las veces subsiguientes en que la aplicación recomienza utilizan dicho esquema. Si usted en algún momento cambia algo del mapeo o del esquema, debe rehabilitar hbm2dll nuevamente para recrear la BD.
Hemos mapeado una clase persistente a una tabla. Ahora, partiendo de esta base, agreguemos algunas asociaciones de clase. Primero, agregaremos algunas personas a nuestra aplicación, y almacenaremos una lista de eventos en los cuales participan.
El primer bosquejo de la clase Person es simple:
package events;
public class Person {
private Long id;
private int age;
private String firstname;
private String lastname;
public Person() {}
// métodos "getter" y "setter" de acceso, y setter privado para 'id'
} Cree un nuevo archivo de mapeo llamado Person.hbm.xml (y no olvide la referencia a la DTD en
la parte superior):
<hibernate-mapping>
<class name="events.Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
</class>
</hibernate-mapping>Por último, agreguemos el nuevo archivo de mapeo a la configuración de Hibernate.
<mapping resource="events/Event.hbm.xml"/> <mapping resource="events/Person.hbm.xml"/>
Y ahora crearemos una asociación entre estas dos entidades, Obviamente, las personas pueden participar en eventos, y los eventos tienen participantes. Las cuestiones de diseño con las que tenemos que lidiar son: multiplicidad, direccionalidad, y comportamiento de las colecciones.
Agregaremos
una colección de eventos a la clase Person. De
este modo podremos navegar cómodamente los eventos para una determinada
persona sin tener que ejecutar una consulta explícita, simplemente llamando
aPerson.getEvents(). Usamos una colección de
Java, un Set, porque esta colección no contendrá elementos duplicados, y el
orden no es relevante para nosotros.
Necesitamos una asociación unidireccional y de varios valores, que pueda ser implementada con un Set. Escribamos el código para esto en las clases de Java, y luego mapeémoslas.
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 asociación, piense en el otro lado (los
eventos). Claramente, podemos simplemente mantener esta asociación
unidireccional. O si no, podríamos crear otra colección en la clase Event,
si deseamos navegar en forma bidireccional, es decir, anEvent.getParticipants(). Esto no es estrictamente
necesario, desde un punto de vista funcional, siempre se puede ejecutar una
consulta explícita para obtener los participamntes de un determinado evento.
Esta es una opción de diseño que le dejamos a usted; pero lo que sacamos en
limpio de esta discusión, es la multiplicidad de la asociación: hay "muchos
valores" desde ambos lados. A esto se lo llama una asociación
"de-muchos-a-muchos" (many-to-many).
Por esto, usamos el mapeo many-to-many de Hibernate.
<class name="events.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="events.Event"/>
</set>
</class> Hibernate soporta todo tipo de mapeos de coleccíón,
siendo el "set" el más común. Para una asociación "many-to-many", o relación
entre entidades, se necesita una tabla de asociación. Cada registro de esta
tabla simboliza un vínculo entre una persona y un evento. El nombre de esta
tabla se configura con el atributo table del
elemento set. La columna indentificadora por el
lado de las personas, se define con el elemento <key>, el nombre de la columna por el lado de
los eventos, con el atributo column del <many-to-many>. Usted también debe decirle a
Hibernate la clase de objetos de su colección.
El esquema de base de datos para este esquema es, entonces:
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | |
|_____________| |__________________| | PERSON |
| | | | |_____________|
| *EVENT_ID | <--> | *EVENT_ID | | |
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE |
|_____________| | FIRSTNAME |
| LASTNAME |
|_____________|
Juntemos a alguna gente con sus eventos, en
un nuevo método de 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();
} Después de cargar una Person y un
Event, simplemente modifique la colección
usando los métodos normales de las colecciones. Como puede usted ver, no hay
llamados explícitos a update() o a save(), Hibernate detecta automáticamente que la
colección ha sido modificada y necesita ser actualizada. Esto se llama
"dirty checking automático", y usted puede experimentar con ello cmabiando
la propiedad "date" en cualquiera de sus objetos. Siempre y cuando se
mantengan en un estado persistente (es decir, ligados a una sesión de
Hibernate, por haber sido cargados o grabados en una unidad de trabajo),
Hibernate monitoreará cualquier cambio y ejecutará SQL entre bambalinas. El
proceso de sincronizar el estado de lo que está en memoria con la base de
datos, generalmente al final de la unidad de trabajo, se denomina "nivelar,
desagotar" la sesión (en inglés, flush). En nuestro código, la unidad de
trabajo termina con un "commit" (o "rollback") de la transacción de base de
datos, tal como se define por la opción de configuración thread para la clase CurrentSessionContext.
Se podría, por supuesto, cargar personas y eventos en distintas unidades de trabajo. O modificar un objeto fuera de una sesión, cuando no está en estado persistente (si lo estuvo anteriormente, este estado se llama "desprendido", en inglés detached). Incluso una colección se puede modificar estando en este estado desprendido.
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();
// fin de la primera unidad de trabajo
aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached
// comienzo de la segunda unidad de trabajo
Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();
session2.beginTransaction();
session2.update(aPerson); // reasociación de aPerson, que estaba desprendida
session2.getTransaction().commit();
} El llamado a update convierte de nuevo
en persistente un objeto que estaba desprendido. Se podría decir que lo liga
a una nueva unidad de trabajo, de manera que cualquier modificación que se
le hubiera hecho mientras estaba desprendido pueda ser grabada en la base de
datos. Esto incluye cualquier modificación que se le hubiera hecho a la
colección de ese objeto entidad.
Bueno, esto no es muy útil en la
situación actual, pero es un concepto importante, que usted puede aplicar al
diseño de su propia aplicación. Por ahora, simplemente complete este
ejercicio agregando una nueva acción al método main() de EventManager, e invóquelo desde la línea de comandos.
Si necesita los identificadores de una persona y de un evento, el método
save() los devuelve (tal vez usted deba
modificar alguno de los métodos previos, para que devuelvan 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);
} Éste fue un ejemplo de una asociación entre dos clases igualmente
importantes, dos "entidades". Como se mencionó anteriormente, hay otras
clases y tipos "menos importantes" en un modelo típico. Algunos, usted ya
los ha visto, como los int o String. A esas clases las llamamos value types, y sus instancias dependen de
una entidad en particular. Las instancias de estos tipos no tienen su propia
identidad, ni se pueden compartir entre entidades (dos personas no pueden,
por ejemplo, hacer referencia al mismo objeto firstname o primer nombre, incluso si son tocayos).
Por supuesto, estos value types
pueden ser encontrados no solamente en la JDK. De hecho, en una aplicación
Hibernate todas las clases JDK son consideradas "value types", pero usted
puede escribir sus propias clases dependientes, para representar una
dirección o una cantidad de dinero, por ejemplo.
También se puede diseñar una colección de "value types". Esto es conceptualmente bien distinto de una colección de referencias a otras entidades, pero en el código Java ambas se ven casi igual.
Agreguemos una colección de objetos
"value type" a la entidad Person. Queremos
almacenar direcciones de correo electrónico (email), de manera que el tipo
que usaremos es String, y la colección, de
nuevo un Set.
private Set emailAddresses = new HashSet();
public Set getEmailAddresses() {
return emailAddresses;
}
public void setEmailAddresses(Set emailAddresses) {
this.emailAddresses = emailAddresses;
} El mapeo de este Set:
<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
<key column="PERSON_ID"/>
<element type="string" column="EMAIL_ADDR"/>
</set> La diferencia, comparada con el mapeo anterior, es la
parte element, la cual le dice a Hibernate que
la colección no contiene referencias a otra entidad, sino a una colección de
elementos de tipo string (al estar con
minúscula nos damos cuenta de que es un tipo/conversor de Hibernate). Une
vez más, el atributo table del set determina el
nombre de la tabla para la colección. El elemento key define el nombre de columna para la clave
foránea, El atributo column de "element" define
en dónde estas cadenas van a ser almacenadas.
Echémosle un vistazo al nuevo esquema de DB
_____________ __________________
| | | | _____________
| 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 |
|_____________|
Se puede ver que la clave primaria de la tabla de la colección es, en realidad, una clave compuesta, que usa ambas columnas. Esto también implica que no puede haber direcciones de email duplicadas para una misma persona, lo cual es precisamente la semántica de un conjunto o "Set" en Java.
Ahora podemos intentar agregar elementos a esta colección, igual que como hicimos antes al vincular personas y eventos. El código Java es el mismo:
private void addEmailToPerson(Long personId, String emailAddress) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
// el getEmailAddresses() podría disparar una carga "haragana" de la colección
aPerson.getEmailAddresses().add(emailAddress);
session.getTransaction().commit();
}Esta vez, no utilizamos una consulta con fetch para inicializar la colección. De ahí que la invocacíón de su método "getter" disparará un SELECT adicional para inicializarla, de manera que podamos agregarle un elemento. Monitoree el log de SQL e intente optimizar esto con un "eager fetch" (captura ansiosa).
Acto seguido, vamos a mapear una asociación bidireccional, es decir, vamos a hacer que la asociación funcione desde ambos lados en Java. Desde luego, el esquema de base de datos no cambia, todavía tenemos una multiplicidad de-muchos-a-muchos (many-to-many). Una base de datos relacional es más flexible que un lenguaje de programación, no necesita cosas como una "dirección de navegación", los datos pueden ser adquiridos de cualquier manera posible.
Primero,
agregue una colección de participantes al código de la clase Event.
private Set participants = new HashSet();
public Set getParticipants() {
return participants;
}
public void setParticipants(Set participants) {
this.participants = participants;
} Ahora, mapee también este lado de la asociación, 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> Como puede ver, éstos son mapeos set normales en ambos documentos de mapeo. Fíjese en
que los nombres de columna en ambos documentos han sido alternados. La
adición más importante es el atributo inverse="true" en el elemento set del mapeo de la colección de Event.
Lo que esto significa, es que Hibernate debería rferirse al otro lado (el lado "Person"), cuando necesite obtener información sobre el vínculo entre los dos. Esto será más fácil de entender una vez que veamos cómo se crea el vínculo bidireccional entre las dos entidades.
Primero que
nada, tenga en cuenta que Hibernate no afecta la semántica normal de Java,
¿Cómo habiamos creado un vínculo entre personas y eventos en el ejemplo
unidireccional? Agregando una instancia de Event a la colección de referencias contenida en una
instancia de Person. Así que, obviamente, si
queremos que este vínculo funcione también en sentido inverso, tenemos que
hacer lo mismo del otro lado: agregar una referencia a Person a la colección en Event. Esto de "establecer el vínculo de los dos
lados" es absolutamente necesario, y usted jamás debe olvidarse de hacerlo.
Muchos programadores trabajan "defensivamente", y crean métodos
facilitadores que asignan correctamente los vínculos a ambos lados. Por
ejemplo, en la clase 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);
}Fíjese en que los métodos "get" y "et" de la colección ahora son "protected" - esto les permite a las clases en el mismo paquete, y a las subclases, acceder a los métodos, pero les impide a todos los demás inmiscuirse con estas colecciones directamente (bueno... casi). Usted debería, probablemente, hacer lo mismo con las colecciones del otro lado.
Y ¿qué hay del atributo de mapeo inverse? Para usted (y para Java), un vínculo
bidrecciolal es simplemente cuestión de asignar correctamente las
referencias en ambos lados .Hibernate, sin embargo, no tiene información
suficiente como para organizar adecuadamente sus llamados a comandos SQL
INSERT y UPDATE. Marcar uno de los lados de la asociación como inverse, básicamente le dice a Hibernate que lo
ignore, que lo considere como un espejo de lo que ocurre en el otro lado.
Eso es todo lo que se necesita para que Hibernate resuelva todos los
problemas que derivan de transformar un modelo de navegación bidireccional
en un esquema de base de datos. Las reglas que usted denbe recordar son son
simples: Toda asociación bidireccional necesita que uno de sus lados sea
"inverse". En una asociación de-uno-a-muchos (one-to-many) tiene que ser el
lado "many". En una asociación de-muchos-a-muchos (many to many), elija
cualquier lado, da lo mismo.
Convirtamos la siguiente discusión en una pequeña aplicación de web.
Una
aplicación de web Hibernate utiliza sesiones y transacciones, lo mismo que
la aplicacióm autosuficiente que vimos anteriormente. Sin embargo, es
conveniente usar algunos patrones de programación ("patterns"). Escribamos
un servlet EventManagerServlet que pueda listar
los eventos alamacenados en la base de datos, y provea un formulario HTML
para ingresar nuevos eventos.
Cree una nueva clase en su directorio de código
fuente, en el paquete events:
package events;
// Imports
public class EventManagerServlet extends HttpServlet {
// Servlet code
} Este servlet maneja requests HTTP del tipo GET solamente, así que el método que implementamos es
doGet():
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
SimpleDateFormat dateFormatter = new SimpleDateFormat("dd.MM.yyyy");
try {
// comienzo nuestra unidad de trabajo
HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();
// procesamiento de la solicitud (request) y presentación de la página...
// fin de la unidad de trabajo
HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit();
} catch (Exception ex) {
HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback();
throw new ServletException(ex);
}
} El "pattern" o patrón de programacíón que estamos aplicando aquí se
llama session-per-request, (una
sesión por cada "solicitud" o request HTTP). Cuando una nueva request apela
al servlet, se abre una nueva sesión de Hibernate mediante el primer llamado
a getCurrentSession() de la SessionFactory. Entonces, ss inicia una transacción
de base de datos (todo el código de acceso a datos ocurre dentro de la
transacción, no importa si para lectura o escritura; en las aplicaciones no
usamos auto-commit).
Nunca
cree una nueva sesión de Hibernate para cada operación de base de datos. Use
una sesión de Hibernate a lo largo de toda la request, es decir, que tenga
un "alcance" de toda la sesión. Use getCurrentSession(), de manera que quede
automáticamente ligada al Thread actual.
A continuación, las acciones posibles de la request son procesadas, y es generada la respuesta HTML. Enseguida nos dedicaremos a esa parte.
Finalmente, la "unidad
de trabajo" finaliza, cuando el procesamiento y presentación hayan sido
completados. Si ocurrió algún problema durante este procesamiento o
presentación, se lanzará una excepción y la se ejecutará un "rollback" de la
transacción. Esto cubre el "pattern" session-per-request. En lugar de escribir código para
demarcar la transacción en cada request, usted debería crea un Servlet
Filter. Refiérase al sitio de web de Hibernate y a la Wiki para más
información acerca de este "pattern", llamado Open Session in View u OSIV, algo así como
"apertura de una sesión por cada vista". Este patrón se necesitará en cuanto
usted considere presentar sus "vistas" usando JSP en lugar de servlets.
Implementemos el procesamiento de la solicitud y la presentación de la página.
// escribe el header encabezado HTML
PrintWriter out = response.getWriter();
out.println("<html><head><title>Event Manager</title></head><body>");
// maneja las acciones
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>");
}
}
// imprime la página
printEventForm(out);
listEvents(out, dateFormatter);
// escribe el pie HTML
out.println("</body></html>");
out.flush();
out.close();Concedido, este tipo de escritura de código, mezclando Java y HTML, sería inadmisible en aplicaciones más complejas, tenga en cuenta que sólo estamos ilustrando conceptos básicos de Hibernate en este instructivo. El código imprime un encabezado y un pie de página en HTML. Dentro de la página propiamente dicha, se imprime un formulario (form) para el ingreso de eventos. El primer método es trivial, y sólo produce 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 método listEvents() usa la sesión
de Hibernate ligada al Thread actual para ejecutar una consulta:
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>");
for (Iterator it = result.iterator(); 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>");
}
} Finalmente, la acción store es
despachada al método createAndStoreEvent(), el
cual usa la sesión del Thread actual.
protected void createAndStoreEvent(String title, Date theDate) {
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
HibernateUtil.getSessionFactory().getCurrentSession().save(theEvent);
} Eso es todo. El servlet está completo. Una request al servlet será
procesada en una única sesión y transacción. Tal como ocurrió en el ejemplo
anterior, Hibernate puede ligar estos objetos al Thread actual de ejecución.
Esto le da a usted la libertad de acceder a la SessionFactory de cualquier manera que guste.
Normalmente, usted usaría un diseño más sofisticado, y movería el código de
acceso a datos (DAO, por sus siglas en inglés) a otra "capa". Refiérase a la
Wiki de Hibernate para más ejemplos.
Para desplegar (deploy) esta aplicación, tiene que
crear un archivo ".war". Agregue la siguiente target de Ant a su archivo
build.xml:.
<target name="war" depends="compile">
<war destfile="hibernate-tutorial.war" webxml="web.xml">
<lib dir="${librarydir}">
<exclude name="jsdk*.jar"/>
</lib>
<classes dir="${targetdir}"/>
</war>
</target> Esta target crea un archivo llamado hibernate-tutorial.war en su directorio de proyecto.
Empaqueta todas las bibliotecas y el descriptor web.xml, el cual se espera que esté en el directorio
base de su proyecto.
<?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>events.EventManagerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Event Manager</servlet-name>
<url-pattern>/eventmanager</url-pattern>
</servlet-mapping>
</web-app> Antes de compilar y desplegar, como ésta es una
aplicación de web, note que se necesita una biblioteca adicional : jsdk.jar. Es el kit de herramientas o "developer kit"
para los servlets de Java. Si aún no tiene esta biblioteca, descárguela del
sitio de web de Sun y colóquela en su directorio de bibliotecas (lib). De
todos modos, se la usa para compilar solamente, no se incluye en los
paquetes WAR.
Para construir (build) y desplegar (deploy), invoque
el comando ant war situado en el directorio de
su proyecto, y luego copie el archivo hibernate-tutorial.war en el directorio webapp de Tomcat. Si no tiene Tomcat instalado,
descárguelo y siga las instrucciones. No es necesario que cambie nada en la
configuración de Tomcat para que este ejemplo ande.
Una vez
desplegada y con Tomcat andando, accceda a la aplicación en http://localhost:8080/hibernate-tutorial/eventmanager.
Asegúrese de monitorear el log de Tomcar para comprobar si Hibernate se
inicializa cuando ocurre la primera solicitud (request) a su servlet (tiene
que ejecutarse el inicializado estático en HibernateUtil), y también asegúrese de obtener una
mensaje detallado si ocurre algún error.
Este instructivo cubrió lo básico para escribir una simple aplicación Hibernate autosuficiente, y una pequeña aplicación de Web.
Si ya va tomando confianza con Hibernate, continúe hojeando la tabla de contenidos en la documentación de referencia, buscando temas que le interesen. Los más solicitados son el procesamiento de transacciones (Capítulo 11, Transacciones y Concurrencia), la performance de las capturas de datos o "fetch performance" (Capítulo 19, Mejorar la performance), el uso de las API (interfaces de programación) (Capítulo 10, Trabajar con objetos) y las características de las consultas (Sección 10.4, “Consultar”).
No olvide chequear el sitio de web de Hibernate en busca de más instructivos especializados.
Una vista a vuelo de pájaro de la arquitectura de Hibernate

Este diagrama muestra a Hibernate usando datos de configuración y base de datos para proveerle servicios de persistencia (y objetos persistentes) a la aplicación.
Nos gustaría mostrar una vista más detallada de la arquitectura en tiempo de ejecución. Lamentablemente, Hibernate es tan flexible, que soporta muchas estrategias. Vamos a mostrar los dos extremos: la arquitectura "liviana" fuerza a la aplicación a proveer sus propias conexiones JDBC y a gerenciar sus propias tranascciones. Esta arquitectura usa un subconjunto mínimo de las APIs de Hibernate.

La arquitectura "con todas las luces" abstrae la aplicación, alejándola de las capas subyacentes de JDBC/JTA, y deja que Hibernate se encargue de los detalles.

He aquí algunas definiciones de los objetos en los diagramas:
org.hibernate.SessionFactory) Un caché thread-safe e inmutable de mapeos, compilados para una
base de datos en particular. Una fábrica (factory) de sesiones, y
cliente de ConnectionProvider. Puede
contener un caché optativo (llamado "de 2do nivel") que es
reusable entre transacciones, a nivel de proceso o de cluster.
org.hibernate.Session)Un objeto de thread simple y de corta visa, que representa una conversación entre la aplicación y el repositorio persistente. Envuelve a una fábrica de conexiones JDBC por transacción. Contiene un caché obligatorio (llamado "de primer nivel") de objetos persistentes, que se usa al navegar el árbol de objetos, o cuando se buscan los objetos por identificador.
Objetos de un solo thread y corta vida, que contienen "estado" persistente y cumplen una función de negocio. Pueden ser JavaBeans comunes, o puros y simples objetos de Java (POJOs por sus siglas en inglés), la única característica notable que tienen, es que están asociados con una sesión. En cuanto la sesión se cierra, serán desprendidos, y quedarán listos para ser usados en cualquier capa de la aplicación (por ejemplo, directamente como objetos de transmisión de datos o "DTOs", desde y hacia la capa de presentación).
Instancias de clases persistenteses que, por el momento, no están asociadas con una sesión. Pudieron haber sido instanciadas por la aplicacíón, y aún no haber sido asociadas con una sesión, o bien haber sido instanciadas por una sesión que en ese momento esté cerrada.
org.hibernate.Transaction)(Optativo) un objeto de un solo thread y corta vida, usado por la aplicacíon para especificar unidades atómicas de trabajo. Abstrae a la aplicación de la transacción JDBC, JTA o CORBA subyacente. Una sesíón puede extenderse a lo largo de varias transacciones en algunos casos. ¡Pero, sea como sea, el código para demarcar transacciones (ya sea utilizando APIs subyancentes o la interfaz Transaction) nunca es optativo!
org.hibernate.connection.ConnectionProvider) (Optativo) Una fábrica y repositorio (pool) de conexiones JDBC.
Abstrae la aplicación de la fuente de datos (Datasource) o del gerente de driver (DriverManager) subyacentes. No está expuesto
directamente a la aplicación, pero puede ser implementado o extendido
por el programador.
org.hibernate.TransactionFactory) (Optativo) Una fábrica de instancias de Transaction. No está expuesta directamente a la
aplicación, pero puede ser implementada o extendida por el programador.
Hibernate ofrece varias interfaces de extensión optativoes, se pueden implementar para personalizar el comportamiento de la capa de persistencia. Vea la documentación de la API para más detalles.
En la arquitectura "liviana", la aplicación se
saltea las APIs de Transaction/TransactionFactory y/o ConnectionProvider APIs para dialogar con JTA o JDBC
directamente.
Una instancia de una clase persistente puede estar en uno de tres estados diferentes, los cuales se definen con respecto a un contexto de persistencia: la sesión.
La instancia no está asociada con ningún "contexto de persistencia" (sesión), ni nunca lo ha estado. Carece de "identidad persistente", es decir, de clave primaria.
La instancia está al momento asociada con un contexto de persistencia. Tiene identidad persistente (valor de clave primaria) y, tal vez, un valor correspondiente en la base de datos. Para un contexto de persistencia determinado, Hibernate garantiza que la identidad persistente equivale a la "identidad Java" (ubicación en memoria del objeto).
La instancia estuvo alguna vez asociada con un contexto de persistencia, pero dicho contexto está cerrado, o la instancia ha sido serializada a otro proceso. Tiene una identidad persistente y, tal vez, el correspondiente registro en la base de datos. Hibernate no ofrece ninguna garantía acerca de la relación entre identida persistente e identidad Java.
JMX es el estándar de J2EE para la administración de componentes
Java. Hibernate puede ser administrado via un servicio JMX estándar.
Nosotros proveemos una implementación de MBean en la distribución: org.hibernate.jmx.HibernateService.
Para un ejemplo sobre cómo desplegar Hibernate como un servicio JMX en el servidor de aplicaciones JBoss, por favor vea la guía del usuario de JBoss. En el servidor de applicaciones JBoss, también se obtienen los siguientes beneficios si se despliega (deploy) usando JMX:
Manejo de sesiones: El
ciclo de vida de una sesión de Hibernate puede ser automáticamente
ligado al alcance (scope) de una transacción JTA. Esto significa que
usted ya no necesita abrir y cerrar manualmente las sesiones, de esto se
encarga el interceptor de JBoss. Tampoco debe preocuparse ya por
demarcar la transacción en su código (a menos que quiera escribir usted
mismo una capa de persistencia portátil, use la API Transaction de Hibernate para eso). Puede llamar
a HibernateContext para acceder a una
sesión.
Despliegue del Archivo de Hibernate
(HAR por sus siglas en inglés): Normalmente se despliega el
servicio JMX de Hibernate usando el "descriptor de despliegue"
(deployment descriptor), en un archivo EAR o SAR, el cual soporta las
opciones de configuración de una SessionFactory de Hibernate. De todos modos, para
esto aún se debe nombrar a todos los archivos de mapeo en el descriptor
de despliegue. En cambio, si decide usar el despliegue HAR, JBoss
detecta automáticamente todos los archivos de mapeo en su archivo HAR.
Consulte la guía del usuario del servidor de aplicaciones JBoss para mayor información acerca de estas opciones.
Otra característica disponible en forma de servicio JMX son las estadísticas de Hibernate en tiempo de ejecución. Vea la Sección 3.4.6, “Estadísticas de Hibernate”
Hibernate también puede ser configurado como un conector JCA. Por favor, vea el sitio de web para más detalles, pero note que el soporte Hibernate de JCA se considera todavía experimental.
La mayoría de las aplicaciones
que usan Hibernate necesitan alguna forma de sesión "contextual", donde una
sesión determinada esté en efecto a lo largo del "scope" o alacance de un
contexto determinado. De todos modos, entre las distintas aplicaciones, la
definición de lo que constituye un "contexto" suele diferir, así como la
definición de qué constituye el contexto "actual". Las aplicaciones que
usaban Hibernate antes de la versión a 3.0, tendían a utilizar o bien
versiones "caseras" de sesión contextual (basadas en ThreadLocal) o bien utilizaban frameworks de terceras
partes (como Spring o Pico), los cuales proveían sesiones contextuales
basadas en proxies e intercepción.
Empezando con la versión 3.0.1,
Hibernate incorporó el método SessionFactory.getCurrentSession(). Inicialmente,
este método asumía el uso de transacciones JTA, donde la transacción JTA
definía tando el alcance como el contexto de la sesión actual.
El
equipo de Hibernate mantiene que, dada la madurez de las numerosas
implementaciones autosuficientes de JTA que existen hoy día, la mayoría (si
no todas) las aplicaciones deberían estar usando JTA para su manejo de
transacciones (estén o no desplegadas en un contenedor J2EE). Basándose en
eso, las sesiones contextuales JTA son todo lo que usted debería necesitar
usar.
Sin embargo, a partir de la versión 3.1, el proceso que
ocurre por detrás de SessionFactory.getCurrentSession() es configurable.
Con este fin, una nueva interfaz de extensión (org.hibernate.context.CurrentSessionContext) y un
nuevo parámetro de configuración (hibernate.current_session_context_class) han sido
agregados, para permitir "enchufar" implementaciones nuevas para definir el
alcance y contexto de las sesiones.
Vea los Javadocs acerca de la
interfaz org.hibernate.context.CurrentSessionContext, para una
descripción detallada del contrato que ésta implica. Define un solo método,
currentSession(), por el cual la implementación
es responsable de rastrear la sesión contextual actual. Hibernate ya viene
con 3 implementaciones incuidas de esta interfaz.
org.hibernate.context.JTASessionContext - las
sesiones actuales son localizadas y su alcance definido por una
transacción JTA. El procesamiento aquí es exactamente el mismo que en el
antiguo enfoque sólo-JTA. Vea los Javadocs para más detalles.
org.hibernate.context.ThreadLocalSessionContext -
las sesiones actuales son localizadas por thread de ejecución. De nuevo,
vea los Javadocs para más detalles.
org.hibernate.context.ManagedSessionContext - las
sesiones actuales son localizadas por thread de ejecución. Sin embargo,
usted es responsable por ligar y desligar las instancias de sesión,
mediante métodos estáticos en esta clase. Nunca abre, cierra o le aplica
flush a una sesión.
Las dos primeras implementaciones proveen un modelo de
programación del tipo "una sesión - una transacción de base de datos",
también llamado session-per-request.
El comienzo y fin de una sesión de Hibernate está definido por la duración
de la transacción de base de datos. Si usted usa demarcación programática de
transacciones, en simple JSE sin JTA, se le aconseja que use la API de Transaction de Hibernate, para ocultar el sistema de
transacciones subyacente. Si el código se está ejecutando en un contenedor
EJB que soporte CMT, los límites de la transacción son definidos
declarativamente, y usted no necesita ninguna transacción ni demarcación de
operaciones en el código propiamente dicho. Refiérase al Capítulo 11,
Transacciones y Concurrencia para más información y ejemplos de
código.
El parámetro de configuración hibernate.current_session_context_class define cuál
implementación de org.hibernate.context.CurrentSessionContext debe ser
usada. Note que, por compatibilidad con versiones anteriores, si este
parámetro de configuración no se asigna, sino que se configura org.hibernate.transaction.TransactionManagerLookup en
su lugar, Hibernate usará el org.hibernate.context.JTASessionContext. Típicamente,
el valor de este parámetro simplemente nombra la clase de implementación a
usar; para las tres implementaciones de fábrica existen los respectivos
nombres cortos: "jta", "thread" y "managed".
Como Hibernate está diseñado para operar en
varios entornos diferentes, hay un gran número de parámetros de
configuración. Afortunadamente, la mayoría tiene valores por defecto
razonables, e Hibernate es distribuido con un archivo hibernate.properties de ejemplo en el directorio
etc/, que muestra varias de las opciones.
Simplemente coloque este ejemplo en su classpath y modifíquelo a medida.
Una instancia de org.hibernate.cfg.Configuration representa un
conjunto completo de mapeos desde los tipos Java de una aplicación, hacia
una base de datos SQL. org.hibernate.cfg.Configuration se usa para
construir una org.hibernate.SessionFactory inmutable. Los
mapeos son compilados a partir de los varios archivos de mapeo XML.
Se puede ontener una instancia de org.hibernate.cfg.Configuration instanciándola
directamente, y especificando los archivos de mapeo XML. Si los archivos de
mapeo están en el classpath, utilice addResource():
Configuration cfg = new Configuration()
.addResource("Item.hbm.xml")
.addResource("Bid.hbm.xml");Una alternativa (a veces preferible), es especificar la clase mapeada, y dejar que Hibernate encuentre el documento de mapeo él solo:
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class); Entonces Hibernate
buscará archivos de mapeo llamados /org/hibernate/auction/Item.hbm.xml y /org/hibernate/auction/Bid.hbm.xml en el calsspath.
Este enfoque elimina la necesidad de "hardcodear" los nombres de archivo.
org.hibernate.cfg.Configuration
también permite especificar propiedades de configuración:
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class)
.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect")
.setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test")
.setProperty("hibernate.order_updates", "true");Ésta no es la única manera de pasarle propiedades de configuración a Hibernate. Entre las muchas otras opciones, están éstas:
Pasarle una instancia de java.util.Properties a Configuration.setProperties().
Colocar un archivo llamado hibernate.properties en un directorio raíz del
classpath.
Configurar propiedades "de sistema" (System) usando java
-Dproperty=value.
Incluir elementos <property>
en hibernate.cfg.xml (se discute más
adelante).
hibernate.properties es el
enfoque más fácil, si lo que se busca es empezar rápido.
La idea de
org.hibernate.cfg.Configuration es ser un
objeto que viva durante el "tiempo de arranque" (startup), para ser
descartado luego, una vez que la SessionFactory
haya sido creada.
Una vez que todos los mapeos hayan sido
revisados por org.hibernate.cfg.Configuration, la aplicación debe
obtener una fábrica o "SessionFactory" para las instancias de org.hibernate.Session. Esta fábrica será
compartida por todos los threads que accedan a la aplicacíón.
SessionFactory sessions = cfg.buildSessionFactory();
Hibernate sí le permite a la aplicación instanciar más de una org.hibernate.SessionFactory. Esto es útil si
se está usando más de una base de datos.
Usualmente, usted querrá que la org.hibernate.SessionFactory cree y administre
el "fondo común" (pool) de sesiones por usted. Si se adopta este enfoque,
abrir una org.hibernate.Session es tan
simple como escribir:
Session session = sessions.openSession(); // abre una nueva sesión
No bien se haga algo que requiera acceso a la base de datos, una nueva conexión JDBC será obtenida del "pool".
Para que esto funcione,
necesitamos pasarle algunas propiedades de conexión JDBC a Hibernate. Todos
los nombres y semántica de las propiedades Hibernate están definidos en la
clase org.hibernate.cfg.Environment. Vamos a
describir los valores más importantes para configurar la conexión JDBC.
Hibernate obtendrá las conexiones (y las administrará en un "pool")
usando un java.sql.DriverManager si usted
configura las siguientes propiedades:
Table 3.1. Hibernate JDBC Properties
| Nombre de la propiedad | Propósito |
|---|---|
| hibernate.connection.driver_class | clase del driver JDBC |
| hibernate.connection.url | URL de JDBC |
| hibernate.connection.username | usuario de la base de datos |
| hibernate.connection.password | clave del usuario de la base de datos |
| hibernate.connection.pool_size | número máximo de conexiones en el "pool" |
El algoritmo que ya viene incluido en Hibernate para el "pooling" de conexiones es, sin embargo, bastante rudimentario. Fue concebido más que nada para ayudarlo a dar los primeros pasos, no para ser usado en un sistema de producción, ni siquiera para un test de performance. Ustde debería usar un "pool" de algún tercero para asegurar mayor redimiento y estabilidad. Simplemente reemplace la propiedad hibernate.connection.pool_size con propiedades específicas de la herramienta de "pooling" de su elección. Esto desactivará el "pooling" interno de Hibernate. Por ejemplo, usted podría elegir C3P0.
C3P0 es una biblioteca para pool de conexiones distribuida junto
con Hibernate en el direactorio lib. Hibernate
usará su org.hibernate.connection.C3P0ConnectionProvider
para efectuar el "pooling" de conexiones, si usted les asigna valores a las
propiedades que empiezan con hibernate.c3p0.*.
Si prefiere usar Proxool, refiérase a las propiedades empaquetadas en hibernate.properties, o al sitio de web de Hibernate
para mayor información.
He aquí un ejemplo de un hibernate.properties para C3P0:
hibernate.connection.driver_class = org.postgresql.Driver hibernate.connection.url = jdbc:postgresql://localhost/mydatabase hibernate.connection.username = myuser hibernate.connection.password = secret hibernate.c3p0.min_size=5 hibernate.c3p0.max_size=20 hibernate.c3p0.timeout=1800 hibernate.c3p0.max_statements=50 hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
Para usar
"pooling" dentro de un servidor de aplicaciones, se debería configurar
Hibernate de manera tal, que siempre obtenga las conexiones desde una fuente
de datos javax.sql.Datasource registrada
usando JNDI. Se necesitarán por lo menos las siguientes propiedades:
Table 3.2. Propiedades para fuente de datos de Hibernate
| Nombre de la propiedad | Propósito |
|---|---|
| hibernate.connection.datasource | nombre JNDI de la fuente de datos |
| hibernate.jndi.url | URL del proveedor de JNDI (optativo) |
| hibernate.jndi.class | clase del InitialContextFactory de JNDI
(optativo) |
| hibernate.connection.username | usuario de la base de datos (optativo) |
| hibernate.connection.password | clave del usuario de base de datos (optativo) |
He aquí un ejemplo de
un archivo hibernate.properties para
configurar la fuente de datos JNDI en un servidor de aplicaciones:
hibernate.connection.datasource = java:/comp/env/jdbc/test hibernate.transaction.factory_class = org.hibernate.transaction.JTATransactionFactory hibernate.transaction.manager_lookup_class = org.hibernate.transaction.JBossTransactionManagerLookup hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
Las conexiones JDBC que se obtengan de una fuente de datos JNDI participarán automáticamente de las transacciones "manejadas por el contenedor" del servidor de aplicaciones.
También se pueden agregar propiedades de
conexión arbitrarias, afijando "hibernate.connection" al nombre de la propiedad de
conexión. Por ejemplo, usted puede especificar una propiedad de conexión
charSet (juego de caracteres), usando "hibernate.connection.charSet".
Usted
también puede definir su propia estrategia de plugin para obtener conexiones
JDBC, implementando la interfaz org.hibernate.connection.ConnectionProvider, y
especificando su propia implementación a medida en la propiedad hibernate.connection.provider_class.
Hay varias otras propiedades que controlan el comportamiento de Hibernate en tiempo de ejecución. Todas son optativas, y tienen valores por defecto razonables.
Advertencia: algunas de estas propiedades existen "a
nivel de sistema" solamente. A las propiedades a nivel de
sistema sólo se les puede asignar valores via java
-Dproperty=value o hibernate.properties. No se les puede asignar valores usando las
técnicas descritas anteriormente.
Tabla 3.3. Porpiedades de Configuration de Hibernate
| Nombre de la propiedad | Propósito |
|---|---|
| hibernate.dialect | El nombre de clase de un org.hibernate.dialect.Dialect que le
permita a Hibernate generar SQL optimizado para una BD relacional en
particular. por
ejemplo En la mayoría de los casos, Hibernate será capaz de elegir la implementación de dialecto correcta, basándose en los metadatos devueltos por el driver JDBC. |
| hibernate.show_sql | Escribir todos los comandos SQL en la consola. Ésta es una
alternativa a asignar el valor "debug" a la categoría de logueo
org.hibernate.SQL. valores posibles |
| hibernate.format_sql | Darle un formato lindo al SQL generado en los logs o consola.
valores posibles
|
| hibernate.default_schema | Al generar SQL, calificar los nombres de tabla con el esquema o
"tablespace" dado. por
ejemplo |
| hibernate.default_catalog | En el SQL generado, calificar los nombres de tablas con el
catálogo dado. por
ejemplo. |
| hibernate.session_factory_name | La org.hibernate.SessionFactory será
vinculada automáticamente a este nombre en JNDI luego de que haya
sido creada. por
ejemplo. |
| hibernate.max_fetch_depth | Les asigna una "profundidad máxima" a los outer joins que sean
generados por aosciaciones "de extremo único" (single-ended). Un
0 anula la captura por defecto en los
outer joins. valores
posibles valor recomendado: entre |
| hibernate.default_batch_fetch_size | Le asigna un valor por defecto a la captura por lotes (batch
fectching) en asociaciones. valores
posibles valores recomendados: |
| hibernate.default_entity_mode | Asigna un método por defecto para la representación de entidades
en todas las sesiones que sean abiertas por esta SessionFactory valores posibles |
| hibernate.order_updates | Fuerza a Hibernate a ordenar los UPDATEs SQL por la clave
primaria de los items que sean actualizados. Esto resultará en menos
deadlocks de transacciones en los sistemas altamente concurrentes.
valores posibles
|
| hibernate.generate_statistics | Hibernate irá juntando estadísticas útiles para el ajuste de
performance. valores
posibles |
| hibernate.use_identifier_rollback | Si se habilita, los identificadores generados serán
reinicializados a valores por defecto una vez que los objetos sean
borrados. valores
posibles |
| hibernate.use_sql_comments | Si se activa, Hibernate generará comentatrios dentro del SQL,
para facilitar el debug. Por defecto, este valor es false. valores posibles |
Tabla 3.4. Propiedades Hibernate de JDBC y Conexión
| Nombre de la propiedad | Propósito |
|---|---|
| hibernate.jdbc.fetch_size | Un valor distinto de 0 determina el tamaño de la captura o
"fetch" JDBC (llama a Statement.setFetchSize()). |
| hibernate.jdbc.batch_size | Un valor distinto de 0 habilita el uso de actualizacionesn en
lotes (batch updates) de JDBC2 por parte de Hibernate. valores posibles se
recomienda entre |
| hibernate.jdbc.batch_versioned_data | Asígnele true a esta propiedad, si
su driver JDBC devuelve conteos de fila al ejecutar executeBatch() (normalmente, es seguro
hacerlo). Entonces, Hibernate usará "batched DML" (lenguaje de
creación de datos en lotes) para datos a los que se les haya
asignado automátiacmente número de versión. El valor por defecto es
false. valores posibles |
| hibernate.jdbc.factory_class | Selecciona un org.hibernate.jdbc.Batcher hecho a
medida. La mayoría de las aplicaciones no necesita configurar esta
propiedad. por
ejemplo |
| hibernate.jdbc.use_scrollable_resultset | Habilita el uso de resultados JDBC2 navegables (scrollable
resultsets) por parte de Hibernate. Esta propiedad sólo es necesaria
cuando se emplean conexiones JDBC provistas por el usuario. De otro
modo, Hibernate usa los metadatos de la conexión. valores posibles |
| hibernate.jdbc.use_streams_for_binary | Indica que se usarán streams al leer o escribir código binario,
o tipos serializables desde o hacia JDBC. *propiedad a nivel de sistema*
valores posibles |
| hibernate.jdbc.use_get_generated_keys | Habilita el uso del PreparedStatement.getGeneratedKeys() de JDBC3
para capturar claves generadas en forma nativa luego de insertar.
Requiere que a Requires JDBC3+ driver y a JRE1.4+, le sea asignado
"false" si el driver que usted está usando tiene problemas con los
generadores de identificadores. Por defecto, intenta determinar las
capacidades del driver usando los metadatos de conexión. valores posibles |
| hibernate.connection.provider_class | El nombre de clase de un org.hibernate.connection.ConnectionProvider
hecho a medida que le provea conexiones JDBC a Hibernate. por ejemplo |
| hibernate.connection.isolation | Determina el nivel de aislamiento de las transacciones JDBC.
Revise java.sql.Connection para
averiguar qué valores tiene sentido asignar aquí, pero note que la
mayoría de las BD no soportan todos los niveles de aislamiento
(isloation levels), y algunas definen niveles no estándar
adicionales. por
ejemplo |
| hibernate.connection.autocommit | Habilita la autocomisión (autocommit) para las conexiones JDBC
en pool (no se recomienda). valors
posibles |
| hibernate.connection.release_mode | Especifica cuándo Hibernate debería liberar conexiones. Por
defecto, una conexión JDBC es retenida hasta que la sesión es
cerrada explícitamente o desconectada. Para fuentes de datos JTA en
un servidor de aplicaciones, se debería usar after_statement para liberar conexiones
agresivamente luego de cada llamado a JDBC. Para conexiones que no
sean JTA, a menudo tiene sentido liberar las conexiones al final de
cada transacción, usando after_transaction. auto elegirá after_statement para las estrategias JTA y
CMT (manejadas por el contenedor), y after_transaction para las estrategias
transaccionales JDBC. valores
posibles Note que este valor
sólo afecta sesiones devueltas por |
| hibernate.connection.<nombreDeLaPropiedad> | Le pasa el valor de propiedad JDBC <propertyName> a DriverManager.getConnection(). |
| hibernate.jndi.<nombreDeLaPropiedad> | Le pasa la propiedad <nombreDeLaPropiedad> a la
InitialContextFactory de JNDI. |
Tabla 3.5. Propiedades del caché de Hibernate
| Nombre de la propiedad | Propósito |
|---|---|
hibernate.cache.provider_class |
El nombre de clase de un CacheProvider hecha a medida. por ejemplo |
hibernate.cache.use_minimal_puts |
Optimiza el caché de 2do nivel para minimizar
escrituras, al costo de realizar lecturas más frecuentemente. Esta
configuración es más útil con cachés en "cluster", y en Hibernate3
se habilita por defecto con cachés en cluster. valores posibles |
hibernate.cache.use_query_cache |
Habilita el caché de consultas. Cada consulta (query) debe ser
individualmente configurada como "cacheable". valores posibles |
hibernate.cache.use_second_level_cache |
Puede ser usado para inhabilitar completamente el caché de
2do nivel, el cual está habilitado por defecto para toda
clase que especifique un mapeo <cache> valores posibles |
hibernate.cache.query_cache_factory |
El nombre de una interfaz QueryCache personalizada, que por defecto es
StandardQueryCache, la cual ya viene de
fábrica. por
ejemplo |
hibernate.cache.region_prefix |
Un prefijo a usar con los nombres de región de caché de
2do nivel. por
ejemplo |
hibernate.cache.use_structured_entries |
Fuerza a Hibernate a almacenar datos en el caché de
2do nivel en un formato más legible. valores posibles |
Tabla 3.6. Propiedades para la configuración de transaciones en Hibernate
| Nombre de la propiedad | Propósito |
|---|---|
hibernate.transaction.factory_class |
El nombre de clase de la TransactionFactory a usar con Hibernate (por
defecto, JDBCTransactionFactory).
por ejemplo |
jta.UserTransaction |
El nombre JNDI usado por JTATransactionFactory para obtener una UserTransaction JTA del servidor de
aplicaciones. por
ejemplo |
hibernate.transaction.manager_lookup_class |
El nombre de clase de un TransactionManagerLookup. Obligatorio cuando
se utiliza caché a nivel de la JVM, o se usa el generador hilo en un
entorno JTA. por
ejemplo |
hibernate.transaction.flush_before_completion |
Si está habilitado, a la sesión le será automátiamente hecho un
"flush" antes de la fase de compleción de la transacción. Es
preferible usar el manejo de transacciones que ya viene incorporado,
y el manejo automático de contexto de sesiones; ver laSección 2.5, “Sesiones
contextuales”. valores
posibles |
hibernate.transaction.auto_close_session |
Si está habilitado, la sesión será automáticamente cerrada
durante la fase de compleción de la transacción. Es preferible usar
el manejo de transacciones que ya viene incorporado, y el manejo
automático de contexto de sesiones; ver la Sección 2.5, “Sesiones
contextuales”. valores
posibles |
Tabla 3.7. Propiedades Misceláneas
| Nombre de la propiedad | Propósito |
|---|---|
hibernate.current_session_context_class |
Provee una estrategia personalizada para determinar el alcance
(scope) respecto de cuál es la sesión "actual". Véase la Sección 2.5, “Sesiones
Contextuales” para más información sobre cuáles son las opciones
que ya vienen incorporadas. valores
posibles |
hibernate.query.factory_class |
Elige la implementación del "revisor" (parser) HQL. valores posibles |
hibernate.query.substitutions |
Mapeo (sustitución) de símbolos en las consultas de Hibernate a
símbolos de SQL. (Dichos símbolos pueden ser funciones o nombres
literales, por ejemplo). por
ejemplo |
hibernate.hbm2ddl.auto |
Exporta o valida automáticamente el esquema DDL a la base de
datos cuando la SessionFactory es
creada. Con create-drop, el esquema de
base de datos será borrado cuando la SessionFactory se cierre explícitamente.
valores posibles |
hibernate.cglib.use_reflection_optimizer |
Habilita el uso de CGLIB en lugar de reflexión en tiempo de
ejecución. Es una propiedad "a nivel de sistema". La reflexión
también puede ser usada para el análisis y resoulción de problemas
(troubleshooting). Note que Hibernate siempre requiere CGLIB,
incluso si el optimizador ha sido inhabilitado. Esta propiedad no se
puede asignar en el archivo hibernate.cfg.xml. valores posibles |
Siempre se debería configurar la propiedad hibernate.dialect al valor correcto. de la subclase
de org.hibernate.dialect.Dialect que
corresponda a su BD. Si se especifica un dialecto, Hibernate usará valores
por defecto adecuados para otras de las propiedades listadas anteriormente,
ahorrándole el esfuerzo de especificarlas manualmente.
Tabla 3.8. Dialectos SQL de Hibernate (hibernate.dialect)
| Base de datos relacional | Dialecto |
|---|---|
| DB2 | org.hibernate.dialect.DB2Dialect |
| DB2 AS/400 | org.hibernate.dialect.DB2400Dialect |
| DB2 OS390 | org.hibernate.dialect.DB2390Dialect |
| PostgreSQL | org.hibernate.dialect.PostgreSQLDialect |
| MySQL | org.hibernate.dialect.MySQLDialect |
| MySQL with InnoDB | org.hibernate.dialect.MySQLInnoDBDialect |
| MySQL with MyISAM | org.hibernate.dialect.MySQLMyISAMDialect |
| Oracle (any version) | org.hibernate.dialect.OracleDialect |
| Oracle 9i/10g | org.hibernate.dialect.Oracle9Dialect |
| Sybase | org.hibernate.dialect.SybaseDialect |
| Sybase Anywhere | org.hibernate.dialect.SybaseAnywhereDialect |
| Microsoft SQL Server | org.hibernate.dialect.SQLServerDialect |
| SAP DB | org.hibernate.dialect.SAPDBDialect |
| Informix | org.hibernate.dialect.InformixDialect |
| HypersonicSQL | org.hibernate.dialect.HSQLDialect |
| Ingres | org.hibernate.dialect.IngresDialect |
| Progress | org.hibernate.dialect.ProgressDialect |
| Mckoi SQL | org.hibernate.dialect.MckoiDialect |
| Interbase | org.hibernate.dialect.InterbaseDialect |
| Pointbase | org.hibernate.dialect.PointbaseDialect |
| FrontBase | org.hibernate.dialect.FrontbaseDialect |
| Firebird | org.hibernate.dialect.FirebirdDialect |
Si su base de datos soporta outer joins
del estilo ANSI, Oracle o Sybase, la captura con
outer joins a menudo aumentará la performance, limitando la
cantidad de viajes de ida y vuelta a la BD (probablemente a costa de un
mayor trabajo efectuado por la base de datos misma). La captura por outer
joins permite que todo un árbol (graph) de ojetos conectados por
asociaciones de-uno-a-muchos, de-muchos-a-uno, de-muchos-a-muchos, y
de-uno-a-uno sea capturado de una sola vez, con un solo comando SQL SELECT.
La captura mediante outer joins
puede ser inhabilitada globalmente
asignándole el valor 0. a la propiedad hibernate.max_fetch_depth. Al asignar un valor de
1 o superior, se permiten las capturas (fetch)
con outer joins para asociaciones que hayan sido mapeadas como de-una-a-una
o de-muchas-a-una con fetch="join".
Vea Sección 19.1, “Estrategias de captura (fetch)” para más información.
Oracle limita el tamaño de los arrays de tipo
byte que pueden ser pasados desde y hacia el
driver JDBC. Si usted desea usar instancias grandes de tipos binary o serializable,
debería habilitar hibernate.jdbc.use_streams_for_binary. Ésta es una propiedad configurable a nivel de sistema
solamente.
Las propiedades con prefijo hibernate.cache le
permiten usar un sistema de caché de 2do nivel con alcance
(scope) de proceso o de cluster. Vea Sección 19.2, “El caché de
2do nivel” para más detalles.
Se pueden definir
nuevos símbolos para las consultas de Hibernate usando hibernate.query.substitutions. Por ejemplo:
hibernate.query.substitutions true=1, false=0
causrá que los símbolos true y false sean traducidos como valores enteros literates
en el SQL que se genere.
hibernate.query.substitutions toLowercase=LOWER
permitiría renombrar la propiedad LOWER (pasar
a minúsculas) de SQL
Si se habilita hibernate.generate_statistics, Hibernate expondrá un
buen número de mediciones que son útiles al ajustar (tuning) el rendimiento
de un sistema en marcha, a través de SessionFactory.getStatistics(). Hibernate incluso
puede ser configurado para exponer dichas estadísticas via JMX. Lea el
Javadoc de las interfaces en org.hibernate.stats para más información.
Hibernate utiliza la librería llamada "Fachada
simple de logueo para Java" (Simple Logging Facade for Java o SLF4J por sus siglas en
inglés), para loguear varios eventos de sistema. SLF4J puede dirigir la
salida de su actividad de logueo a varios "frameworks" de logueo: NOP,
Simple, log4j 1.2, JDK 1.4, JCL o logback), dependiendo de la vinculación
elegida. Para configurar el logueo adecuadamente, necesitará slf4j-api.jar en su classpath, así como el archivo
de jar de su preferencia (en el caso de log4j, slf4j-log4j12.jar). Vea la documentación de
SLF4J para más detalles. Para usar log4j también necesitará colocar un
archivo log4j.properties en su classpath. Un
archivo de propiedades a modo de ejemplo viene distribuido con Hibernate en
el directorio src/.
Le recomendamos fuertemente que se familiarice con los mensajes de log de Hibernate. Se ha invertido mucho trabajo en lograr que el logueo en Hibernate sea lo más detallado posible, sin volverlo ilegible. Es una herramienta esencial para la detección y resolución de problemas. Las categorías más interesantes son las siguientes:
Table 3.9. Hibernate Log Categories
| Category (categoría) | Función |
|---|---|
org.hibernate.SQL |
Loguea todo el "lenguaje de modificación de datos" (DML) de SQL a medida que éste es ejecutado. |
org.hibernate.type |
Loguea todos los parámetros JDBC |
org.hibernate.tool.hbm2ddl |
Loguea todos los comandos SQL de definición de datos (DDL) que hayan sido ejecutados |
org.hibernate.pretty |
Loguea el estado de todas las entidades (con un máximo de 20) asociadas con una sesión al momento de aplicarle "flush". |
org.hibernate.cache |
Loguea toda la actividad del caché de 2do nivel |
org.hibernate.transaction |
Loguea toda actividad relacionada con transacciones |
org.hibernate.jdbc |
Loguea toda adquisición de recursos JDBC |
org.hibernate.hql.ast.AST |
Loguea todo AST de HQL y SQL durante la revisión (parsing) de las consultas |
org.hibernate.secure |
Loguea todas las autorizaciones para requests JAAS |
org.hibernate |
Loguea todo (un montón de información, pero muy útil a para detectar y solucionar problemas). |
Al desarrollar
aplicaciones con Hibernate, se debería trabajar casi siempre con debug habilitado para org.hibernate.SQL, o, alternativamente, habilitar la
propiedad hibernate.show_sql enabled.
La
interfaz org.hibernate.cfg.NamingStrategy
permite especificar un estándar de nombrado o "NamingStrategy" para los
objetos de base de datos y elementos del esquema.
Usted puede
proveer reglas para generar automáticamente los identificadores a partir de
identificadores Java, o para procesar los nombres de tabla y columna
"lógicos" dados en el archivo de mapeo, y convertirlos en nombres de tabla y
columna "físicos". Esta característica reduce la locuacidad de los
documentos de mapeo, eliminando el "ruido" provocado por la repetición de
prefijos TBL_, por ejemplo. La estrategia que
Hibernate usa por defecto es bastante parca.
Se puede especificar
una estrategia diferente, llamando a Configuration.setNamingStrategy() antes de agregar
mapeos.
SessionFactory sf = new Configuration()
.setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
.addFile("Item.hbm.xml")
.addFile("Bid.hbm.xml")
.buildSessionFactory(); org.hibernate.cfg.ImprovedNamingStrategy es una
estrategia que ya viene de incorporada, y puede ser un punto de partida útil
para algunas aplicaciones.
Un enfoque alternativo de configuración, es
especificar la configuración completa en un archivo llamado hibernate.cfg.xml. Este archivo puede usarse en
reemplazo del archivo hibernate.properties, o,
si ambos están presentes, para suplantar propiedades de éste.
Se
espera que el archivo de configuración XML esté por defecto en la raíz de su
CLASSPATH. He aquí un ejemplo:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- una instancia de SessionFactory listada como /jndi/name -->
<session-factory
name="java:hibernate/SessionFactory">
<!-- propiedades -->
<property name="connection.datasource">java:/comp/env/jdbc/MyDB</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="show_sql">false</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
<property name="jta.UserTransaction">java:comp/UserTransaction</property>
<!-- archivos de mapeo -->
<mapping resource="org/hibernate/auction/Item.hbm.xml"/>
<mapping resource="org/hibernate/auction/Bid.hbm.xml"/>
<!-- configuración del caché -->
<class-cache class="org.hibernate.auction.Item" usage="read-write"/>
<class-cache class="org.hibernate.auction.Bid" usage="read-only"/>
<collection-cache collection="org.hibernate.auction.Item.bids" usage="read-write"/>
</session-factory>
</hibernate-configuration> Como se puede ver, la ventaja de
este enfoque es externalizar la configuración de los nombres de los acrhivos
de mapeo. hibernate.cfg.xml también es más
conveniente para ajustar los valores del caché. Note que usar hibernate.cfg.xml o hibernate.properties es indistinto, excepto por los
beneficios mencionados de usar una sintaxis XML.
Con la configuración XML, hacer arrancar a Hibernate es tan simple como:
SessionFactory sf = new Configuration().configure().buildSessionFactory();
Se puede elegir un archivo distinto de configuración, usando:
SessionFactory sf = new Configuration()
.configure("catdb.cfg.xml")
.buildSessionFactory();Hibernate tiene los siguientes puntos de integración con la infraestructura J2EE
Fuentes de datos manejadas por el
contenedor: Hibernate puede usar conexiones JDBC manejadas
por el contenedor (container-mamaged), provistas a través de JNDI.
Usualmente, un TransactionManager
compatible con JTA y un ResourceManager se
hacen cargo del manejo de transacciones (CMT), especialmente
transcciones distribuidas a lo largo de varias fuentes de datos. Por
supuesto, usted también puede demarcar los límites de sus transacciones
programáticamente (BMT) o usar la API de transacciones de Hibernate
(Transaction) para mantener su código
portable.
Enlace JNDI automático:
Hibernate puede ligar su SessionFactory a
JNDI luego del arranque.
Enlace de sesión JTA: La
sesión de Hibernate puede ser automáticamente ligada al alcance o
"scope" de las transacciones JTA. Simplemente obtenga la SessionFactory de JNID (haciendo un "lookup"), y
de ahí obtenga la sesión actual. Deje que Hibernate se haga cargo de
aplicarle "flush" y de cerrar la sesión cuando la transacción JTA se
haya terminado. La demarcación de transacciones es o bien declarativa
(CMT) o bien programática (BMT/UserTransaction).
Despliegue JMX: Si usted
cuenta con un servidor de aplicaciones habilitado para JMX, (por
ejemplo, JBoss), puede elegir desplegar Hibernate como un "Managed
MBean" Esto le ahorra el códgo de arranque de una línea que construye la
SessionFactory a partir de Configuration. El container hará arrancar su
HibernateService, e, idealmente, también se
hará cargo de las dependencias de servicios (la fuente de datos tiene
que estar disponible antes de que Hibernate arranque, etc).
Dependiendo del entorno, usted puede tener que asignar el
valor "true" a la opción de configuración hibernate.connection.aggressive_release, si su
servidor de aplicaciones produce excepciones de "contención de conexiones"
("connection containment" exceptions).
La sesión de
Hibernate es independiente de cualquier sistema de demarcación de
transacciones presente en la arquitectura de su sistema. Si usted deja que
Hibernate use JDBC directamente a través de un "pool" de conexiones, usted
puede comenzar y terminar sus transacciones llamando a la API de JDBC. Si
usted ejecuta su aplicación en un servidor de aplicaciones J2EE, es posible
que prefiera usar transaccioes manejadas por bean (bean-managed) e invocar
la API de JTA y UserTransaction según haga
falta.
Para mantener su código portable entre estos dos entornos (y
otros), le recomedamos emplear la API Transaction de Hibernate, la cual envuelve y oculta
el sistema subyacente. Va a tener que especificar una clase "fábrica" de
instancias de Transaction, configurando la
propiedad de Hibernate hibernate.transaction.factory_class.
Éstas son las tres opciones estándar, que ya vienen incluidas "de fábrica":
org.hibernate.transaction.JDBCTransactionFactorydelega a las transacciones JDBC de la base de datos (es el valor por defecto)
org.hibernate.transaction.JTATransactionFactorydelega a la transacción manejada por el contenedor (container-managed) si hay una tranascción en proceso en este contexto (por ejemplo, un método de un Session EJB), en caso contrario, crea una nueva transción, y se utilizan transacciones manejadas por bean.
org.hibernate.transaction.CMTTransactionFactorydelega a transacciones JTA manejadas por el contenedor
Usted también puede definir sus propias estrategias transaccionales (por un servicio de transacciones CORBA, por ejemplo).
Algunas características de Hibernate (por ejemplo, caché de
2do nivel, sesiones contextuales con JTA, etc) requieren aceso al
TransactionManager de JTA en un entorno
administrado. En un servidor de aplicaciones, usted tiene que especificar
cómo Hibernate obtendrá una referencia al TransactionManager, dado que en J2EE no hay un
mecanismo estándar y único.
Tabla 3.10. TransactionManagers de JTA
| Fábrica de transacciones | Servidor de aplicaciones |
|---|---|
org.hibernate.transaction.JBossTransactionManagerLookup |
JBoss |
org.hibernate.transaction.WeblogicTransactionManagerLookup |
Weblogic |
org.hibernate.transaction.WebSphereTransactionManagerLookup |
WebSphere |
org.hibernate.transaction.WebSphereExtendedJTATransactionLookup |
WebSphere 6 |
org.hibernate.transaction.OrionTransactionManagerLookup |
Orion |
org.hibernate.transaction.ResinTransactionManagerLookup |
Resin |
org.hibernate.transaction.JOTMTransactionManagerLookup |
JOTM |
org.hibernate.transaction.JOnASTransactionManagerLookup |
JOnAS |
org.hibernate.transaction.JRun4TransactionManagerLookup |
JRun4 |
org.hibernate.transaction.BESTransactionManagerLookup |
Borland ES |
Una fábrica de sesiones (SessionFactory) ligada a JNDI puede simplificar la
búsqueda y obtención (lookup) de nuevas sesiones. Note que esto no se
relaciona con las Datasource, también ligadas a
JNDI, ¡ambas simplemente comparten el mismo repositorio de registro
(registry)!
Si usted desea mantener la fábrica de sesiones ligada a
un "espacio de nombre" o "namespace" JNDI, especifique un nombre (por
ejemplo, java:hibernate/SessionFactory) usando
la propiedad hibernate.session_factory_name. Si
esta propiedad es omitida, la fábrica de sesiones no quedará ligada a JNDI.
(Esto es especialmente útil en entornos que por defecto tienen una JNDI de
sólo lectura, como por ejemplo Tomcat).
Cuando de liga una fábrica
de sesiones a JNDI, Hibernate empleará los valores de hibernate.jndi.url y hibernate.jndi.class para instanciar un contexto
inicial. Si no son especificadas, se usa el InitialContext por defecto.
Hibernate
colocará la fábrica de sesiones (SessionFactory) automáticamente en JNDI en cuanto se
invoque cfg.buildSessionFactory(). Esto
significa que este llamado estará presente, por lo menos, en algún código de
arranque (o clase utilitaria) de su aplicación, a menos que usted usa el
despliegue JMX, con HibernateService (lo cual
se discute luego).
Si usted usa una fábrica de sesiones JNDI, un EJB o cualquier otra clase puede obtenerla usando el JNDI lookup.
Le recomendamos que, en un entorno administrado, use JNDI para obtener sus
fábricas de sesiones, y en cualquier otro entorno, un singleton estático.
Para escudar su aplicación de estos detalles, también le recomendamos que
oculte el código que efectivamente realiza el "lookup" dentro de una clase
utilitaria, (como por ejemplo, HibernateUtil.getSessionFactory()). Note que dicha
clase es también una buena manera de hacer arrancar Hibernate (capítulo 1).
La manera más
fácil de manejar sesiones y transacciones es el manejo automático que
Hibernate hace de la "sesión actual". Vea la discusión en Sesiones actuales. Usando el contexto
de sesión "jta", si no hay ninguna sesión de
Hibernate asociada con la transacción JTA actual, se creará una y se la
asociará con la transacción la primera vez que sessionFactory.getCurrentSession() sea invocada. A
las sesiones que hayan sido creadas de esta manera, les será automáticamente
efectuado el "flush" antes de que la transacción finalice, serán cerradas
después de que la transacción finalice, y las conexiones JDBC serán
agresivamente liberadas después de cada comando. Esto permite que las
sesiones sean manejadas por el ciclo de vida de la transacción JTA al cual
están asociadas, manteniendo al código libre de ese tipo de preocupaciones
administrativas. Su código puede utilizar o bien JTA programáticamente a
través de UserTransaction, o (lo que más
recomendamos para producir código portable) usar la API de Transaction de Hibernate para establecer los límites
de la transacción. Si su aplicación se está ejecutando en un contenedor de
EJB, se prefiere utilizar demarcación declarativa de transacciones con CMT
(siglas en inglés de "transacciones manejadas por el contenedor").
La línea cfg.buildSessionFactory() aín tiene que ser ejecutada
en algún lado para meter una fábrica de sesiones en JNDI. Esto se puede
lograr o bien con un bloque inicializador estático en una clase utilitaria
(como el que está en HibernateUtil) o bien
desplegando Hibernate como un "servicio administrado" (managed service),
Hibernate es
distribuido con org.hibernate.jmx.HibernateService para el despliegue
en servidores de aplicaciones con capacidad JMX, como JBoss. El despliegue y
configuración son específicos de la marca del servidor. He aquí un jboss-service.xml de ejemplo para for JBoss 4.0.x:
<?xml version="1.0"?>
<server>
<mbean code="org.hibernate.jmx.HibernateService"
name="jboss.jca:service=HibernateFactory,name=HibernateFactory">
<!-- Servicios requeridos -->
<depends>jboss.jca:service=RARDeployer</depends>
<depends>jboss.jca:service=LocalTxCM,name=HsqlDS</depends>
<!-- Ligar el servicio Hibernate a JNDI -->
<attribute name="JndiName">java:/hibernate/SessionFactory</attribute>
<!-- fuente de datos -->
<attribute name="Datasource">java:HsqlDS</attribute>
<attribute name="Dialect">org.hibernate.dialect.HSQLDialect</attribute>
<!-- integración de transacciones -->
<attribute name="TransactionStrategy">org.hibernate.transaction.JTATransactionFactory</attribute>
<attribute name="TransactionManagerLookupStrategy">org.hibernate.transaction.JBossTransactionManagerLookup</attribute>
<attribute name="FlushBeforeCompletionEnabled">true</attribute>
<attribute name="AutoCloseSessionEnabled">true</attribute>
<!-- opciones de captura -->
<attribute name="MaximumFetchDepth">5</attribute>
<!-- caché de 2do nivel -->
<attribute name="SecondLevelCacheEnabled">true</attribute>
<attribute name="CacheProviderClass">org.hibernate.cache.EhCacheProvider</attribute>
<attribute name="QueryCacheEnabled">true</attribute>
<!-- Logueo -->
<attribute name="ShowSqlEnabled">true</attribute>
<!-- archivos de mapeo -->
<attribute name="MapResources">auction/Item.hbm.xml,auction/Category.hbm.xml</attribute>
</mbean>
</server> Este archivo es desplegado en un directorio llamado
META-INF, y empaquetado en un archivo JAR con
la extensión .sar (service archive o "archivo
de servicio). Sus EJB (usualmente session beans) pueden ser conservados en
su propio archivo JAR, pero este archivo JAR de EJB se puede incluir en el
archivo principal de servicio para obtener una única desplegable "en
caliente" (hot deployment). Consulte la documentación del servidor de
aplicaciones JBoss para más información acerca del servicio JMX y el
despliegue de EJB.
Las clases persistentes son clases de una aplicación que implementen las entidades de un problema de negocios (por ejemplo, "Cliente" y "Orden" en una aplicación de e-commerce). No todas las instancias de una clase persistente se considera que estén en un "estado persistente". Una instancia pude, en cambio, ser transitoria (transient) o desprendida (detached).
Hibernate trabaja mejor si estas clases siguen un
formato muy simple, conocido como el modelo de programación de "objeto Java
liso y llano" o POJO (Plain Old Java Object) por sus siglas en inglés. Sin
embargo, ninguna de estas reglas es estrictamente obligatoria. Mas aún,
Hibernate3 presupone muy poco acerca de la naturaleza de sus objetos
persistentes. Un modelo de dominio (domain model) se puede expresar de otras
maneras: mediante árboles de instancias de Map,
por ejemplo.
La mayoría de las aplicaciones Java requieren una clase persistente que represente felinos:
package eg;
import java.util.Set;
import java.util.Date;
public class Cat {
private Long id; // identificador
private Date birthdate;
private Color color;
private char sex;
private float weight;
private int litterId;
private Cat mother;
private Set kittens = new HashSet();
private void setId(Long id) {
this.id=id;
}
public Long getId() {
return id;
}
void setBirthdate(Date date) {
birthdate = date;
}
public Date getBirthdate() {
return birthdate;
}
void setWeight(float weight) {
this.weight = weight;
}
public float getWeight() {
return weight;
}
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
void setSex(char sex) {
this.sex=sex;
}
public char getSex() {
return sex;
}
//litter=en inglés, camada
void setLitterId(int id) {
this.litterId = id;
}
public int getLitterId() {
return litterId;
}
void setMother(Cat mother) {
this.mother = mother;
}
public Cat getMother() {
return mother;
}
//kittens=gatitos
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}
// addKitten no es requerido por Hibernate
public void addKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId( kittens.size() );
kittens.add(kitten);
}
}Aquí hay 4 reglas principales a seguir:
Cat tiene un constuctor sin argumentos. Todas las
clases persistentes deben tener un constuctor por defecto (el cual puede no
ser público) de manera qu Hibernate pueda instanciarlas usando Constructor.newInstance(). Recomendamos fuertemente
un constructor por defecto, que tenga al menos visibilidad package para la generación del "proxy" en
Hibernate.
Cat tiene una propiedad llamada id. Esta propiedad corresponde a la columna de clave
primaria de la base de datos. La propiedad puede llamarse como sea, y su
tipo puede ser cualquiera de los tipos de dato primitivos, cualquier tipo
"envoltorio" (wrapper), java.lang.String, o
java.util.Date. Si su base de datos anticuada
tiene claves compuestas, usted puede usar inclusive una clase definida por
el usuario con propiedades de los tipos mencionados (vea la sección sobre
claves compuestas más adelante).
La propiedad indentificadora es estrictamente optativa. La puede omitir e Hibernate aún podrá seguirle la pista al objeto, internamente. De todos modos, no le recomendamos que haga esto.
De hecho, algunas de las funcoionalidades están disponibles sólo para aquellas clases que sí tienen identificador.
Recuperación (reattachment) transitiva para objetos desprendidos (update o merge en cascada)-vea Sección 10.11, “Persistencia Transitiva”
Session.saveOrUpdate()
Session.merge()
Le recomendamos que declare propiedades indentificador nombradas de una manera consistente, en sus clases persistentes. Mas aún, le recomendamos que use un tipo anulable (es decir, no primitivo).
Una característica central de Hibernate, los representantes o proxies, depende de que la clase persistente no sea final, o de que sea la implementación de una interfaz con todos sus métodos públicos.
Se puede persistir clases finales que no implementen una interfaz con Hibernate, pero usted no será capaz de usar proxies para la captura por asociaciones perezosas (lazy association fetching), lo cual limitará sus opciones de ajuste de performance.
También se debería evitar declarar métodos public
final en las clases no finales. Si se quiere usar clases con métodos
públicos finales, se debe inhabilitar el "proxying" explícitamente,
especificando lazy="false".
Cat declara métodos de acceso para todos sus campos
persistentes. Muchos otras herramientas de ORM persisten directamente sus
variables de instancia. Nosotros creemos que es mejor proveer un nivel de
aislamiento entre el esquema relacional y la estructura interna de datos de
la clase. Por defecto, Hibernate persiste propiedades del tipo JavaBean, y
reconoce nombres de método de la forma getAlgo,
isAlgo y setAlgo.
Si es necesario, usted puede revertir esto, y permitir el acceso directo,
para propiedades específicas,
No se necesita que las propiedaes sean declaradas como públicas. Hibernate puede persistir una propiedad con un par get/set que tenga acceso por defecto (package) o privado.
Una subclase también debe cumplir con la primera y
la segunda regla. Hereda su identificador de la superclase Cat.
package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
} Usted debe
reemplazar (override) los métodos equals() y
hashCode() si:
planea poner instacias de clases persistentes en un Set (lo cual es la manera recomendada de
representar cualquier asociación con un lado "muchos"), y
planea usar recuperación (reattachment) de instancias desprendidas
Hibernate garantiza la equivalencia de la identidad
persistente (el registro de base de datos) y la identidad de Java, sólo
dentro del alcance de una sesión en particular. Así que, tan pronto como
mezclamos instancias que hayan sido obtenidas de distintas sesiones, debemos
implementar equals() y hashCode() si deseamos tener una semántca con sentido
para los Sets.
La manera más obvia es
implementar equals()/hashCode() comparando el valor identificador de ambos
objetos. Si el valor es el mismo, ambos deben ser el mismo registro de la
base de datos, siendo por lo tanto iguales (si ambos son agregados al Set., sólo tendremos un elemento en el Set.). ¡Desafortunadamente, no podemos utilizar ese
enfoque con los identificadores generados! Hibernate sólo asignará
identificadores los objetos que hayan sido creados, y una instancia que haya
sido recientemente creada ¡aún no tiene ningún identificador! Más aún, si
una de estas instancias, sin grabar, está dentro de un Set., grabarla le va a asignar un identificador, y
sus valores de equals() y hashCode() (que estarían basados en el identificador)
cambiarían, rompiendo el contrato del Set. Vea
el sitio de web de Hibernate para una discusión completa de este problema.
Note que esto no es un problema de Hibernate, sino parte de la semántica
normal de Java en lo que respecta a la igualdad e identidad de los objetos.
Nosotro recomendamos implementar equals() y hashCode()
utilizando una igualdad "por clave de negocio". Esto quiere decir, que estos
métodos comparen solamente propiedades que formen la clave de negocio, una
clave que identificaría a nuestro objeto en el mundo real (una clave
candidata natural).
public class Cat {
...
public boolean equals(Object other) {
if (this == other) return true;
if ( !(other instanceof Cat) ) return false;
final Cat cat = (Cat) other;
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
if ( !cat.getMother().equals( getMother() ) ) return false;
return true;
}
public int hashCode() {
int result;
result = getMother().hashCode();
result = 29 * result + getLitterId();
return result;
}
}Note que la clave de negocio no necesita ser tan sólida como una clave primaria de la base de datos. (vea Sección 11.1.3, “Considerar la identidad de un objeto”). Propiedades que sean únicas o inmutables, usualmente son buenas candidatas a "clave de negocio".
Advierta que las siguientes características se consideran experimentales, y pueden cambiar en el futuro cercano
Las entidades persistentes no tienen
que ser representadas, en tiempo de ejecución, necesariamente, com clases
POJO u objetos JavaBean. Hibernate también soporta modelos dinámicos (usando
Maps de Maps en
tiempo de ejecución), y la representación de entidades como árboles DOM4J.
Con este enfoque, no se escriben clases, simplemente archivos de mapeo.
Por defecto, Hibernate trabaja en el modo POJO normal. Se puede
configurar un modo de representación de entidad por defecto para una fábrica
de sesiones en particular, usando la opción de configuración default_entity_mode (vea Tabla 3.3,
“Propiedades de Configuración de Hibernate”.
Los ejemplos
siguientes demuestran la representación usando mapas. Primero, en el
archicvo de mapeo, se debe declara un entity-name en lugar de (o además de) el nombre de la
clase:
<hibernate-mapping>
<class entity-name="Customer">
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<property name="name" column="NAME" type="string"/>
<property name="address" column="ADDRESS" type="string"/>
<many-to-one name="organization" column="ORGANIZATION_ID" class="Organization"/>
<bag name="orders" inverse="true" lazy="false" cascade="all">
<key column="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>
</class>
</hibernate-mapping>Note que, incluso si las asociaciones son declaradas usando nombres de clases de destino, el tipo de destino de una asociación también puede ser una entidad dinámica en lugar de un POJO.
Después de configurar el modo de entidad por defecto a dynamic-map para esta SessionFactory, podemos trabajar con "mapas de mapas"
en tiempo de ejecución.
Session s = openSession();
Transaction tx = s.beginTransaction();
Session s = openSession();
// Crear un cliente
Map david = new HashMap();
david.put("name", "David");
// Crear una organización
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");
// Vincular a ambos
david.put("organization", foobar);
// Grabar a ambos
s.save("Customer", david);
s.save("Organization", foobar);
tx.commit();
s.close();La ventaja de un mapeo dinámico es acelerar el tiempo de entrega, para prototipos, sin la necesidad de implementar clases de entidades. De todos modos, se pierde el chequeo de tipos en tiempo de compilación, y esto muy probablemente causará varias excepciones en tiempo de ejecución. Gracias al mapeo de Hibernate, el esquema de base de datos puede ser fácilmente normalizado y saneado, permitiendo agregar una implementación apropiada de modelo de dominio encima, más adelante.
Los modos de representación de entidad también pueden ser configurados por sesión:
Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
// Crea un cliente
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continúa con la sesión "POJO" Dese cuenta por favor de que el
llamado a getSession() usando un EntityMode determinado, es cosa de la API de Session API, no de SessionFactory. De esta manera, la nueva sesión
comparte la misma conexión JDBC subyacente, y otra información de contexto.
Esto vuelve innecesario invocar flush() y close() en la sesión secundaria, y le deja el manejo
de transacciones y conexiones a unidad de trabajo primaria.
Se puede encontrar más información sobre las capacidades de representación con XML en Capítulo 18, Mapeo XML.
org.hibernate.tuple.Tuplizer,
y sus sub-interfaces, son los encargados de manejar una representación en
particular de un fragmento de datos, dado el org.hibernate.EntityMode de dicha representación. Si
un determinado fragmento de datos se concibe como una estructura de datos,
entonces un t-uplizador es lo que sabe cómo crear dicha estructura. Por
ejemplo, para el modo de entidad "POJO", el t-uplizador correspondiente sabe
cómo crear el POJO mediante su constructor, y cómo acceder a las propiedades
de POJO usando los métodos de acceso. Hay dos t-uplizadores de alto nivel,
representados por las interfaces org.hibernate.tuple.entity.EntityTuplizer y org.hibernate.tuple.component.ComponentTuplizer. Los
EntityTuplizers see encargan de manejar los
"contratos" recién descritos para las entidades, mientras que los ComponentTuplizers hacen lo propio para los
componentes.
El usuario puede, asimismo, insertar sus propios
t-uplizadores. Tal vez usted desee que par el modo de entidad "dynamic-map"
se utilice una implementación de java.util.Map
que no sea java.util.HashMap, o tal vez usted
necesita que se utilice una estrategia de generación de proxies distinta de
la que viene de fábrica. Las definiciones de t-uplizer van adjuntas a la
entidad o mapeo de componentes que están destinadas a manejar. Volviendo al
ejemplo de la entidad "cliente" (customer).
<hibernate-mapping>
<class entity-name="Customer">
<!--
Reemplaza el t-uplizer de modo de entidad dynamic-map
por el de entity-mode
-->
<tuplizer entity-mode="dynamic-map" class="CustomMapTuplizerImpl"/>
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<!-- otras propiedades-->
...
</class>
</hibernate-mapping>
public class CustomMapTuplizerImpl extends org.hibernate.tuple.entity.DynamicMapEntityTuplizer {
// suplanta al método buildInstantiator() para "enchufar" nuestro mapeo hecho a medida ...
protected final Instantiator buildInstantiator(org.hibernate.mapping.PersistentClass mappingInfo) {
return new CustomMapInstantiator( mappingInfo );
}
//suplanta a generateMap() para devolver nuestro map hecho a medida
private static final class CustomMapInstantiator extends org.hibernate.tuple.DynamicMapInstantitor {
protected final Map generateMap() {
return new CustomMap();
}
}
}Los mapeos objeto/relacionales generalmente se definen en un documento XML. El documento de mapeo ha sido diseñado para ser legible y editable a mano. El lenguaje de mapeo es "Javacéntrico", lo cual significa que los mapeos se construyen en torno a declaraciones de clases persistentes, no de tablas.
Note que, aunque muchos usuarios de Hibernate eligen escribir el XML a mano, existe un buen número de herramientas para generar el documento de mapeo, como: XDoclet, Middlegen y AndroMDA.
Comencemos con un mapeo de ejemplo:
<?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="eg">
<class name="Cat" table="cats" discriminator-value="C">
<id name="id">
<generator class="native"/>
</id>
<discriminator column="subclass" type="character"/>
<property name="weight"/>
<property name="birthdate" type="date" not-null="true" update="false"/>
<property name="color" type="eg.types.ColorUserType" not-null="true" update="false"/>
<property name="sex" not-null="true" update="false"/>
<property name="litterId" column="litterId" update="false"/>
<many-to-one name="mother" column="mother_id" update="false"/>
<set name="kittens" inverse="true" order-by="litter_id">
<key column="mother_id"/>
<one-to-many class="Cat"/>
</set>
<subclass name="DomesticCat" discriminator-value="D">
<property name="name" type="string"/>
</subclass>
</class>
<class name="Dog">
<!-- acá podría ir un mapeo para Perro -->
</class>
</hibernate-mapping>
(N.del T): "eg" son las siglas
de "exempli gratia", una locución latina que en inglés hace las veces de
"por ejemplo". A lo largo de esta documentación, "eg" se usa como el paquete
por defecto para los ejemplos de código.
Ahora discutiremos el
contenido del documente de mapeo. Sólo describiremos los elementos y
atributos del documento que son usados por Hibernate en tiempo de ejecución.
El documento de mapeo también contiene algunos atributos optativos
adicionales, y elementos que afectan los esquemas de BD exportados por
herramientas de exportación de esquemas (por ejemplo, el atributo not-null).
Todos los mapeos XML deberían
declarar el doctype que se muestra. La DTD real puede ser encontrada en la
URL mencionada, en el directorio hibernate-x.x.x/src/org/hibernate o en hibernate3.jar. Hibernate siempre buscará la DTD
primero en el classpath. Si experimenta problemas al buscar la DTD debido a
su conexión de Internet, compare su declaración de DTD contra el contenido
de su classpath.
Como se mencionó anteriormente, Hibernate
primero intentará resolver la DTD en su classpath. La manera en que lo hace,
es registrando una implementación personalizada de org.xml.sax.EntityResolver con el SAXReader que usa
para leer los archivos xml. Este EntityResolver
perosnalizado reconoce dos espacios de nombre de systemId diferentes.
un "espacio de nombre" (namespace)
de Hibernate es reconocido siempre que el resolver encuentra un systemId
qie comience con http://hibernate.sourceforge.net/; el resolver
intenta resolver estas entidades mediante el classloader que haya
cargado las clases de Hibernate.
un espacia de nombre de usuario se reconoce siempre que el
resolver encuentra un systemId que use un protocolo de URL classpath://; el resolver intentará resolver esas
entidades a través de (1) el classloader del contexto de thread actual,
y (2), el classloader que haya cargado las clases de Hibernate.
Un ejemplo de utilización de un espacio de nombre (namespace) hecho a medida:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [
<!ENTITY types SYSTEM "classpath://your/domain/types.xml">
]>
<hibernate-mapping package="your.domain">
<class name="MyEntity">
<id name="id" type="my-custom-id-type">
...
</id>
<class>
&types;
</hibernate-mapping> En donde types.xml es un recurso en el paquete your.domain y contiene una definición de tipo a
medida typedef.
Este elemento tiene varios atributos optativos. Los
atributos schema y catalog especifican que las tablas a las que este
mapeo se refiere pertenecen al esquema o catálogo indicado. Si se
especifican, los nombres de tabla serán calificados con el esquema y/o
catálogo dados. Si no están presentes, los nombres de tabla no serán
calificados. El atributo default-cascade
especifica qué estilo de propagación en cascada debería asumirse para las
propiedades y colecciones que no especifiquen un atributo cascade. El atributo auto-import nos permite, por defecto, usar un nombre
de clase no calificado en el lenguaje de consultas.
<hibernate-mapping
schema="schemaName" (1)
catalog="catalogName" (2)
default-cascade="cascade_style" (3)
default-access="field|property|ClassName" (4)
default-lazy="true|false" (5)
auto-import="true|false" (6)
package="package.name" (7)
/> |
|
|
|
|
|
|
|
|
|
|
|
|
Si usted tiene dos clases persistentes cuyo
nombre (no calificado) es el mismo, debería configurar auto-import="false". Hibernate lanzará una excepción
si usted le intenta asignar dos clases al mismo nombre "imported".
Note que el elemento hibernate-mapping le
permite anidar los mapeos de varias clases persistentes, como se muestra
anteriormente. Sin embargo, mapear sólo una clase persistente (o jerarquía
de clases) por archivo de mapeo es una costumbre más establecida (y lo que
algunas herramientas esperan). Por ejemplo, Cat.hbm.xml, Dog.hbm.xml, o, si se usa herencia, Animal.hbm.xml.
Se puede declarar una clase persistente usando el elemento class.
<class
name="ClassName" (1)
table="tableName" (2)
discriminator-value="discriminator_value" (3)
mutable="true|false" (4)
schema="owner" (5)
catalog="catalog" (6)
proxy="ProxyInterface" (7)
dynamic-update="true|false" (8)
dynamic-insert="true|false" (9)
select-before-update="true|false" (10)
polymorphism="implicit|explicit" (11)
where="arbitrary sql where condition" (12)
persister="PersisterClass" (13)
batch-size="N" (14)
optimistic-lock="none|version|dirty|all" (15)
lazy="true|false" (16)
entity-name="EntityName" (17)
check="arbitrary sql check condition" (18)
rowid="rowid" (19)
subselect="SQL expression" (20)
abstract="true|false" (21)
node="element-name"
/> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Es perfectamente aceptable que la clase
persistente nombrada sea une interfaz. En ese caso, se debe declarar la
clase implementadora usando el elemento <subclass>. Se puede persistir cualquier clase
anidada (inner class) estática. Se
debería especificar el nombre de este tipo de clases usando la nomenclatura
estándar, es decir com.Mi$ClaseInterna.
Las clases inmutables (mutable="false") no
pueden ser actualizadas o borradas por la aplicación. Esto le permite a
Hibernate realizar algunas optimizaciones menores.
El atributo
optativo proxy permite la inicialización
perezosa de instancias persistentes de la clase. Inicialmente, Hibernate
devolverá proxies CGLIB, que implementan la interfaz mencionada. El objeto
persistente propiamente dicjo será cargado cuando se invoque un método del
proxy. Vea "Inicializar colecciones y proxies" más adelante.
Polimorfismo implícito significa que
cualquier consulta que nombre a una superclas o interfaz devolverá
instancias de la clase misma. Polimorfismo
explícito significa que instancias de esta clase sólo serán
devueltas por consulta que nombren a esta clase, y que las consultas que
nombren a esta clase devolverán sólo instancias de subclases que hayan sido
mapeados como <subclass> o <joined-subclass>. Para la mayoría de los
casos, el valor por defecto polymorphism="implicit", es lo más apropiado. El
polimorfismo explícito es útil cuando hay dos clases diferentes mapeadas a
la misma tabla (lo cual permite tener una clase "de peso ligero" que
contenga un subconjunto de las columnas de la tabla).
El atributo
persister le permite personalizar la estrategia
de persistencia usada para la clase. Por ejemplo, usted puede especificar su
propia subclase de org.hibernate.persister.EntityPersister, o hasta
puede proveer una implementación completamente nueva de la interfaz org.hibernate.persister.ClassPersister que persista,
por ejemplo, valiéndose de una llamada a un procedimiento almacenado de base
de datos, o usando serialización a archivos planos, o LDAP. Vea org.hibernate.test.CustomPersister para un ejemplo
simple (de "persistencia" a una Hashtable).
Note que las asignaciones de valores dynamic-update y dynamic-insert no son heredadas por la subclases, así
que pueden ser especificadas en los elementos <subclass> o <joined-subclass>. Estos valores pueden
incrementar la performance en algunos casos, pero reducirla en otros. Úselos
con cuidado.
El uso de select-before-update normalmente bajará la
performance. Es muy útil para evitar que un "update trigger" de la base de
datos sea invocado innecesariamente si usted está revinculando todo un árbol
de instancias desprendidas a una sesión.
Si usted habilita dynamic-update, tendrá las siguientes opciones de
"locking" optimista
version verifica las columnas de
versión/timestamp
all verifica todas las columnas
dirty verifica las columnas que
hayan cambiado, permitiendo algunas modificaciones concurrentes
none no usa "locking" optimista
Recomendamos muy
fuertemente que se utilicen las columnas de versión/timestamp para locking
optimista con Hibernate. Ésta es la estrategia óptima en lo que a performace
se refiere, y es la única estrategia que maneja correctamente las
modificaciones que se hagan a instancias desprendidas (es decir, cuando se
use Session.merge() ).
Para un mapeo Hibernate, no hay diferencia entre una vista y una tabla de la base de datos. Como es de esperarse, esto es transparente a nivel de base de datos (note que algunas base de datos no soportan vistas correctamente, especialmente actualizaciones de éstas). A veces usted quiere usar una vista, pero no puede crear una en la base de datos (por ejemplo, con un esquema heredado anticuado). En este caso, usted puede mapear una entidad inmutable y de sólo-lectura a una expresión "subselect" de SQL.
<class name="Summary">
<subselect>
select item.name, max(bid.amount), count(*)
from item
join bid on bid.item_id = item.id
group by item.name
</subselect>
<synchronize table="item"/>
<synchronize table="bid"/>
<id name="name"/>
...
</class> Declara las tablas contra las cuales se ha de
sincronizar esta entidad, asegurándose de que el auto-flush ocurra
correctamente. y que las consultas efectuadas contra la entidad derivada no
devuelvan datos vencidos. En el mapeo, <subselect> está disponible como atributo y
como elemento anidado.
Las clases mapeadas deben declarar la
columna de clave primaria de la tabla en la base de datos. La mayoría de las
clases también tendrán una propiedad del estilo JabaBeans conteniendo el
didentificador único de una instancia determinada. El elemento <id> define el mapeo de esa propiedad a la
columna de clave primaria.
<id
name="propertyName" (1)
type="typename" (2)
column="column_name" (3)
unsaved-value="null|any|none|undefined|id_value" (4)
access="field|property|ClassName"> (5)
node="element-name|@attribute-name|element/@attribute|."
<generator class="generatorClass"/>
</id> |
|
|
|
|
|
|
|
|
Si falta el atributo name , se asume que la clase no posee propiedad
indentificadora.
El atributo unsaved-value casi nunca se usa en Hibernate3.
Hay una declaración alternativa de <composite-id>, para permitur el acceso a datos
de formato anticuado, con clave compuesta. Desaconsejamos su uso en
cualquier otro caso.
El elemento opcional hijo <generator> nombra a la clase de Java que se
usa para generar los identificadores únicos de la clase persistente. Si hace
falta algún parámetro para inicializar la instancia del generador, se pasa
usando el elemento <param>.
<id name="id" type="long" column="cat_id">
<generator class="org.hibernate.id.TableHiLoGenerator">
<param name="table">uid_table</param>
<param name="column">next_hi_value_column</param>
</generator>
</id> Todos los generadores implementan la interfaz org.hibernate.id.IdentifierGenerator. Es una interfaz
muy simple; algunas aplicaciones podrían elegir proveer su propia
implementación a medida. De todos modos, Hibernate provee una variedad de
implementaciones que ya vienen incluidas. Los siguientes son los apodos
mnemónicos para estos generadores ya incluidos:
increment genera identificadores de tipo long, short o int que son únicos solamente cuando ningún otro
proceso está insertando datos en la misma tabla. No usar en un cluster.
identity soporta "columnas de identidad" (identity) en DB2, MySQL, MS SQL
Server, Sybase e HypersonicSQL. El identificador que se devuelve es de
tipo long, short o int.
sequence usa una secuencia (sequence) en DB2, PostgreSQL, Oracle, SAP DB,
McKoi o un generador en Interbase. El identificador que se devuelve es
de tipo long, short o int
hilousa un algoritmo hi/lo para generar identificadores de los tipos
long, short o
int eficientemente, dada una tabla y una
columna (por defecto, hibernate_unique_key
y next_hi respectivamente) como fuente de
los valores "hi". El algorithmo hi/lo genera identificadores que son
únicos sólo para una base de datos en particular.
seqhilo usa un algoritmo hi/lo para generar identificadores de los tipos
long, short o
int eficientemente, dada una secuencia
(sequence) nombrada de base de datos.
uuidusa un algoritmo UUID de 128 bits para generar identificadores de tipo string, únicos para toda una red (se usa la dirección de IP). El UUID es codificado como una cadena de 32 dígitos hexadecimales.
guidusa una cadena GUID generada por la base de datos, en MS SQL Server y MySQL.
native elije identity, sequence o hilo,
dependiendo de las capacidades de la base de datos subyancente.
assigned deja que sea la aplicación la que asigne el identificador al
objeto antes de que save() sea llamado.
Ésta es la estrategia por defecto si no se especifica un elemento <generator>.
selectobtiene una clave primaria asignada por un trigger de la base de datos, seleccionando la fila por alguna clave única y obteniendo el valor de clave primaria.
foreign usa el identificador de algún otro objeto asociado. Normalmente
se usa en conjunción con una asocoación <one-to-one> por clave primaria.
sequence-identityuna generación de secuencias especializada que utiliza una sequencia de base de datos para la generación del valor en sí, pero lo combina con el método de JDBC3 getGeneratedKeys para devolver el valor final, como parte del comando INSERT. Esta estrategia sólo es soportada, que sepamos, por los drivers de Oracle 10g diseñados para JDK1.4. Note que los comentarios en estos comandos están inhabilitados, debido a un error en los drivers de Oracle.
Los generadores hilo y
seqhilo proveen dos implementaciones
alternativas del algoritmo hi/lo, un enfoque muy común para generar
identificadores. La primera implementación requiere una tabla de base de
datos especial que almacene el siguiente valor "hi" disponible. La segunda,
usa una secuencia al estilo de Oracle (si esto se soporta).
<id name="id" type="long" column="cat_id">
<generator class="hilo">
<param name="table">hi_value</param>
<param name="column">next_value</param>
<param name="max_lo">100</param>
</generator>
</id><id name="id" type="long" column="cat_id">
<generator class="seqhilo">
<param name="sequence">hi_value</param>
<param name="max_lo">100</param>
</generator>
</id> Desafortunadamente, usted no puede usar hilo cuando le provee su propia conexión (Connection) a Hibernate. Cuando Hibernate use una
fuente de datos de un servidor de aplicaciones para obtener conexiones
inscriptas en JTA, usted deberá configurar adecuadamente la hibernate.transaction.manager_lookup_class.
El UUID (siglas en inglés de "identificador universal único) contiene: la dirección de IP, el tiempo de comienzo de la JVM (al cuarto de segundo), la hora del sistema, y un valor contador que es único a lo ancho de la JVM. Desde el código Java no se puede obtener la dirección MAC o direcciones de memoria, así que esto es lo mejor que se puede lograr, sin usar JNI.
Con las BD que soportan columnas
de identidad (DB2, MySQL, Sybase, MS SQL), se puede usar la generación de
claves identity. Con las que soportan
secuencias (DB2, Oracle, PostgreSQL, Interbase, McKoi, SAP DB) se puede usar
el valor sequence. Ambas estrategias requieren
el uso de 2 comandos SQL para insertar un objeto nuevo.
<id name="id" type="long" column="person_id">
<generator class="sequence">
<param name="sequence">person_id_sequence</param>
</generator>
</id><id name="id" type="long" column="person_id" unsaved-value="0">
<generator class="identity"/>
</id> La estrategia native produce
un desarrollo más portátil entre distintas plataformas (cross-platform), ya
que elige entre identity, sequence e hilo,
dependiendo de las capadidades de la DB subyacente.
Si desea que sea la aplicación la
que asigne los identificadores (en lugar de que Hibernate los genere), usted
puede usar el "generador" assigned. Este
generador especial usa el valor de identificador ya asignado a la propiedad
indentificadora del objeto. Este generador se usa cuando la clave primaria
es una clave natural en lugar de una clave sustituta (surrogate key). Éste
es el comportamiento por defecto, si no se especifica el elemento <generator>.
Elegir el generador assigned, hace que Hibernate use unsaved-value="undefined", forzándolo a determinar si
una instancia es transitoria o desprendida, a menos que haya una propiedad
"version" o "timestamp", o usted defina Interceptor.isUnsaved().
Para esquemas de DB anticuados/heredados solamente (Hibernate no genera DDL con triggers).
<id name="id" type="long" column="person_id">
<generator class="select">
<param name="key">socialSecurityNumber</param>
</generator>
</id>(N del T): el número de seguridad social o SSN es un número único asignado por el Estado a cada persona en EE.UU.
En
el ejemplo precedente, hay un valor de propiedad único llamado socialSecurityNumber, definido por la clase, como
clave natural, y una clave primaria en la tabla llamada person_id, cuyo valor es generado por un trigger que
seleciona dicho número.
A partir de la versión 3.2.3, hay 2 nuevos generadores que representan un replanteo de 2 aspectos diferentes de la generación de identificadores. El primer aspecto, es la portabilida de base de datos, el segundo es la optimización (no tener que consultar a la base de datos cad vez que se requiera un valor de identificador nuevo). Estos dos nuevos generadores (a partir de la versión 3.3.x) han sido concebidos con el objetivo de reemplazar a algunos de los generadores nombrados anteriormente De todos modos, siguen incluyéndose en los lanzamientos actuales, y se puede hacer referencia a ellos via FQN.
El primero de estos nuevos generadores es el org.hibernate.id.enhanced.SequenceStyleGenerator, el
cual ha sido concebido como reemplazo del generador sequence, y como un generador con una portabilidad
superior a native (porque native (generalmente) elije entre identity y sequence, las
cuales tienen en general semánticas distintas, lo cual puede causar sutiles
problemas aplicaciones que consideren portabilidad). org.hibernate.id.enhanced.SequenceStyleGenerator, sin
embargo, consigue la portabilidad de una manera diferente. Elije entre usar
una tabla o una secuencia en la base de datos para almacenar sus valores
incrementados, dependiendo de las capacidades del dialecto que esté siendo
usado. La diferencia entre esto y native, es
que el almacenamiento basado en tablas y el basado en secuencias tienen
exactamente la misma semántica (de hecho, las secuencias son exactamente lo
que Hibernate trata de emular con sus generadores badados en tablas). Este
generador tiene varios parámetros de configuración:
sequence_name (optativo, por
defecto, hibernate_sequence): El nombre de
la secuencia (o tabla) a ser usada.
initial_value (optativo, por
defecto, 1): El valor inicial a ser
devuelto por la tabla/secuencia. En términos de la creación de la
secuencia, esto es análogo a la cláusula típica "STARTS WITH".
increment_size (optativo, por
defecto, 1): El incremento usdo en llamadas
ulteriores a la tabla/secuencia. En términos de la creación de la
secuencia, esto es análogo a la cláusula típica "INCREMENT BY".
force_table_use (optativo, por
defecto, false): Deberíamos forzar el uso
de una tabla como estructura de respaldo, incluso si el dialecto soporta
secuencias?
value_column (optativo, por
defecto, next_val): ¡Sólo relevante para
estructuras de tabla! El nombre de la columna en la tabla usada para
almacenar el valor.
optimizer (optativo, por defecto,
none): Vea Sección 5.1.6,
“Optimización de los generadores de identificador”
El segundo de estos nuevos generadores es org.hibernate.id.enhanced.TableGenerator, el cual ha
sido primariamente concebido en reemplazo del generador table, (aunque, en realidad, funciona mucho más como
un org.hibernate.id.MultipleHiLoPerTableGenerator).
Secundariamente, ha sido concebido como una reimplementación de org.hibernate.id.MultipleHiLoPerTableGenerator que
utiliza la noción de optimizadores "enchufables" (pluggable). En esencia,
este generador define una tabla que es capaz de contener múltiples valores
de incremento distintos, usando múltiples registros con claves diferentes.
Este generador tiene varios parámetros de configuración:
table_name (optativo, por defecto,
hibernate_sequences): El nombre de la tabla
a usar.
value_column_name (optativo, por
defecto, next_val): El nombre de la columna
en la tabla, que será usada para contener el valor.
segment_column_name (optativo, por
defecto, sequence_name): El nombre de la
columna en la tabla que setá usada para contener la "clave de segmento".
Este es el valor que identifica de manera distintiva qué valor de
incremento usar.
segment_value (optativo, por
defecto, default): El valor de "clave de
segmento" del cual queremos extraer valores de incremento para este
generador.
segment_value_length (optativo, por
defecto, 255): Usado para la generaciónde
esquemas; el tamaño del campo de esta "clave de segmento".
initial_value (optativo, por
defecto, 1): El valor inicial a ser
devuelto por la tabla.
increment_size (optativo, por
defecto, 1): El valor por el cual las
llamdas subsiguientes a la tabla deberían diferir.
optimizer (optativo, por defecto,
): Vea Sección 5.1.6,
“Optimización de los generadores de identficadores”
Para los identificadores que almacenan valores en la base de datos, es ineficiente acudir a la base de datos cada una de las veces en que se necesita generar un nuevo valor de identificador. En lugar de ello, lo ideal es agrupar un buen número de ellos en la memoria, y solamente acudir a la base de datos cuando se ese grupo de valores se haya agotado. Ésta es la función de los "optimizadores enchufables". Por el momento sólo los dos generadoeres mejorados (Sección 5.1.5, “Generadores mejorados de identificadores ”) soportan esta noción.
none (éste es, generalmente, el
valor por defecto si no se especificó ningún optimizador): Esto indica
que no se efectúe ninguna optimización, y que se acuda a la base de
datos para todos y cada uno de los pedidos.
hilo: aplica un algoritmo hi/lo en
torno a los valores devueltos por la base de datos. Se espera que los
valores de base de datos para este optmizador sean secuenciales. Los
valores devueltos desde la estructura de base de datos para este
optmizador indican el "número de grupo"; el increment_size (tamaño del incremento) se
multiplica por el valor en memoria para definir un grupo "hi value".
pooled: como fue discutido para
hilo, estos optimizadores intentan
minimizar el número de viajes a la base de datos. Aquí, sin embargo,
simplemente almacenamos en valor inicial para el "próximo grupo" en la
estructura de base de datos, más que un valor secuencial en combinación
con un algoritmo de agrupamiento en memoria.Aquí, increment_size se refiere a los valores que
vienen de la base de datos.
<composite-id
name="propertyName"
class="ClassName"
mapped="true|false"
access="field|property|ClassName">
node="element-name|."
<key-property name="propertyName" type="typename" column="column_name"/>
<key-many-to-one name="propertyName class="ClassName" column="column_name"/>
......
</composite-id> En una tabla con clave compuesta, se puede
mapear varias de sus propiedades como identificadores. El elemento <composite-id> acepta mapeos de propiedades
<key-property> y mapeos como elementos
hijos.
<composite-id>
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id> Su clase persistente debe reemplazar equals() y hashCode()
para implementar igualdad entre identificadores compuestos. También debe
implementar Serializable.
Desafortunadamente, este abordaje de los identificadores significa que el objeto persistente es su propio identificador. No hay otro "puntero" al objeto que no sea el objeto mismo. Se debe instanciar el objeto persistente mismo, y poblarle sus propiedades identificadores antes de poder cargarlo en estado persistente asociado con una clave primaria. A este enfoque lo llamamos "identificador compuesto incrustado (embedded)", y lo desaconsejamos para cualquier aplicación seria.
El segundo abordaje es lo que
llamamos "identificador compuesto mapeado", en donde las propiedades
identificador usadas dentro del <composite-id> son duplicadas tanto en la clase
persistente como en una clase identificadora separada.
<composite-id class="MedicareId" mapped="true">
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id> (N.del.T): "Medicare" es el nombre en inglés
del seguro estatal de salud en EE.UU. y otros países.
En este
ejemplo, tanto la clase del identificador compuesto, MedicareId, como la clase de la entidad misma tienen
propiedades llamadas medicareNumber y dependent. La clase identificadora debe reemplazar
equals() y hashCode() e implementar Serializable. La desventaja de este abordaje es
obvia: duplicación de código.
Los siguientes atributos son usados para especificar un identificador compuesto mapeado:
mapped (optativo, por defecto,
false): indica que se usa un identificador
mapeado compuesto, y que los mapeos contenidos de propiedades se
refieren tanto a la clase entidad como a la clase del identificador
compuesto.
class (optativo, pero requerido
para un identificador compuesto mapeado): La clase usada como
identificador compuesto.
Vamos a describir una tercera manera, aún más conveniente de enfocar el tema de los modificadores compuestos, en donde el identificador compuesto es implementado como una clase componente en Sección 8.4, “Componentes como identificadores compuestos”. Los atributos descritos a continuación se aplican sólo a este enfoque alternativo:
name (optativo, pero obligatorio
para este abordaje): Una propiedad de tipo "componente" que contiene el
identificador compuesto (ver capítulo 9).
access (optativo, por defecto,
property): La estrategia que Hibernate
debería usar para acceder al valor de las propiedades.
class (optativo, por defecto, el
tipo de propiedad determinado por reflexión): La clase componente usada
como identificador compuesto (ver la sección siguiente).
Este tercer abordaje, un compoennte identificador, es el que recomendamos para casi todas las aplicaciones.
El elemento <discriminator> se requiere para la
persistencia polimórfica que use la estrategia de mapeo de "una tabla por
cada jerarquía de clases" y declara una columna como el "discriminador" de
la tabla. La columna "discriminador" contiene un valor rótulo, que le dice a
la capa de persistencia qué clase debe instanciar para cada registro en
particular. Sólo un número restringido de tipos puede ser usado: string, character, integer, byte, short, boolean, yes_no, true_false.
<discriminator
column="discriminator_column" (1)
type="discriminator_type" (2)
force="true|false" (3)
insert="true|false" (4)
formula="arbitrary sql expression" (5)
/> |
|
|
|
|
|
|
|
|
Los verdaderos valores de la columna del
discriminador son especificados por el atributo discriminator-value de los elementos <class> y <subclass>.
El atributo force solamente es útil si la tabla contiene valores
de discriminador "adicionales" que no están mapeados a una clase
persistente. Éste no es casi nunca el caso.
Usando el atributo
formula, se puede declarar una expresión SQL
arbitraria que puede ser usada para evaluar el tipo de una fila.
<discriminator
formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end"
type="integer"/> El elemento <version> element es optativo e indica que la
tabla contiene datos con versión. Esto es particularmente útil si se planea
usar transacciones largas (véase más
abajo).
<version
column="version_column" (1)
name="propertyName" (2)
type="typename" (3)
access="field|property|ClassName" (4)
unsaved-value="null|negative|undefined" (5)
generated="never|always" (6)
insert="true|false" (7)
node="element-name|@attribute-name|element/@attribute|."
/> |
|
|
|
|
|
|
|
|
|
|
|
|
Los números de versión pueden ser de los tipos
de Hibernate long, integer, short, timestamp o calendar.
Una propiedad de versión o timestamp nunca debería ser nula para
una instancia desprendida, de manera que Hibernate pueda identificar
cualquier instancia con una versión o timestamp nulos como transitoria, sin
importar qué otras estrategias de unsaved-value
se hayan usado. Declarar una propiedad versión o
timestamp como anulable es un forma fácil de evitar problemas con la
revinculación transitiva en Hibernate, ¡especialmente útil para quienes usen
identificadores asignados o claves compuestas!
El elemento optativo <timestamp> indica que la tabla contiene datos
marcados con fecha y hora. Esto apunta a ser una alternativa a asignar
números de versión. Las timestamps son, por naturaleza, una implementación
menos segura de "locking" optimista. De todos modos, la aplicación podría
usar timestamps de otras formas.
<timestamp
column="timestamp_column" (1)
name="propertyName" (2)
access="field|property|ClassName" (3)
unsaved-value="null|undefined" (4)
source="vm|db" (5)
generated="never|always" (6)
node="element-name|@attribute-name|element/@attribute|."
/> |
|
|
|
|
|
|
|
|
|
|
Note que <timestamp> equivale a <version type="timestamp">. Y <timestamp source="db"> equivale a <version type="dbtimestamp">
El elemento <property> declara una propiedad pesistente de
la clase, estilo JavaBeans.
<property
name="propertyName" (1)
column="column_name" (2)
type="typename" (3)
update="true|false" (4)
insert="true|false" (4)
formula="arbitrary SQL expression" (5)
access="field|property|ClassName" (6)
lazy="true|false" (7)
unique="true|false" (8)
not-null="true|false" (9)
optimistic-lock="true|false" (10)
generated="never|insert|always" (11)
node="element-name|@attribute-name|element/@attribute|."
index="index_name"
unique_key="unique_key_id"
length="L"
precision="P"
scale="S"
/> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typename puede ser:
El nombre de un tipo básico de Hibernate (por ejemplo integer, string, character, date, timestamp, float,
binary, serializable, object, blob).
El nombre de una clase de Java con un tipo básico por defecto
(por ejemplo int, float, char, java.lang.String,
java.util.Date, java.lang.Integer, java.sql.Clob).
El nombre de una clase de Java serializable.
El nombre de un tipo de Java hecho a medida (por ejemplo, com.mipaquete.MiTipoPersonalizado).
Si no se especifica un tipo, Hibernate usará reflexión en
la propiedad nombrada, para adivinar el tipo Hibernate correcto. Hibernate
intentará interpretar el nombre de la clase devuelta por el método "getter"
de la propiedad, usando las reglas 2, 3 y 4, en ese orden. De todos modos,
esto no siempre es suficiente. En algunos casos, aún se necesita el atributo
type (por ejemplo, para distinguir entre Hibernate.DATE e Hibernate.TIMESTAMP, o para especificar un tipo hecho
a medida).
El atributo access le
permite controlar cómo Hibernate accederá a la propiedad en tiempo de
ejecución. Por defecto, Hibernate invocará al par get/set. Si usted
especifica access="field", Hibernate salteará
el par get/set y accederá al campo directamente, usando reflexión. Usted
puede especificar su propia estrategia de acceso, nombrando una clase que
implemente la interfaz org.hibernate.property.PropertyAccessor.
Las
propiedades derivadas son una característica especialmente poderosa. Estas
propiedades son de sólo lectura, por definición. Son computadas en el
momento en que se cargan. Se declara dicha computación como un comando SQL,
que se traduce en una subconsulta SELECT en el
código SQL que carga la instancia.
<property name="totalPrice"
formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/> Note que
usted se puede referir a la tabla misma de la entidad, evitando usar el
alias para una columna en particular (en el ejemplo, customerId). También note que puede usar elementos
<formula> anidados si no le gusta
especificar el valor como un atributo.
Una asociación común con otra clase persistente se declara
usando un elemento many-to-one element. El
modelo relacional es una asociación de-muchos-a-uno: la clave foránea en una
tabla se refiera a la(s) columna(s) clave de la tabla de destino.
<many-to-one
name="propertyName" (1)
column="column_name" (2)
class="ClassName" (3)
cascade="cascade_style" (4)
fetch="join|select" (5)
update="true|false" (6)
insert="true|false" (6)
property-ref="propertyNameFromAssociatedClass" (7)
access="field|property|ClassName" (8)
unique="true|false" (9)
not-null="true|false" (10)
optimistic-lock="true|false" (11)
lazy="proxy|no-proxy|false" (12)
not-found="ignore|exception" (13)
entity-name="EntityName" (14)
formula="arbitrary SQL expression" (15)
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
index="index_name"
unique_key="unique_key_id"
foreign-key="foreign_key_name"
/> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Asignarle cualquier valor que tenga sentido al
atributo cascade que no sea none propagará ciertas operaciones al objeto
asociado. Los valores que tienen sentido son las operaciones básicas de
Hibernate, persist, merge, delete, save-update, evict,
replicate, lock, refresh, así como los valores especiales delete-orphan y all, y
combinaciones de nombres de operacion separados por comas, por ejemploc ascade="persist,merge,evict" o cascade="all,delete-orphan". Vea la Sección 10.11, “Persistencia
transitiva” para una explicación completa. Note que las asociaciones a
un solo valor (de-muchos-a-uno y de-uno-a-uno) no soportan el borrado de
huérfanos.
Una declaración many-to-one
típica se ve tan simple como esto:
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
El atributo property-ref debe ser usado
solamente para datos heredados/anticuados en donde la clave foránea apunte a
una clave única de la tabla asociada que no es la clave primaria. Éste es un
modelo relacional feo. Por ejemplo, suponga que la clase Product class tiene un número de serie único, que no
es la clave primaria. (El atributo unique
controla en Hibernate la generación de DDL con la herramienta SchemaExport).
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
Entonces, el mapeo para OrderItem podría usar:
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
De todos modos, esto se desaconseja.
Si la clave única referida
comprende múltiples propiedades de la entidad asociada, usted debería mapear
las propiedades referidas dentro de un elemento llamado <properties>.
Si la clave única referida es la propiedad de un componente, se puede especificar un "path de propiedades":
<many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/>
Una asociación de-uno-a-uno a otra clase
persistente se declara usando el elemento one-to-one.
<one-to-one
name="propertyName" (1)
class="ClassName" (2)
cascade="cascade_style" (3)
constrained="true|false" (4)
fetch="join|select" (5)
property-ref="propertyNameFromAssociatedClass" (6)
access="field|property|ClassName" (7)
formula="any SQL expression" (8)
lazy="proxy|no-proxy|false" (9)
entity-name="EntityName" (10)
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
foreign-key="foreign_key_name"
/> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Hay dos variantes de asociación de-uno-a-uno:
asociaciones por clave primaria.
asociaciones por clave foránea única.
Las asociaciones por clave primaria no necesitan una columna extra en la tabla; si dos filas están relacionadas por esta asociación, entonces las dos filas en las respectivas tablas comparten el mismo valor de clave primaria. ¡Así que si usted quiere que dos objetos estén relacionados por la misma asociación de clave primaria, tiene que asegurarse de que se les asigne el mismo valor de identificador!
Para una asociación por clave primaria, agréguele los siguientes mapeos a
Employee y Person,
respectivamente.
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
Ahora, debemos asegurarnos de que las claves primarias de las filas
relacionadas en las tablas PERSON y EMPLOYEE son iguales. Usamos una
estrategia especial de Hibernate para asegurarnos de que las claves
primarias de filas relacionadas sean iguales: un a estrategia de generación
de identificador llamada foreign:
<class name="person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="foreign">
<param name="property">employee</param>
</generator>
</id>
...
<one-to-one name="employee" class="Employee" constrained="true"/>
</class> Entonces, a una instancia recientemente creada de
Person se le asigna el mismo valor de clave
primaria que a la instancia de Employee
referida por la propiedad employee.
Alternativamente, una clave foránea que apunte a una constraint única de
Employee a Person,
puede expresarse como:
<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>
Y esta asociación puede ser convertida en bidireccional agregando lo
siguiente al mapeo de Person:
<one-to-one name="employee" class="Employee" property-ref="person"/>
<natural-id mutable="true|false"/>
<property ... />
<many-to-one ... />
......
</natural-id> Aunque recomendamos el uso de clave sustitutas
como claves primarias, aún se deberían identificar claves naturales para
todas las entidades. Una clave natural es una propiedad o combinación de
propiedades que sea única y no nula. Si también es inmutable, mejor todavía.
Mapee las propiedades de la clave natural dentro del elemento <natural-id>, e Hibernate generará las
constraints necesarias de unicidad y nulabilidad, y su mapeo quedará más
auto-documentado.
Recomentamos fuertemente que implemente equals() y hashCode()
para comparar las propiedades de la clave natural de la entidad.
Este mapeo no debería usarse con entidades para las cuales la clave primaria es la clave natural.
mutable (optativo, por defecto,
false): Por defecto, propiedades
identificador naturales que asume son inmutables (constantes).
El elemento <component> mapea propiedades de un objeto hijo
a columnas de la tabla de la clase padre. Los componenes a su vez pueden
declarar sus propias propiedades, componentes o colecciones. Vea
"Componentes" a continuación.
<component
name="propertyName" (1)
class="className" (2)
insert="true|false" (3)
update="true|false" (4)
access="field|property|ClassName" (5)
lazy="true|false" (6)
optimistic-lock="true|false" (7)
unique="true|false" (8)
node="element-name|."
>
<property ...../>
<many-to-one .... />
........
</component> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Las propiedades <property> hijas mapean propiedades de la clase
hija a columnas de la tabla.
El elemento <component> acepta un subelemento <parent> que mapea una propiedad del componente
como una referencia que apunta de vuelta a la entidad contenedora.
El elemento <dynamic-component> permite
que un Map sea mapeado como componente, en
donde el nombre de la propiedad se refiere a claves del mapa, véase Sección 8.5,
“Componentes dinámicos”.
El elemento <properties> permite la definición de un
agrupamiento lógico, con un nombre, de algunas propiedades de una clase. La
utilidad más importante de este tipo de construcción, es que permite que una
combinación de propiedades sea el blanco de un property-ref. También es una manera conveniente de
definir una constraint de unicidad.
<properties
name="logicalName" (1)
insert="true|false" (2)
update="true|false" (3)
optimistic-lock="true|false" (4)
unique="true|false" (5)
>
<property ...../>
<many-to-one .... />
........
</properties> |
|
|
|
|
|
|
|
|
Por ejemplo, si tenemos el siguiente mapeo de
<properties>:
<class name="Person">
<id name="personNumber"/>
...
<properties name="name" unique="true" update="false">
<property name="firstName"/>
<property name="initial"/>
<property name="lastName"/>
</properties>
</class> Y entonces podemos tener una asociación de datos al
estilo anticuado, que se refiera a esta clave única de la tabla Person table, en lugar de a la clave primaria.
<many-to-one name="person" class="Person" property-ref="name">
<column name="firstName"/>
<column name="initial"/>
<column name="lastName"/>
</many-to-one>Pero no recomendamos esto, excepto en el contexto de un mapeo a datos anticuados/heredados.
Finalmente, la persistencia polimórfica
requiere la declaracíón de cada subclase de la clase persistente raíz. Para
la estrategia de mapeo una-tabla-por-clase, se usa la declaración <subclass>.
<subclass
name="ClassName" (1)
discriminator-value="discriminator_value" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
entity-name="EntityName"
node="element-name"
extends="SuperclassName">
<property .... />
.....
</subclass> |
|
|
|
|
|
|
Cada subclase debería declara sus propias
propiedades persistentes y subclases. Se asume que las propiedades <version> e <id> serán heredadas de la clase raíz. Cada
subclase en la jerarquía debe definir un valor único de discriminator-value. Si no es especificado ninguno,
se usa el nomre enteramente calificado de la clase de Java.
Para información sobre el mapeo de herencias, vea Capítulo 9, Mapeo de Herencia.
Alternativamente, cada subclase puede ser mapeada a
su propia tabla (la estrategia de mapeo "una-table-por-subclase"). El estado
heredado se captura haciendo un "join" con la tabla de la superclase. Usamos
el elemento <joined-subclass>.
<joined-subclass
name="ClassName" (1)
table="tablename" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
<key .... >
<property .... />
.....
</joined-subclass> |
|
|
|
|
|
|
Para esta estrategia de mapeo no se requiere
ninguna columna discriminadora. Sin embargo, cada subclase debe declarar una
columna de tabla que contenga al identificador de objeto, usando el elemento
<key>. El mapeo del comienzo del capítulo
sería rescrito así:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class name="Cat" table="CATS">
<id name="id" column="uid" type="long">
<generator class="hilo"/>
</id>
<property name="birthdate" type="date"/>
<property name="color" not-null="true"/>
<property name="sex" not-null="true"/>
<property name="weight"/>
<many-to-one name="mate"/>
<set name="kittens">
<key column="MOTHER"/>
<one-to-many class="Cat"/>
</set>
<joined-subclass name="DomesticCat" table="DOMESTIC_CATS">
<key column="CAT"/>
<property name="name" type="string"/>
</joined-subclass>
</class>
<class name="eg.Dog">
<!-- acá podría ir el mapeo de Dog -->
</class>
</hibernate-mapping>Para información sobre los mapeos de herencias, vea Capítulo 9, Mapeo de Herencia.
Una tercera opción es mapear a tablas sólo las
clases concretas de la jerarquía de herencias. (la estrategia de
"una-tabla-por-clase-concreta"), en la cual una tabla define todo el estado
persustente de la clase, incluido el estado persistente. En Hibernate, no es
absolutamente necesario mapear dichas jerarquías de herencia. Se puede,
simplemente, mapear cada clase con una declaracíón separada de <class>. De todos modos, si desea usar
asociaciones polimórficas (por ejemplo, una asociación a la superclase de su
jeraquía), es necesario usar un mapeo con <union-subclass>.
<union-subclass
name="ClassName" (1)
table="tablename" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
abstract="true|false"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
<property .... />
.....
</union-subclass> |
|
|
|
|
|
|
Para esta estrategia de mapeo no se requiere una columna discriminadora.
Para información sobre los mapeos de herencias, vea Capítulo 9, Mapeo de Herencia.
Usando el elemento <join>, es posible
mapear propiedades de una clase a varias tablas, cuando hay una relacion
de-uno-a-uno entre dichas tablas.
<join
table="tablename" (1)
schema="owner" (2)
catalog="catalog" (3)
fetch="join|select" (4)
inverse="true|false" (5)
optional="true|false"> (6)
<key ... />
<property ... />
...
</join> |
|
|
|
|
|
|
|
|
|
|
Por ejemplo, la información de la dirección de una perosna puede ser mapeada en una tabla separada, al tiempo que se preserva la semántica para todas las propiedades.
<class name="Person" table="PERSON">
<id name="id" column="PERSON_ID">...</id>
<join table="ADDRESS">
<key column="ADDRESS_ID"/>
<property name="address"/>
<property name="zip"/>
<property name="country"/>
</join>
... Esta característica es a menudo sólo útil para modelos de
datos anticuados. Recomendamos que haya menos tablas que clases, y un modelo
de dominio bien detallado.
N.del T.: se les suele llamar
"detallados" (en inglés "fine-grained") a los modelos con clases que
contiengan otras clases como miembro, por ejemplo, una clase Dirección
dentro de una clase Persona, en lugar de incluir los campos de la direccíón
(calle, número, etc) directamente en Persona.
Aunque los campos de
Dirección probablemente se terminen persistiendo en la misma tabla que los
de Persona, se considera que una representación jerárquica que use las
clases Persona y Dirección es más "detallada" que la simple tabla PERSONA
subyacente
De todos modos, "join-table" es útil para
alternar entre distintas estrategias de mapeo en una misma jerarquía, como
se explica más adelante.
Hemos visto surgir el elemento <key>
varias veces ya. Aparece cada vez que el mapeo de un elemento padre define
una asociación a una nueva tabla, que haga referencia a la clave primaria de
la tabla original.
<key
column="columnname" (1)
on-delete="noaction|cascade" (2)
property-ref="propertyName" (3)
not-null="true|false" (4)
update="true|false" (5)
unique="true|false" (6)
/> |
|
|
|
|
|
|
|
|
|
|
Recomendamos que, para sistemas en los cuales
la performance de delete sea importante, toda las claves sean definidas con
on-delete="cascade", e Hibernate generará una
constraint ON CASCADE DELETE a nivel de la base
de datos, en lugar de varios comandos DELETE
individuales. Tenga presente que esta característica saltea la estrategia
usual de locking optimista para datos versionados.
Los atributos
not-null y update
son útiles cuando se mapea una asociación de-uno-a-muchos unidireccional. Si
la mapea a una clave foránea no anulable, hay que declarar la columa clave usando
<key not-null="true">.
Cualquier elemento de mapeo que
acepte un atributo column aceptará,
alternativamente, un subelemento <column>. De la misma manera, el elemento <formula> es una alternativa al atributo formula.
<column
name="column_name"
length="N"
precision="N"
scale="N"
not-null="true|false"
unique="true|false"
unique-key="multicolumn_unique_key_name"
index="index_name"
sql-type="sql_type_name"
check="SQL expression"
default="SQL expression"/><formula>SQL expression</formula>
Los atributos column y formula pueden incluso ser combinados dentro de la
mismo mapeo de propiedad o asociación, para expresar, por ejemplo,
codiciones de asociación exóticas.
<many-to-one name="homeAddress" class="Address"
insert="false" update="false">
<column name="person_id" not-null="true" length="10"/>
<formula>'MAILING'</formula>
</many-to-one> Supongamos que su aplicación tiene dos clases persistenes con el
mismo nombre, y usted no quiere especificar un nombre de clase enteramente
calificado (con el paquete) en las consutas de Hibernate. Las clases pueden
ser "importadas" explícitamente en lugar de tener que confiar en el auto-import="true". Usted puede incluso importar
clases e interfaces que no estén mapeadas explícitamente.
<import class="java.lang.Object" rename="Universe"/>
<import
class="ClassName" (1)
rename="ShortName" (2)
/> |
|
|
Hay un tipo más de mapeo de propiedad: El elemento de mapeo <any> define una asociación polimórfica a
clases de múltiples tablas. Este tipo de mapeo siempre requiere más de una
columna. La primera columna contiene el tipo de la entidad asociada. Es
imposible especificar una constraint de clave foránea para este tipo de
asociaciones. Esto se debería usar sólo en casos muy especiales (por
ejemplo, logs de auditoría, datos de sesión de usuario, etc).
El
atributo meta-type le permite a la aplicación
especificar un tipo hecho a medida, que mapeará valores de columna en la
base de datos a clases persistentes, las cuales tendrán propiedades
indentificadoras del tipo identificado por id-type. Se debe especificar el mapeo desde los
valores valores del meta-tipo a los nombres de las clases.
<any name="being" id-type="long" meta-type="string">
<meta-value value="TBL_ANIMAL" class="Animal"/>
<meta-value value="TBL_HUMAN" class="Human"/>
<meta-value value="TBL_ALIEN" class="Alien"/>
<column name="table_name"/>
<column name="id"/>
</any><any
name="propertyName" (1)
id-type="idtypename" (2)
meta-type="metatypename" (3)
cascade="cascade_style" (4)
access="field|property|ClassName" (5)
optimistic-lock="true|false" (6)
>
<meta-value ... />
<meta-value ... />
.....
<column .... />
<column .... />
.....
</any> |
|
|
|
|
|
|
|
|
|
|
Para comprender el comportamiento de varios objetos (a nivel del lenguaje Java) con respecto al servicio de persistencia, necesitamos clasificarlos en 2 grupos:
Una entidad (en inglés, "entity") existe independientemente de que cualquier otro objeto tenga o no referencias a ella. Contraste esto con el modelo de Java normal, en donde un objeto sufre "garbage colection" en cuanto dejan de referirse a él. Las entidades deben ser grabadas y borradas explícitamente (excepto cuando los borrados o grabaciones en cascada se transmiten de padre a hijos). Éste es un tipo distinto de manejo de objetos de datos (ODMG, por sus siglas en inglés), que consiste en la persistencia de objetos basada en la capacidad de acceder a ellos, lo cual corresponde más íntimamente con cómo los objetos de una aplicación son usados en sistemas grandes. Las entidades soportan referencias compartidas y circulares, y pueden ser versionadas.
El estado persistente de una entidad consiste en referencias a otras entidades, y a instancias de lo que en Hibernate se denomina "value types" (tipos "valor"), Los "value types" son los tipos primitivos, las colecciones (el componente colección en sí, no lo que está dentro de ellas), y ciertos objetos inmutables. Los "value types" no pueden ser grabados ni versionados independientemente, sino que son persistidos junto con la entidad que los contiene. No tienen "identidad" independiente, y por lo tanto no pueden ser compartidos entre dos o más entidades, ni entre colecciones.
Hasta ahora hemos usado el término "clase persistente" para referirnos a entidades. Lo seguiremos haciendo, pero, estrictamente hablando, sin embargo, un "componente" (en inglés, component) también es una clase, definida por el usuario, que es persistente. La diferencia es que estos "componentes" tienen la semántica de un "value type", no la de una entidad. Un "value type" puede ser, entonces, un tipo primitivo de la JDK, o una String, o un tipo definido por el usuario. Una clase definida por el usuario puede ser, a criterio del programador de la aplicación, un "value type" o una entidad. En un modelo de dominio, ser una entidad en general implica que varios otros objetos compartirán referencias a ella, mientras que los "value types" simplemente forman parte de una única clase, vía composición o agrupación (en inglés, "composition" y "aggregation").
Retomaremos estos conceptos todo a lo largo de la documentación.
El desafío es mapear
el sistema de tipos de Java (junto con la definición del desarrollador de
las entidades y "value types") a un sistema del tipo SQL/BD. El puente entre
ambos sistemas es provisto por Hibernate. Para las entidades proveemos <class>, <subclass> y así sucesivamente. Para los "value
types" usamos <property>, <component>, etc., normalmente con un atributo
type. El valor de este atributo es uno de los
tipos para mapeo de Hibernate.
Hibernate trae varios mapeos (para los tipos estándar de la JDK) ya
incluidos. Pero usted puede escribir sus propios tipos para mapeo e
implementar sus propias estrategias de conversión, como veremos más
adelante.
Todos los tipos que vienen ya incluidos en Hibernate, excepto las colecciones, soportan la semántica de nulo.
Los tipos de mapeo básicos ya incluidos pueden ser someramente clasificados en:
integer, long, short,
float, double, character, byte, boolean, yes_no,
true_false Mapeos de los respectivos tipos primitivos o "clases envoltorio"
a valores de columna SQL (que dependen de la marca de la BD): boolean, yes_no y true_false son todas codificaciones alternativas
de un boolean o un java.lang.Boolean de Java.
string Un mapeo de tipo de java.lang.String a VARCHAR (o el VARCHAR2 de Oracle).
date, time,
timestamp Mapeos de tipo de java.util.Date y
sus subclases a tipos SQL DATE, TIME y TIMESTAMP (o
equivalentes).
calendar,
calendar_date Mapeos de tipo de java.util.Calendar a tipos SQL TIMESTAMP y DATE (o
equivalente).
big_decimal,
big_integer Mapeos de tipo de java.math.BigDecimal y java.math.BigInteger a NUMERIC (e el NUMBER
de Oracle).
locale, timezone,
currency Mapeos de tipo de java.util.Locale,
java.util.TimeZone y java.util.Currency a VARCHAR (o el VARCHAR2 de Oracle). Las instancias de Locale y Currency
son mapeadas a sus códigos ISO. Las instancias de TimeZone son mapeadas a su ID.
class Un mapeo de tipo java.lang.Class a
VARCHAR (o la VARCHAR2 de Oracle). Una Class es mapeada con su nombre enteramente
calificado.
binaryMapea arrays de bytes a un tipo SQL binario apropiado.
text Mapea cadenas largas de Java los tipos CLOB o TEXT de SQL.
serializable Mapea tipos serializables de Java a un tipo SQL binario
apropiado. También se puede indicar el tipo de Hibernate serializable con el nombre de una clase o
interfaz serializable de Java, que no sea por defecto un tipo básico.
clob,
blob Mapeos de tipo para las clases JDBC java.sql.Clob y java.sql.Blob. Estos tipos pueden ser
inconvenientes para algunas aplicaciones, dado que los objetos clob y
blob no pueden ser reusados fuera de una transacción. (Más aún, el
soporte de drivers es esporádico e inconsistente).
imm_date, imm_time,
imm_timestamp, imm_calendar, imm_calendar_date, imm_serializable,
imm_binary Mapeos de tipo para lo que normalmente se considera "tipos
mutables de Java", en los que Hibernate adopta ciertas optimizaciones
que son sólo apropiadas para tipos inmutables de Java, y la aplicación
trata al objeto como inmutable. Por ejemplo: para una instancia mapeada
como imm_timestamp, no se debería invocar
Date.setTime(). Para cambiar el valor de la
propiedad (y hacer que ese valor cambiado sea persistido), la aplicación
tiene que asignarle un nuevo objeto (no idéntico) a la propiedad.
Los identificadores únicos de las entidades y
colecciones pueden ser de cualquier tipo básico excepto binary, blob y clob. (Los identiicadores compuestos también están
permitidos, ver más adelante).
Los "value types" básicos tienen
constantes Type definidas en org.hibernate.Hibernate. Por ejemplo, Hibernate.STRING representa el tipo string.
Para los programadores, es relativamente fácil
crear sus propios "value types". Por ejemplo, usted podría querer persistir
propiedades del tipo java.lang.BigInteger a
columnas VARCHAR. Hibernate no trae un tipo ya
incluido para esto. Pero los tipos a medida no solamente sirven para mapear
una propiedad de Java (o un elemento colección) a una sola columna de una
tabla. Por ejemplo, usted puede tener una propiedad Java con métodos getNombre()/setNombre()
de tipo java.lang.String que sea persistida
varias columnas, como PRIMER_NOMBRE, INICIAL_DEL_SEGUNDO, APELLIDO.
Para crear un tipo a medida,
implemente org.hibernate.UserType o org.hibernate.CompositeUserType y declare propiedades
usando el nombre enteramente calificado del tipo. Revise org.hibernate.test.DoubleStringType para comprobar el
tipo de cosas que es posible hacer.
<property name="twoStrings" type="org.hibernate.test.DoubleStringType">
<column name="first_string"/>
<column name="second_string"/>
</property> Note el uso de los elementos <column> para mapear una sola propiedad a
múltiples columnas.
Las interfaces CompositeUserType, EnhancedUserType, UserCollectionType, y UserVersionType poveen soporte para usuarios más
especializados.
Incluso se le pueden proveer parámetros a un UserType en el archivo de mapeo. Para lograr esto, su
UserType debe implementar la interfaz org.hibernate.usertype.ParameterizedType. Para
proveerle parámetros a su tipo a medida, usted puede usar el elemento <type> en sus archivos de mapeo.
<property name="priority">
<type name="com.mycompany.usertypes.DefaultValueIntegerType">
<param name="default">0</param>
</type>
</property> Ahora el UserType
puede aceptar valrores para el parámetro llamado default con el objeto Properties que le fue pasado.
Si se usa un
objeto UserType muy a menudo, sería útil
definirle un nombre corto. Esto se puede hacer usando el elemento <typedef>. Los "typedefs" le asignan un nombre
a un tipo a medida, y pueden contener también una lista de parámetros por
defecto si el tipo es parametrizado.
<typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero">
<param name="default">0</param>
</typedef><property name="priority" type="default_zero"/>
También es posible sustituir (override) los parámetros provistos en un typedef, caso por caso, usando parámetros de tipo en el mapeo de propiedades.
Aunque la rica variedad de tipos ya incluidos en
Hibernate hace que sólo en contadas ocasiones realmente se necesite usar un
tipo a medida, se considera aconsejable crear tipos a medida para clases (no
entidades) que ocurran frecuentemente en su aplicación. Por ejemplo, una
clase MonetaryAmount (suma de dinero) sería una
buena candidata para un CompositeUserType,
incluso si pudiere ser fácilmente mapeada como componente. Uno de los
motivos para esto es abstracción. Con un tipo a medida como éste, sus
documentos de mapeo serán a prueba de posibles cambios en la forma en que
los valores monetarios se representaren en el futuro.
Es posible proveer más de un mapeo para una clase persistente en particular. En tal caso, se debe proveer un nombre de entidad para desambigüar entre instancias de las dos entidades mapeadas. (Por defecto, el nombre de la entidad es igual al nombre de la clase). Hibernate permite especificar el nombre de entidad al trabajar con objetos persistentes, al escribir consultas, o al mapear asociaciones a dicha entidad.
<class name="Contract" table="Contracts" entity-name="CurrentContract">
...
<set name="history" inverse="true" order-by="effectiveEndDate desc">
<key column="currentContractId"/>
<one-to-many entity-name="HistoricalContract"/>
</set>
</class>
<class name="Contract" table="ContractHistory" entity-name="HistoricalContract">
...
<many-to-one name="currentContract" column="currentContractId" entity-name="CurrentContract"/>
</class> Note cómo ahora las asociaciones son especificadas
usando entity-name en lugar de class.
Se puede forzar a Hibernate a encerrar el identificador entre comillas, en el SQL generado, encerrando el nombre de tabla o de columna en signos de acento grave (`), en inglés, "backticks". Hibernate usará el entrecomillado adecuado para el dialecto SQL correspondiente (lo cual es usualmente comillas, pero para SQL Server son corchetes, y para MySQL, estos "backticks").
<class name="LineItem" table="`Line Item`">
<id name="id" column="`Item Id`"/><generator class="assigned"/></id>
<property name="itemNumber" column="`Item #`"/>
...
</class>XML no es para ualquiera, así que hay algunas otras alternativas para definir los metadatos del mapeo O/R en Hibernate.
Muchos usuarios e Hibernate prefieren
incrustar la información de mapeo directamente en su código fuente, usando
las tags @hibernate.tags de XDoclet. No
cubriremos esta estrategia aquí, dado que es estrictamente parte de XDoclet.
De todos modos, incluimos el siguiente ejemplo de la clase Cat con mapeo XDoclet.
package eg;
import java.util.Set;
import java.util.Date;
/**
* @hibernate.class
* table="CATS"
*/
public class Cat {
private Long id; // identifier
private Date birthdate;
private Cat mother;
private Set kittens
private Color color;
private char sex;
private float weight;
/*
* @hibernate.id
* generator-class="native"
* column="CAT_ID"
*/
public Long getId() {
return id;
}
private void setId(Long id) {
this.id=id;
}
/**
* @hibernate.many-to-one
* column="PARENT_ID"
*/
public Cat getMother() {
return mother;
}
void setMother(Cat mother) {
this.mother = mother;
}
/**
* @hibernate.property
* column="BIRTH_DATE"
*/
public Date getBirthdate() {
return birthdate;
}
void setBirthdate(Date date) {
birthdate = date;
}
/**
* @hibernate.property
* column="WEIGHT"
*/
public float getWeight() {
return weight;
}
void setWeight(float weight) {
this.weight = weight;
}
/**
* @hibernate.property
* column="COLOR"
* not-null="true"
*/
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
/**
* @hibernate.set
* inverse="true"
* order-by="BIRTH_DATE"
* @hibernate.collection-key
* column="PARENT_ID"
* @hibernate.collection-one-to-many
*/
public Set getKittens() {
return kittens;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kittens.add(kitten);
}
/**
* @hibernate.property
* column="SEX"
* not-null="true"
* update="false"
*/
public char getSex() {
return sex;
}
void setSex(char sex) {
this.sex=sex;
}
}Vea el sitio de web de Hibernate para más ejemplos de XDoclet con Hibernate.
La JDK 5.0 introdujo anotaciones al estilo XDoclet
a nivel del lenguaje. Son de tipo comprobado (type-safe) y se verifican en
tiempo de compilación. Este mecanismo es más poderoso que las anotaciones
XDoclet, y mejor soportado por las herramientas gráficas (IDE) como IntelluJ
IDEA, muchas de las cuales proveen autocompleción y resaltado de sintaxis
para anotaciones de JDK 5.0. La nueva revisión de los EJB, (JSR-220), usa
anotaciones como su principal mecanismo de metadatos para los Entity Beans.
Hibernate3 implementa el EntityManager JSR-220
(la API de persistencia), hay soporte disponible para el mapeo de metadatos
en el paquete de Annotataciones de
Hibernate (Hibernate Annotations), como una descarga separada.
Los metadatos que se soportan son tanto Hibernate3 como EJB3 (JSR-220).
El siguiente es un ejemplo de un POJO anotado como Entity Bean EJB.
@Entity(access = AccessType.FIELD)
public class Customer implements Serializable {
@Id;
Long id;
String firstName;
String lastName;
Date birthday;
@Transient
Integer age;
@Embedded
private Address homeAddress;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="CUSTOMER_ID")
Set<Order> orders;
// métodos Getter/setter, y de negocio ...
}Note que el soporte para anotaciones JDK 5.0 (y JSR-220) todavía es un trabajo en curso, incompleto. Por favor remítase al módulo de Anotaciones de Hobernate para más detales.
Las propiedades generadas son propiedades cuyos valores son generados por la base de datos. En general, las aplicaciones Hibernate tienen que refrescar los objetos que contengan cualquier propiedad para la cual la base de datos esté generando valores. Sin embargo, al marcar propiedades como "generadas" se deja que la aplicación delegue esta responsabilidad en Hibernate. Esencialmente, cada vez que Hibernate emita un INSERT o UPDATE para una entidad que tenga propiedades generadas definidas, inmediatamente generará un SELECT para capturar los valores generados.
Por añadidura, las propiedades marcadas como generadas debe ser no insertables y e inmodifocables. Sólo las versiones, las timestamps, y las propiedades simples pueden ser marcadas como generadas.
never (el valor por defecto) - significa que el valor
de la propiedad dada nunca es generada por la base de datos.
insert - declara que el valor de la propiedad dada es
generado al ocurrir un INSERT, pero no es regenerado en los UPDATEs
subsiguientes. Cosas como "fecha de creación" entrarían en esta categoría.
Note que, aunque las propiedades version y timestamp pueden ser marcadas como
generadas, esta opción no está disponible para ellas.
always - declara que el valor de esta propiedad es
generado tanto al insertar como al modificar.
Permite la
ejecución de comandos CREATE y DROP arbitrarios para objetos de base de
datos, en conjunción con las herramientas de evolución de esquema de base de
datos con las que cuenta Hibernate, para poveer la capacidad de definir un
esquema totalmente dentro de los archivos de mapeo de Hibernate. Aunque fue
designada específicamente para eliminar (DROP) cosas como triggers o
procedimientos almacenados, en realidad cualquier comando SQL que pueda ser
ejecutado mediante un java.sql.Statement.execute() es válido aquí: ALTERs,
INSERTs, etc). Esencialmente, hay dos maneras de definir objetos de base de
datos auxiliares:
La primera es listar explícitamente los comandos CREATE y DROP en el archivo de mapeo:
<hibernate-mapping>
...
<database-object>
<create>CREATE TRIGGER my_trigger ...</create>
<drop>DROP TRIGGER my_trigger</drop>
</database-object>
</hibernate-mapping> La segunda es proveer una clase a medida,
la cual sepa cómo construir los comandos CREATE y DROP. Esta clase a medida
debe implementar la interfaz org.hibernate.mapping.AuxiliaryDatabaseObject.
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
</database-object>
</hibernate-mapping>Adicionalmente, a estos objetos de base de datos se les puede definir un alcance, de manera que se apliquen sólo cuando se usen ciertos dialectos determninados.
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
<dialect-scope name="org.hibernate.dialect.Oracle9Dialect"/>
<dialect-scope name="org.hibernate.dialect.OracleDialect"/>
</database-object>
</hibernate-mapping>Hibernate requiere que los campos con valor de colección sean declarados como el tipo de una interfaz, por ejemplo:
public class Product {
private String serialNumber;
private Set parts = new HashSet();
public Set getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
public String getSerialNumber() { return serialNumber; }
void setSerialNumber(String sn) { serialNumber = sn; }
} La interfaz puede ser, efectivamente, un java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap o cualquier otra cosa que
implemente la interfaz org.hibernate.usertype.UserCollectionType.)
Dese cuenta de cómo inicializamos la variable de instancia con una instancia
de HashSet. Ésta es la mejor manera de
inicializar propiedades con valor de colección, o instancias recientemente
inicializadas (no persistentes). Cuando una clase se vuelve persistente (al
llamar persist(), por ejemplo) Hibernate en
realidad reemplazará el HashSet con una
implementación de Set propia de Hibernate
mismo. Esté atento a errores como éstos:
Cat cat = new DomesticCat(); Cat kitten = new DomesticCat(); .... Set kittens = new HashSet(); //kittens=gatitos kittens.add(kitten); cat.setKittens(kittens); session.persist(cat); kittens = cat.getKittens(); // Correcto, porque la colección kittens es un Set (HashSet) cat.getKittens(); // ¡Error!
Las colecciones persistentes
que son inyectadas por Hibernate se comportan como: HashMap, HashSet, TreeMap, TreeSet or
ArrayList, dependiendo del tipo de interfaz.
Las instancias de coleccioones tienen el comportamiento usual de los "value tupes". Son automáticamente persistidas cuando son referidas por un objeto persistente, y aumáticamente borradas cuando son "des-referidas". Si una colección se pasa de un objeto persistente a otro, sus elementos pueden ser movidos de una tabla a otra. Dos entidades no pueden compartir una referencia a la misma instancia de una colección. Dado el modelo relacional subyacente, las propiedades con valor de colección no soportan la semántica de nulo. Hibernate no distingue entre una colección nula y una colección vacía.
No vale la pena preocuparse mucho por nada de esto. Use las colecciones persistentes del mismo modo en que usaría las colecciones comunes de Java. Sólo asegúrese de comprender la semántica de las asociaciones bidireccionales (la cual se discute más adelante).
Hay una gran variedad de mapeos que puede ser generada para colecciones, abarcando muchos modelos relacionales de uso común. Le sugerimos que experimente con la herramienta de generación de esquemas de DB para tener una idea de cómo las declaraciones de mapeo se traducen en tablas de una base de datos.
El elemento de
mapeo de Hibernate que se use para mapear una colección depende del tipo de
interfaz. Por ejemplo, un elemento <set>
se usa para mapear propiedades del tipo Set.
<class name="Product">
<id name="serialNumber" column="productSerialNumber"/>
<set name="parts">
<key column="productSerialNumber" not-null="true"/>
<one-to-many class="Part"/>
</set>
</class> Además de <set>,
también están los elementos de mapeo <list>, <map>, <bag>, <array> y <primitive-array> . El elemento <map> es representativo:
<map
name="propertyName" (1)
table="table_name" (2)
schema="schema_name" (3)
lazy="true|extra|false" (4)
inverse="true|false" (5)
cascade="all|none|save-update|delete|all-delete-orphan|delet(6)e-orphan"
sort="unsorted|natural|comparatorClass" (7)
order-by="column_name asc|desc" (8)
where="arbitrary sql where condition" (9)
fetch="join|select|subselect" (10)
batch-size="N" (11)
access="field|property|ClassName" (12)
optimistic-lock="true|false" (13)
mutable="true|false" (14)
node="element-name|."
embed-xml="true|false"
>
<key .... />
<map-key .... />
<element .... />
</map> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Las instancias de colecciones se distinguen
en la BD por la clave foránea que posee a la colección. Esta clave foránea
se denomina columna (o columnas) clave de la
colección en la tabla de la colección. La columna clave de la
colección es mapeada por el elemento <key>.
Puede haber constraints de
nulabilidad en la columna de la clave foránea. Para la mayoría de las
colecciones, esto está implícito. Para las asociaciones de-uno-a-muchos
unidireccionales, la columna de la clave primaria es anulable por defecto,
así que a veces es necesario especificar not-null="true".
<key column="productSerialNumber" not-null="true"/>
la constraint de clave foránea puede usar ON DELETE
CASCADE.
<key column="productSerialNumber" on-delete="cascade"/>
Vea el capítulo correspondiente para una definición completa del elemento
<key>.
Las colecciones pueden contener casi cualquier otro tipo de Hibernate, incluyendo todos los tipos básicos, los tipos a medida, componentes, y, por supuesto, referencias a otras entidades. Esta es una distinción importante: un objeto en una colección puede ser manipulado con la semántica de "valor" (que todo su ciclo de vida dependa del dueño de la colección), o puede ser una referencia a otra entidad, con su propio ciclo de vida. En este último caso, sólo el "vínculo" entre ambos objetos es lo que se considera como estado almacenado por la colección.
Al tipo
contenido se lo refiere como al tipo del elemento
de la colección. Los elementos de la colección son mapeados por
<element> o <composite-element>, o, en el caso de
referencias a entidades, con <one-to-many> o <many-to-many>. Los primeros dos, mapean
elementos con la semántica de valor. Los otros dos, se usan para mapear
asociaciones de entidades.
Todos los mapeos de colección, excepto los los que tengan
semántica de set o de bag, necesitan una columna
índice en la tabla de la colección (una columna que mapea a un
índice de una array), o un índice de List, o
una clave de un Map. El índice de un Map puede ser de cualqueira de los tipos básico,
mapeado con <map-key>, puede ser una
entidad mapeada con <map-key-many-to-many>, o puede ser un tipo
compuesto, mapeado con <composite-map-key>. El índice de un array o
lista es siempre del tipo integer y se mapea
usando el elemento <list-index> element.
La columna mapeada contiene enteros secuenciales (comenzando por cero, por
defecto).
<list-index
column="column_name" (1)
base="0|1|..."/> |
|
|
<map-key
column="column_name" (1)
formula="any SQL expression" (2)
type="type_name" (3)
node="@attribute-name"
length="N"/> |
|
|
|
|
<map-key-many-to-many
column="column_name" (1)
formula="any SQL expression" (2)(3)
class="ClassName"
/> |
|
|
|
|
Si su tabla no tiene una columna índice, pero
usted aún desea usar una List como el tipo de
propiedad, usted debería mapear la propiedad como una <bag> de Hibernate. Una bag (bolsa)
no retiene su orden cuando es obtenida de la base de datos, pero puede ser
opcionalmente ordenada.
Cualquier colección de valores o asociación de-muchos-a-muchos requiere una tabla de colección dedicada, con una columna o columnas de clave foránea, una columna o columnas para el elemento de la colección, y, posiblemente, una columna o columnas índice.
Para una colección de valores,
usamos la tag <element>.
<element
column="column_name" (1)
formula="any SQL expression" (2)
type="typename" (3)
length="L"
precision="P"
scale="S"
not-null="true|false"
unique="true|false"
node="element-name"
/> |
|
|
|
|
Una asociación de-muchos-a-muchos (many-to-many) se especifica usando el
elemento <many-to-many>.
<many-to-many
column="column_name" (1)
formula="any SQL expression" (2)
class="ClassName" (3)
fetch="select|join" (4)
unique="true|false" (5)
not-found="ignore|exception" (6)
entity-name="EntityName" (7)
property-ref="propertyNameFromAssociatedClass" (8)
node="element-name"
embed-xml="true|false"
/> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A continuación, algunos ejemplos. Primero, un conjunto de strings.
<set name="names" table="person_names">
<key column="person_id"/>
<element column="person_name" type="string"/>
</set> Una bag conteniendo enteros, con un orden de iteración
determinado por el atributo order-by:
<bag name="sizes" table="item_sizes" order-by="size asc">
<key column="item_id"/>
<element column="size" type="integer"/>
</bag>Un array de entidades (en este caso, una asociación de-uno-a-muchos):
<array name="addresses" table="PersonAddress" cascade="persist">
<key column="personId"/>
<list-index column="sortOrder"/>
<many-to-many column="addressId" class="Address"/>
</array>Un mapa de índices string a fechas:
<map name="holidays" table="holidays" schema="dbo" order-by="hol_name asc">
<key column="id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date" type="date"/>
</map>Una lista de componentes (se discute más adelante):
<list name="carComponents" table="CarComponents">
<key column="carId"/>
<list-index column="sortOrder"/>
<composite-element class="CarComponent">
<property name="price"/>
<property name="type"/>
<property name="serialNumber" column="serialNum"/>
</composite-element>
</list>Una asociación de-uno-a-muchos vincula las tablas de dos clases a través de una clave foránea, sin la intervención de una "tabla de colección". Este mapeo pierde algo de la semántica de las colecciones normales de Java, porque:
Una instancia de la clase-entidad contenida, no puede pertenecer a más de una instancia de la colección.
Una instancia de la clase-entidad contenida, no puede aparecer en más de un valor de índice de la colección.
Una asociación de Product a
Part (producto a parte) requiere la existencia
de una clave foránea, y posiblemente una columna índice en la tabla Part. Una tag <one-to-many> indica que ésta es una asociación
de-uno-a-muchos.
<one-to-many
class="ClassName" (1)
not-found="ignore|exception" (2)
entity-name="EntityName" (3)
node="element-name"
embed-xml="true|false"
/> |
|
|
|
|
Note que el elemento <one-to-many> no necesita declarar ninguna
columna. Tampoco es necesatio especificar un nombre de tabla en ningún lado.
Nota muy importante: Si la
columna de clave foránea de una asociación <one-to-many> se declara NOT NULL, se debe declarar el mapeo de <key> not-null="true" o usar una
asociación bidireccional con el mapeo de la colección marcado
como inverse="true". Vea la discusíón sobre
asociaciones bidireccionales más adelante en este capítulo.
Este
ejemplo muestra un mapa de entidades Part por
nombre (donde el nombre de la parte, partName,
es una propiedad persistente de Part). Note el
uso de un índice basado en una fórmula.
<map name="parts" cascade="all">
<key column="productId" not-null="true"/>
<map-key formula="partName"/>
<one-to-many class="Part"/>
</map> Hibernate soporta colecciones que implementen java.util.SortedMap y
java.util.SortedSet. Se debe especificar un comparador en el archivo
de mapeo.
<set name="aliases" table="person_aliases" sort="natural">
<key column="person"/>
<element column="name" type="string"/>
</set>
<map name="holidays" sort="my.custom.HolidayComparator">
<key column="year_id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date" type="date"/>
</map> Valores permitidos del atributo sort son unsorted, natural y el nombre de una clase que implemente java.util.Comparator.
Las colecciones
ordenadas en realidad se comportan como un java.util.TreeSet o java.util.TreeMap.
Si quiere que la base de
datos misma ordene los elementos de la colección, use el atributo order-by del mapeo de los mapeos de set, bag o map. Esta solución sólo se encuentra disponible bajo
la JDK 1.4 o superior (se implementa usando LinkedHashSet o LinkedHashMap). Esto efectúa el ordenamiento en la
consulta SQL, no en memoria.
<set name="aliases" table="person_aliases" order-by="lower(name) asc">
<key column="person"/>
<element column="name" type="string"/>
</set>
<map name="holidays" order-by="hol_date, hol_name">
<key column="year_id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date type="date"/>
</map> ¡Note que el valor del atributo order-by es un ordenamiento SQL, no HQL!
Las
asociaciones pueden incluso ser ordenadas por algún criterio arbitrario en
tiempo de ejecución, usandp un filtro (filter()) de colección.
sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();
Una asociación bidireccional (bidirectional association) permite navegar desde ambos "extremos" de la asociación. Se soportan dos tipos de asociación bidireccional:
(de-uno-a-muchos) un set o una bag en un extremo, un valor simple en en otro extremo.
(de-muchos-a-muchos) un set o una bag con valores en ambos extremos
Se puede especificar una asociación bidireccional de-muchos-a-muchos simplemente mapeando dos asociaciones a la misma tabla de la base de datos, y declarando uno de los extremos como inverse (cuál, es a elección, pero no puede ser una colección indexada).
He aquí un ejemplo de una asociación bidireccional de-muchos-a-muchos; cada categoría puede tener muchos items y cada ítem puede tener muchas categorías:
<class name="Category">
<id name="id" column="CATEGORY_ID"/>
...
<bag name="items" table="CATEGORY_ITEM">
<key column="CATEGORY_ID"/>
<many-to-many class="Item" column="ITEM_ID"/>
</bag>
</class>
<class name="Item">
<id name="id" column="ITEM_ID"/>
...
<!-- lado inverso -->
<bag name="categories" table="CATEGORY_ITEM" inverse="true">
<key column="ITEM_ID"/>
<many-to-many class="Category" column="CATEGORY_ID"/>
</bag>
</class>Los cambios que se le hacen sólo al extremo inverso de la asociación, no son persistidos. Esto significa que Hibernate tiene dos representaciones en memoria por cada asociación bidireccional.: un vínculo de A a B, y otro vínculo de B a A. Esto es más fácil de entender si se piensa en el modelo de objetos Java y cómo se crea una relación de-muchos-a-muchos en Java.
category.getItems().add(item); // La categoría ahora "sabe" acerca de la relación item.getCategories().add(category); // El ítem ahora "sabe" acerca de la relación session.persist(item); // ¡La relación no será grabada! session.persist(category); // La relación sí será grabada
El lado no-inverso se usa para grabar en la base de datos la representación en memoria.
Se puede definir una asociación bidireccional
de-muchos-a-muchos mapeando un a asocíación de-uno-a-muchos a la(s) misma(s)
columna(s) de la tabla que una asociación de-muchos-a-uno, y poniendo inverse="true" en el el extremo 'muchos'.
<class name="Parent">
<id name="id" column="parent_id"/>
....
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
</class> Mapear uno de los extremos de la asociación con inverse="true" no afecta las propagaciones en
cascada. ¡Son conceptos ortogonales!
Una asociación bidireccional en donde un extremo esté representado como una
<list> o un <map>necesita algunas consideraciones
especiales. Si existe una propiedad de la clase hija que mapee a la columna
de índice, no hay problema, podemos continuar usando inverse="true" en el mapeo de la colección.
<class name="Parent">
<id name="id" column="parent_id"/>
....
<map name="children" inverse="true">
<key column="parent_id"/>
<map-key column="name" type="string"/>
<one-to-many class="Child"/>
</map>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<property name="name" not-null="true"/>
<many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
</class> Pero si tal propiedad no existe en la clase hoja, no
podemos concebir la asociación como verdaderamete bidireccional (hay
información disponible en un extremo que no lo está en el otro extremo). En
este caso, no podemos mapear la colección con inverse="true". En lugar de eso, podemos usar el
siguiente mapeo.
<class name="Parent">
<id name="id" column="parent_id"/>
....
<map name="children">
<key column="parent_id" not-null="true"/>
<map-key column="name" type="string"/>
<one-to-many class="Child"/>
</map>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<many-to-one name="parent" class="Parent" column="parent_id" insert="false" update="false" not-null="true"/>
</class>Note que en este mapeo, el extremo con la colección es el responsable de las actualizaciones de la clave foránea. A HACER: ¿esto resulta realmente en la ejecución de UPDATES innecesarios?
Hay tres abordajes posibles para
mapear asociaciones ternarias. Uno es usar un Map con la asociación como índice:
<map name="contracts">
<key column="employer_id" not-null="true"/>
<map-key-many-to-many column="employee_id" class="Employee"/>
<one-to-many class="Contract"/>
</map><map name="connections">
<key column="incoming_node_id"/>
<map-key-many-to-many column="outgoing_node_id" class="Node"/>
<many-to-many column="connection_id" class="Connection"/>
</map>Un segundo abordaje es simplemente remodelar la asociación como una clase de entidad. Éste es el enfoque que se usa más comúnmente.
Una última alternativa, es usar elementos compuestos, lo cual se discute más adelante.
Si usted está totalmente convencido de que las claves compuestas son una cosa mala, y de que las entidades debe tener identidicadores "sintéticos" (claves sustitutas), entonces encontrará un tanto raro que las asociaciones de-muchos-a-muchos y las colecciones que le hemos mostrado hasta ahora ¡se mapean todas a tablas con claves compuestas! Este punto es debatible. Una tabla que sea puramente "de asociación" no se beneficia mucho con la existencia de una clave sustituta (aunque una colección de valores compuestos sí podría). De todos modos, Hibernate provee un mecanismo que permite mapear asociaciones de-muchos-a-muchos y colecciones de valores a una tabla con clave sustituta.
El elemento <idbag> le permite
mapear una List (o Collection) con semántica de bag.
<idbag name="lovers" table="LOVERS">
<collection-id column="ID" type="long">
<generator class="sequence"/>
</collection-id>
<key column="PERSON1"/>
<many-to-many column="PERSON2" class="Person" fetch="join"/>
</idbag> Como se puede ver, una <idbag> tiene un generador de id "sintético",
¡igual que una clase de entidad! A cada fila de la colección se le asigna
una clave sustituta diferente. Si embargo, Hibernate no provee ningún
mecanismo para descubrir el valor de la clave sustituta de una fila en
particular.
Note que la performance al actualizar una <idbag> ¡es mucho mejor que la de una <bag> común! Hibernate puede localizar filas
individuales eficientemente, y actualizarlas o borraras individualmente,
como si fuera una list, un map o un set.
En la implementación
actual, el identificador native como estrategia
de generación de identificadores no se soporta para las <idbag>s.
Las secciones precedentes son bastante confusas. Así que veamos un ejemplo. Esta clase:
package eg;
import java.util.Set;
public class Parent {
private long id;
private Set children;
public long getId() { return id; }
private void setId(long id) { this.id=id; }
private Set getChildren() { return children; }
private void setChildren(Set children) { this.children=children; }
....
....
} Tiene una colección de instancias Child. Si cada child (hijo) tiene como mucho un
padre, el mapeo más natural es una asociación de-uno-a-muchos:
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>Esto se mapea a las siguientes definiciones de tabla:
create table parent ( id bigint not null primary key ) create table child ( id bigint not null primary key, name varchar(255), parent_id bigint ) alter table child add constraint childfk0 (parent_id) references parent
If the parent is required, use a bidirectional one-to-many association:
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
<many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
</class>
</hibernate-mapping> Note la constraint NOT NULL:
create table parent ( id bigint not null primary key )
create table child ( id bigint not null
primary key,
name varchar(255),
parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent
Alternativamente, si usted insiste absolutamente en que la asociación debe
ser unidireccional, debe declarar la constraint NOT
NULL en el mapeo de la <key>.
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>Por otra parte, si un hijo tiene múltiles padres, lo apropiado es una relación de-muchos-a-muchos.
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" table="childset">
<key column="parent_id"/>
<many-to-many class="Child" column="child_id"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>Definiciones de tablas:
create table parent ( id bigint not null primary key ) create table child ( id bigint not null primary key, name varchar(255) ) create table childset ( parent_id bigint not null, child_id bigint not null, primary key ( parent_id, child_id ) ) alter table childset add constraint childsetfk0 (parent_id) references parent alter table childset add constraint childsetfk1 (child_id) references child
Para más ejemplos, y una explicación paso a paso del mapeo de una relación padre-hijo, vea Capítulo 21, Ejemplo: Padre/Hijo.
Son posibles mapeos de asociaciones aún más exóticas. Catalogaremos todas las posibilidades en el capítulo siguiente.
Los
mapeos de asociaciones son lo más difícil de comprender correctamente. En
esta sección revisaremos los casos canónicos uno por uno, empezando con los
mapeos unidireccionales, y siguiendo con los bidireccionales. Usaremos Person y Address
("persona" y "dirección") en todos los ejemplos.
Clasificaremos las asociaciones según usen o no una tabla de asociación, y según su multiplicidad.
Las claves foráneas anulables no son consideradas una práctica recomendable, en la modelización de datos tradicional, así que todos nuestros ejemplos usarán claves foráneas no nulas. Esto no es obligatorio, los mapeos aún funcionarían si se eliminara la constraint de nulabilidad.
Una asociación unidireccional de-muchos-a-uno es el tipo más común de asociación unidireccional.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address" column="addressId" not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
Una asociación unidireccional de-uno-a-uno por clave foránea es prácitcamente idéntica. La única diferencia es la constraint de unicidad.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
Una asociación unidirectional de-uno-a-uno por clave primaria normalmente usa un generador de id especial (note que hemos revertido la dirección de la asociación en este ejemplo).
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
</class>
<class name="Address">
<id name="id" column="personId">
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<one-to-one name="person" constrained="true"/>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
Una asociación unidireccional de-uno-a-muchos por clave foránea es un caso muy inusual, que no recomendamos usar.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses">
<key column="personId" not-null="true"/>
<one-to-many class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( addressId bigint not null primary key, personId bigint not null )
Pensamos que para este caso es mejor usar una tabla de asociación.
Una asociación unidireccional
de-uno-a-muchos por tabla de asociación es mucho más
recomendable. Note que al especificar unique="true", hemos cambiado la multiplicidad de
"de-muchos-a-muchos" a "de-uno-a-muchos".
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId" unique="true" class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
Una asociación A unidireccional de-muchos-a-uno por tabla de asociación es muy común, cuando la asociación es optativa
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress" optional="true">
<key column="personId" unique="true"/>
<many-to-one name="address" column="addressId" not-null="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
Una asociación unidireccional de-uno-a-uno por tabla de asociación es muy inusual, pero posible.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress" optional="true">
<key column="personId" unique="true"/>
<many-to-one name="address" column="addressId" not-null="true" unique="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
Finalmente, tenemos una asociación unidireccional de-muchos-a-muchos.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId" class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
Una asociación bidireccional de-muchos-a-uno es el tipo más común de asociación (la relación padre/hijo estándar).
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address" column="addressId" not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true">
<key column="addressId"/>
<one-to-many class="Person"/>
</set>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
Si se usa una List (u otra
colección idexada) se necesitará asignarle not
null a la columna "key" de la clave
foránea, y dejar que Hibernate maneje la asociación desde el extremo
"muchos" para mantener el índice de cada elemento (convirtiendo el otro lado
virtualmente en inverso, al especificar update="false" y insert="false"):
<class name="Person">
<id name="id"/>
...
<many-to-one name="address" column="addressId" not-null="true" insert="false" update="false"/>
</class>
<class name="Address">
<id name="id"/>
...
<list name="people">
<key column="addressId" not-null="true"/>
<list-index column="peopleIdx"/>
<one-to-many class="Person"/>
</list>
</class> Es importante que se defina not-null="true" en el elemento <key> del mapeo de la colección, si la columna
de clave foránea sunyacente es NOT NULL. No
debe declararse solamente not-null="true" en un
posible elemento <column> anidado, sino
en el elemento <key>.
Un asociación bidireccional de-uno-a-uno por clave foránea es bastante común
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address" column="addressId" unique="true" not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<one-to-one name="person" property-ref="address"/>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
Un asociación bidireccional de-uno-a-uno por clave primaria usa el generador de id especial.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<one-to-one name="address"/>
</class>
<class name="Address">
<id name="id" column="personId">
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<one-to-one name="person" constrained="true"/>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
Un asociación bidireccional de-uno-a-muchos por tabla de
asociación. Note que el inverse="true" puede ir an cualquier extremo de la
asociación, en la colección, o en el join.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId" unique="true" class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress" inverse="true" optional="true">
<key column="addressId"/>
<many-to-one name="person" column="personId" not-null="true"/>
</join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
Un asociación bidireccional de-uno-a-uno por tabla de asociación es extremadamente inusual, pero posible.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress" optional="true">
<key column="personId" unique="true"/>
<many-to-one name="address" column="addressId" not-null="true" unique="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress" optional="true" inverse="true">
<key column="addressId" unique="true"/>
<many-to-one name="person" column="personId" not-null="true" unique="true"/>
</join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
Finalmente, tenemos una asociación bidireccional de-muchos-a-muchos.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId" class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true" table="PersonAddress">
<key column="addressId"/>
<many-to-many column="personId" class="Person"/>
</set>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
Asociaciones más complejas son extremadamente raras. Hibernate posibilita
manejar asociaciones más complejas usando fragmentos SQL incrustados en el
documento de mapeo. Por ejemplo, si una tabla con información contable
histórica definiere las columnas accountNumber,
effectiveEndDate and effectiveStartDate, mapeadas de esta manera:
<properties name="currentAccountKey">
<property name="accountNumber" type="string" not-null="true"/>
<property name="currentAccount" type="boolean">
<formula>case when effectiveEndDate is null then 1 else 0 end</formula>
</property>
</properties>
<property name="effectiveEndDate" type="date"/>
<property name="effectiveStateDate" type="date" not-null="true"/>
entonces podemos mapear una asociación a la instancia actual (la que tiene effectiveEndDate nula) usando:
<many-to-one name="currentAccountInfo" property-ref="currentAccountKey" class="AccountInfo">
<column name="accountNumber"/>
<formula>'1'</formula>
</many-to-one> En un ejemplo más complejo, imagine que la
asociación entre Employee y Organization estuviere mantenida por una tabla Employment, llena de datos históricos de empleo.
Entonces, una asociación al empleador más
reciente de un empleado (el que tuviere la startDate más reciente) podría ser mapeada de esta
manera:
<join>
<key column="employeeId"/>
<subselect>
select employeeId, orgId
from Employments
group by orgId
having startDate = max(startDate)
</subselect>
<many-to-one name="mostRecentEmployer" class="Organization" column="orgId"/>
</join>Uno se puede poner bastante creativo con esta funcionalidad, pero normalmente es más práctico manejar estos casos unando HQL o consultas Criteria.
La noción de componente (en inglés, component) se reusa a lo largo y a lo ancho de Hibernate en distintos contextos, para distintos propósitos.
Un componente es un objeto contenido, que es persistido como "value type", no como referencia a una entidad. El término "componente" se refiere a la noción orientada a objetos de "composición" (no a "componentes" a nivel de arquitectura). Por ejemplo, se puede modelar a una persona así:
public class Person {
private java.util.Date birthday;
private Name name;
private String key;
public String getKey() {
return key;
}
private void setKey(String key) {
this.key=key;
}
public java.util.Date getBirthday() {
return birthday;
}
public void setBirthday(java.util.Date birthday) {
this.birthday = birthday;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
......
......
}public class Name {
char initial;
String first;
String last;
public String getFirst() {
return first;
}
void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
void setLast(String last) {
this.last = last;
}
public char getInitial() {
return initial;
}
void setInitial(char initial) {
this.initial = initial;
}
} Ahora, Name puede ser persistida como
un componente de Person (en inglés, "nombre" y
"persona", respectivamente). Note que Name
define métodos getter y setter para sus propiedades persistentes, pero no
necesita declarar ninguna interfaz ni propiedades identiicadoras.
Nuestro mapeo Hibernate se vería así:
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid"/>
</id>
<property name="birthday" type="date"/>
<component name="Name" class="eg.Name"> <!-- el atributo 'class" es optativo -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</component>
</class> La tabla PERSON tendría las columnas pid, birthday, initial, first y last.
Como todos los "value types", los componentes no soportan referencias compartidas. Dos personas pueden tener el mismo nombre, pero los dos objetos Person correspondientes contendrán dos objetos nombre independientes, sólo que con "el mismo" valor. La semántica de nulo de un componente es ad hoc. Cuando se recargue el objeto contenedor, Hibernate assumirá que el componente entero es nulo. Esto debería alcanzar para la mayoría de los casos.
Las propiedades de un componente pueden ser de cualquier tipo Hibernate (colecciones, asociaciones de-muchos-a-uno, otros componentes, etc). Los componentes anidados no deben ser considerados una rareza. Se espera que Hibernate soporte un modelo de objetos muy jerárquico y detallado en este sentido.
El elemento <component> acepta
un subelemento <parent> que se mapee a
una propiedad desde la clase del componente como referencia de vuelta a la
entidad contenedora.
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid"/>
</id>
<property name="birthday" type="date"/>
<component name="Name" class="eg.Name" unique="true">
<parent name="namedPerson"/> <!-- referencia de vuelta a Person -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</component>
</class> Hay soporte para colecciones de componentes
(por ejemplo, un array de tipos Name). Declare
su colección de componentes reemplazando la tag del elemento <element> con una tag <composite-element>.
<set name="someNames" table="some_names" lazy="true">
<key column="id"/>
<composite-element class="eg.Name"> <!-- el atributo class es obligatorio -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</composite-element>
</set> Nota: si se define un Set de elementos compuestos, es
importante implementar equals() y hashCode() correctamente.
Los elementos
compuestos pueden contener otros componentes, pero no colecciones. Si su
componente compuesto a su vez contiene componentes, use la tag <nested-composite-element>. Éste es un caso
batante exótico: una colección de componentes que a su vez tengan
componentes. A este punto usted debería preguntarse si no sería más
apropiada una asociación de-uno-a-muchos. Trate de remodelar el elemento
compuesto como una entidad, pero dése cuenta de que, aunque el modelo de
Java es el mismo, le modelo relacional y la semántica de persistencia son
aún ligeramente diferentes.
Por favor, note que un mapeo de
elemento compuesto no soporta propiedades anulables si se está usando un
<set>. Hibernate tiene que usar el valor
de cada columna para identificar un registro cuando se borra objetos (no hay
una clave primaria separada en la tabla de elementos compuestos), lo cual no
es posible con valores nulos. Se deberá o bien usar sólo valores no nulos en
un <composite-element>, o bien elegir una
<list>, <map>, <bag>
o <idbag>.
Un caso especial de
elemento compuesto ese el elemento compuesto que tiene un elemento <many-to-one> anidado. Un mapeo como éste
permite mapear columnas extra de una tabla de asociación de-muchos-a-muchos
a la clase del elemento compuesto. La siguiente es una asociación
de-muchos-a-muchos de from Order a Item (de orden a ítem) en donde la fecha de compra (
purchaseDate), el precio (price) y la cantidad (quantity) son propiedades de la asociación.
<class name="eg.Order" .... >
....
<set name="purchasedItems" table="purchase_items" lazy="true">
<key column="order_id">
<composite-element class="eg.Purchase">
<property name="purchaseDate"/>
<property name="price"/>
<property name="quantity"/>
<many-to-one name="item" class="eg.Item"/> <!-- el atributo "clase" es optativo -->
</composite-element>
</set>
</class> Por supuesto, no puede haber una referencia a la
compra del otro lado, para proveer navegación bidireccional de la
asociación. Recuerde que los componentes son "value types", y no aceptan
referencias compartidas. Una simple compra (Purchase) puede estar en el set de una Order, pero no puede ser referida por el Item al mismo tiempo.
Incluso son posibles asociaciones ternarias o cuaternarias:
<class name="eg.Order" .... >
....
<set name="purchasedItems" table="purchase_items" lazy="true">
<key column="order_id">
<composite-element class="eg.OrderLine">
<many-to-one name="purchaseDetails class="eg.Purchase"/>
<many-to-one name="item" class="eg.Item"/>
</composite-element>
</set>
</class>Los elementos compuestos pueden aparecer en consultas, usando la misma sintaxis que las entidades.
El elemento <composite-map-key> (clave compuesta de mapa)
permite mapear una clase componente como la clave de un Map. Asegúrese de reemplazar correctamente los
métodos hashCode() y equals() en la clase componente.
Se puede usar un componente como identificador de una clase entidad. Dicho componente debe satisfacer ciertos requisitos:
Debe implementar java.io.Serializable.
Debe reimplementar equals() y hashCode(), de una manera consistente con la
noción de igualdad para la clave compuesta en la DB.
Nota: en Hibernate3, el segundo requisito