Documentos de Académico
Documentos de Profesional
Documentos de Cultura
html
Prefacio
1. Introducción a Hibernate
1.1. Prefacio
1.2. Parte 1 - La Primera Aplicación Hibernate
1.2.1. La primera clase
1.2.2. El archivo de mapeo
1.2.3. Configuración de Hibernate
1.2.4. Construyendo con Ant
1.2.5. Comienzo y ayudantes
1.2.6. Cargar y almacenar objetos
1.3. Parte 2 - Mapear asociaciones
1.3.1. Mapear la clase Person
1.3.2. Una asociación unidireccional basada en un Set
1.3.3. Trabajar con la asociación
1.3.4. Colección de valores
1.3.5. Asociaciones bidireccionales
1.3.6. Trabajar con vínculos bidireccionales
1.4. Parte 3 - La aplicación de web "Event Manager"
1.4.1. Escribir el servlet básico
1.4.2. Procesamiento y presentación
1.4.3. Despliegue (deploy) y test
1.5. Sumario
2. Arquitectura
2.1. Generalidades
2.2. Estados de una instancia
2.3. Integración con JMX
2.4. Soporte de JCA
2.5. Sesiones contextuales
3. Configuración
3.1. Configuración programática
3.2. Obtener una SessionFactory
3.3. Conexiones JDBC
3.4. Propiedades optativas de configuración
3.4.1. Dialectos de SQL
3.4.2. Captura (fetching) por Outer Join
3.4.3. Streams Binarios
3.4.4. Caché de 2do nivel y caché de consultas
3.4.5. Sustituciones en el lenguaje de consultas.
3.4.6. Estadísticas de Hibernate
3.5. Logueo (logging, bitácora)
3.6. Implementando una NamingStrategy
3.7. Archivo de configuración XML
3.8. Integración con los Servidores de Aplicación J2EE
3.8.1. Configuración de una estrategia transaccional
3.8.2. SessionFactory ligada a JNDI
3.8.3. Manejo del contexto actual de la sesión con JTA
3.8.4. Despliegue de JMX
4. Clases Persistentes
4.1. Un simple ejemplo de POJO
Prefacio
Trabajar 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.
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).
1.1. Prefacio
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.
Importante
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.
Nota
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.
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.
...
<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 se
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
</dependencies>
</project>
Nota
Básicamente, aquí estamos describiendo el archivo /tutorials/web/pom.xml. Vea el sitio de Maven para
más información.
Consejo
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.
package org.hibernate.tutorial.domain;
import java.util.Date;
public Event() {}
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.java
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.
<?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>
</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>
</hibernate-mapping>
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>
</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.xml
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
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.
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:hsqldb:hsql://localhost</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<property name="connection.pool_size">1</property>
<!-- habilita el manejo automátio de contexto de sesión por parte de Hibernate -->
<property name="current_session_context_class">thread</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.
<path id="libraries">
<fileset dir="${librarydir}">
<include name="*.jar"/>
</fileset>
</path>
<target name="clean">
<delete dir="${targetdir}"/>
<mkdir dir="${targetdir}"/>
</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.*;
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);
}
}
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,.
.
+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;
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
HibernateUtil.getSessionFactory().close();
}
session.beginTransaction();
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>
El valor del argumento "action" se asigna en la línea de comandos cuando esta target se invoca.
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());
}
}
session.beginTransaction();
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.
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.
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.
clase. Primero, agregaremos algunas personas a nuestra aplicación, y almacenaremos una lista de eventos en los cuales
participan.
package events;
public Person() {}
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>
</hibernate-mapping>
<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.
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.
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
</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.
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | |
|_____________| |__________________| | PERSON |
| | | | |_____________|
| *EVENT_ID | <--> | *EVENT_ID | | |
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE |
|_____________| | FIRSTNAME |
| LASTNAME |
|_____________|
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.
session.getTransaction().commit();
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.
<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.
_____________ __________________
| | | | _____________
| 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:
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.
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:
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.
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.
package events;
// Imports
// Servlet code
}
Este servlet maneja requests HTTP del tipo GET solamente, así que el método que implementamos es doGet():
try {
// comienzo nuestra unidad de trabajo
HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();
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.
if ( "store".equals(request.getParameter("action")) ) {
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);
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:
El método listEvents() usa la sesión de Hibernate ligada al Thread actual para ejecutar una consulta:
Finalmente, la acción store es despachada al método createAndStoreEvent(), el cual usa la sesión del Thread actual.
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:.
<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.
<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.
1.5. Sumario
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”).
Capítulo 2. Arquitectura
2.1. Generalidades
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.
SessionFactory (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.
Session (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.
Transacción (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!
ConnectionProvider (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.
TransactionFactory (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.
Interfaces de extensión
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.
transitorio (transient)
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.
persistente (persistent)
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).
desprendida (detached)
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.
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
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”
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.
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.
Capítulo 3. Configuración
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 alternativa (a veces preferible), es especificar la clase mapeada, y dejar que Hibernate encuentre el documento de
mapeo él solo:
Ésta no es la única manera de pasarle propiedades de configuración a Hibernate. Entre las muchas otras opciones, están
éstas:
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.
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.
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:
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.
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:
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.JBossTransactionManagerLooku
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.
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.
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.
El nombre de clase de un
org.hibernate.connection.ConnectionProvider hecho a medida que le
hibernate.connection.provider_class provea conexiones JDBC a Hibernate.
por ejemplo 1, 2, 4, 8
manualmente.
PostgreSQL org.hibernate.dialect.PostgreSQLDialect
MySQL org.hibernate.dialect.MySQLDialect
Sybase org.hibernate.dialect.SybaseDialect
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
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".
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:
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
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.
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:
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.
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 espera que el archivo de configuración XML esté por defecto en la raíz de su CLASSPATH. He aquí un ejemplo:
<hibernate-configuration>
<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>
</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.
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).
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.JDBCTransactionFactory
delega a las transacciones JDBC de la base de datos (es el valor por defecto)
org.hibernate.transaction.JTATransactionFactory
delega 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.CMTTransactionFactory
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.
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.
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),
<?xml version="1.0"?>
<server>
<mbean code="org.hibernate.jmx.HibernateService"
name="jboss.jca:service=HibernateFactory,name=HibernateFactory">
<attribute name="JndiName">java:/hibernate/SessionFactory</attribute>
<attribute name="FlushBeforeCompletionEnabled">true</attribute>
<attribute name="AutoCloseSessionEnabled">true</attribute>
<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.
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.
package eg;
import java.util.Set;
import java.util.Date;
this.color = color;
}
//kittens=gatitos
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}
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".
4.1.4. Declare métodos de acceso y "mutadores" (accessors, mutators) para los campos persistentes.
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.
package eg;
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
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 boolean equals(Object other) {
if (this == other) return true;
if ( !(other instanceof Cat) ) return false;
return true;
}
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".
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">
</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");
// 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:
// 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.
4.5. T-uplizadores
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
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
-->
...
</class>
</hibernate-mapping>
// suplanta al método buildInstantiator() para "enchufar" nuestro mapeo hecho a medida ...
protected final Instantiator buildInstantiator(org.hibernate.mapping.PersistentClass mappingInf
return new CustomMapInstantiator( mappingInfo );
}
4.6. Extensiones
A HACER: documentar el framework de extensiones de usuario y paquetes proxy.
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.
<?xml version="1.0"?>
<hibernate-mapping package="eg">
<id name="id">
<generator class="native"/>
</id>
<property name="weight"/>
</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.
5.1.1.1. EntityResolver
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.
<?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.
5.1.2. hibernate-mapping
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)
/>
(1)
schema (optativo): El nombre de un esquema de BD
(2)
catalog (optativo): El nombre de un catálogo de BD
(3)
default-cascade (optativo - por defecto, none): El estilo por defecto de propagación en cascada
(4)
default-access (optativo - por defecto, property): La estrategia que Hibernate debería usar para acceder a
todas las propiedades. Puede ser una implementacíón personalizada de PropertyAccessor.
(5)
default-lazy (optativo - por defecto, true): El valor por defecto para los atributos lazy no especificados de
clases y colecciones.
(6)
auto-import (optativo - por defecto true): Especifica si se puede usar nombres de clase no calificados (de clases
en el mapeo) el en lenguaje de consultas.
(7)
package (optativo): Especifica un prefijo de paquete a ser asumido, para las clases no calificadas en el documento
de mapeo.
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.
5.1.3. 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)
(1)
name (optativo): El nombre (totalmente calificado) de la clase (o interfaz) persistente de Java. Si este atributo está
ausente, se asume que no se trata de una entidad POJO.
(2)
table (optativo - por defecto el nombre no calificado de la clase): El nombre de su tabla en la base de datos
(3)
discriminator-value (optativo - por defecto, el nombre de la clase): Un valor que distingue entre subclases
individuales, usado para comportamiento polimórfico. null y not null también son valores aceptables.
(4)
mutable (optativo, por defecto, true): Especifica si las instancias de esta clase son o no mutables.
(5)
schema (optativo): Suplanta al nombre de esquema especificado por el elemento <hibernate-mapping> raíz.
(6)
catalog (optativo): Suplanta al nombre de catálogo especificado por el elemento <hibernate-mapping> raíz.
(7)
proxy (optativo): Especifica una interfaz a utilizar para los proxies de inicialización perezosa. Se puede especificar
el nombre de la clase misma.
(8)
dynamic-update (optativo, por defecto false): Especifica que los comandos SQL UPDATE deberían ser generados
en tiempo de ejecución, y contener sólo aquéllas comlumnas cuyos valores hayan cambiado.
(9)
dynamic-insert (optativo, por defecto, false): Especifica que se deberían generar comandos SQL INSERT en
tiempo de ejecución, conteniendo sólo las columnas cuyos valores son no nulos.
(10)
select-before-update (optativo, por defecto false): Especifica que Hibernate nunca debería ejecutar un
UPDATE SQL a menos que esté seguro de que en realidad se está modificando algún objeto. En algunos casos (en
realidad, sólo cuando un objeto transitorio ha sido asociado con una nueva sesión usando update()), esto significa
que Hibernate ejecutará un comando SQLSELECT adicional, para determinar si un en realidad un UPDATE es
necesario.
(11)
polymorphism (optativo, por defecto, implicit): Determina si se usa polimorfismo explícito o implícito.
(12)
where (optativo) especifica una condición SQL WHERE arbitraria a ser usada cuando se seleccionen objetos de esta
clase.
(13)
persister (optativo): Especifica un ClassPersister hecho a medida.
(14)
batch-size (optativo, por defecto, 1) especifica un "tamaño de lote" para capturar instancias de esta clase por
identificador.
(15)
optimistic-lock (optativo, por defecto, version): Determina la estrategia de "locking" optimista.
(16)
lazy (optativo): La captura haragana puede ser completamente inhabilitada espefificando lazy="false".
(17)
entity-name (optativo, por defecto, el nombre de la clase): Hibernate3 permite que una clase sea mapeada
muchas veces (potencialmente, a distintas tablas), y permite mapeos de entidades qie estén representados por
Maps a nivel de Java, En estos casos, se debería proveer un nombre arbitrario explícito para la entidad. Vea
Sección 4.4, “Modelos dinámicos” y Capítulo 18, Mapeo XML for more information.
(18)
check (optativo): Una expresión SQL arbitraria usada para generar una constraint tipo check multifila a usar
durante la generación automática del esquema.
(19)
rowid (optativo): Hibernate puede usar los vulgarmente llamados ROWIDs, en aquellas DB que lo soporten. Por
ejemplo, en Oracle, Hibernate puede usar la columna adicional rowid para actualizar más rapidamente, si usted
configura esta opción a rowid. Un ROWID es un detalle de implementación, y representa la ubicación física de
una t-upla alamacenada.
(20)
subselect (optativo): Mapea una entidad inmutable y de sólo-lectura a un subselect de la base de datos. Es útil si
se quiere tener una vista en lugar de una tabla, pero no se cuenta con una vista en la base de datos. Vea más
adelante para más información.
(21)
abstract (optativo): Usado para marcar superclases abstractas en jerarquías de <union-subclass>.
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.
dirty verifica las columnas que hayan cambiado, permitiendo algunas modificaciones concurrentes
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.
5.1.4. id
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>
(1)
name (optativo): El nombre de la propiedad indentificadora.
(2)
type (optativo): Un nombre que indica el tipo de Hibernate
(3)
column (optativo, por defecto, el nombre de la propiedad): El nombre de la columna de la clave primaria
(4)
unsaved-value (optativo, por defecto, un valor "adecuado"): Una propiedad identficadora que indica que una
instancia ha sido recientemente instanciada (no grabada), distinguiéndola de las instancias desprendidas que fueron
grabadas o cargadas en una sesión previa.
(5)
access (optativo, por defecto, property): La estrategia que Hibernate debería usar para acceder al valor de la
propiedad.
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.
5.1.4.1. Generator
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>.
<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
hilo
usa 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.
uuid
usa 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.
guid
usa 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>.
select
obtiene 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-identity
una 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).
<param name="column">next_value</param>
<param name="max_lo">100</param>
</generator>
</id>
<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.
<generator class="sequence">
<param name="sequence">person_id_sequence</param>
</generator>
</id>
<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().
<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.
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”
incremento distintos, usando múltiples registros con claves diferentes. Este generador tiene varios parámetros de
configuración:
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.
5.1.7. composite-id
<composite-id
name="propertyName"
class="ClassName"
mapped="true|false"
access="field|property|ClassName">
node="element-name|."
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.
(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)
/>
(1)
column (optativo, por defecto, class) el nombre de la columna del discriminador
(2)
type (optativo, por defecto, string) un nombre que indica el tipo de Hibernate
(3)
force (optativo, por defecto, false) "fuerza" a Hibernate a especificar valores permitidos de discriminador
cuando captura todas las instancias de la clase raíz.
(4)
insert (optativo, por defecto, true) asígnele false si su columna de discriminador tambíén forma parte de un
identificador compuesto mapeado (le dice a Hibernate que no incluya a esta columna en el INSERT).
(5)
formula (optativo) una expresión SQL arbitraria que se ejecuta cuando un tipo tiene que ser evaluado. Permite
discriminación basada en contenido.
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|."
/>
(1)
column (optativo, por defecto, el nombre de la propiedad): El nombre de la columna que contiene el número de
versión.
(2)
name: El nombre de una propiedad de la clase persistente.
(3)
type (optativo, por defecto, integer): El tipo del número de versión.
(4)
Los números
accessde(optativo,
versión pueden ser deproperty
por defecto, los tipos de Hibernate
): La que, integer
long
estrategia , short
Hibernate , timestamp
debería o calendar
usar para obtener . de la
el valor
propiedad.
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|."
/>
(1)
column (optativo, por defecto, the property name): el nombre de una columna que contiene la timestamp.
(2)
name: El nombre de una propiedad estilo JavaBeans del tipo Date o Timestamp de la clase persistente.
(3)
access (optativo, por defecto, property): La estrategia que Hibernate debería usar para acceder al valor de la
propiedad.
(4)
unsaved-value (optativo, por defecto, null): Una propiedad de versión, que indica que una instancia ha sido
recientemente instanciada (no grabada, "transitoria"), distinguiéndola de una instancia desprendida, que haya sido
cargada o grabada por una sesión previa (undefined especifica que debería usarse el valor de la propiedad
indentificadora).
(5)
source (optativo, por defecto, vm): ¿De dónde debería Hibernate obtener el valor de la timestamp? ¿De la base de
datos, o de la JVM actual? Las timestamps originadas en la base de datos son costosas, porque Hibernate debe
consultar la base de datos para determinar el "próximo" valor, pero serán más seguras en entornos de cluster. Note
también, que no se sabe si todos los dialectos soportan esta obtención de timestamps de la base de datos, mientras
que otros pueden ser inseguros de usar en "locking", dada su falta de precisión (Oracle 8, por ejemplo).
(6)
generated (optativo, por defecto, never): Especifica que esta propiedad timestamp en realidad es generada por la
base de datos. Vea la discusión de propiedades generadas.
Note que <timestamp> equivale a <version type="timestamp">. Y <timestamp source="db"> equivale a <version
type="dbtimestamp">
5.1.11. property
<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"
/>
(1)
name: el nombre de la propiedad, com una letra minúscula inicial.
(2)
column (optativo, por defecto, the property name): el nombre de la columna de la tabla en la base de datos
mapeada. También puede especificarse con elemento(s) <column> anidados.
(3)
type (optativo): un nombre que indica el tipo de Hibernate.
(4)
update, insert (optativo, por defecto, true) : indica que las columnas mapeadas deben ser incluidas en los
comandos SQL UPDATE y/o INSERT. Asignarles false a las dos permite una propiedad puramente "derivada", cuyo
valor es inicializado solamente por alguna otra propieadad que se mapea a la(s) misma(s) columna(s), o por un
trigger, u otra aplicación.
(5)
formula (optativo): una expresión SQL que define el valor de una propiedad computada. Las propiedades
computadas no tienen un mapeo de columna propio.
(6)
access (optativo, por defecto, property): La estrategia que Hibernate debería usar para acceder al valor de esta
propiedad.
(7)
lazy (optativo, por defecto, false): Especifica que esta propiedad debería ser obtenida de manera haragana,
cuando se acceda por primera vez a la variable de instancia (requiere implementación bytecode en tiempo de
ejecución).
(8)
unique (optativo): Habilita la generación del lenguaje de definición de datos (DDL) para una constraint única para
las columnas. También permite que esto sea apuntado por una property-ref.
(9)
not-null (optativo): Habilita la generación del lenguaje de definición de datos (DDL) para una constraint de
nulabilidad para las columnas.
(10)
optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones al valor de esta propiedad
requieren o no un "lock" optimista. En otras palabras, determina si pueden ocurrir incrementos de versión cuando
la propiedad está "sucia".
(11)
generated (optativo, por defecto, never): Especifica que el valor de esta propiedad es en realidad generado por la
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).
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.
5.1.12. many-to-one
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"
/>
(1)
name: El nombre de la propiedad.
(2)
column (optativo): El nombre de la columna de clave foránea. También puede ser especificada por elementos
<column> anidados.
(3)
class (optativo, por defecto, el tipo de la propiedad, determinado por reflexión): El nombre de la clase asociada
(4)
cascade (optativo): Especifica qué operaciones serán propagadas en cascada desde el objeto padre hacia los
objetos asociados.
(5)
fetch (optativo, por defecto, select): Elije entre la captura (fetching) por outer-join y la captura por selección
secuencial.
(6)
update, insert (optativo, por defecto, true) especifica que las columnas mapeadas deben incluirse en el los
comandos SQL UPDATE y/o INSERT. Asignarles false permite una propiedad puramente "derivada", cuyo valor es
inicializado desde alguna otra propiedad, o desde un trigger, u otra aplicación.
(7)
property-ref: (optativo) El nombre de una propiedad de una clase asociada que está ligada a esta clave foránea.
Si no se especifica, se usa la clave primaria de la clase asociada.
(8)
access (optativo, por defecto, property): La estrategia que Hibernate debería usar para acceder al valor de la
propiedad.
(9)
unique (optativo): Habilita la generación de lenguaje de definición de datos (DDL) para la creación de una
constraint única para la columna de clave foránea. También permite que ésta sea el blanco referido por una
property-ref. Esto hace que la "multiplicidad" de la asociación sea, efectivamente, de-uno-a-uno.
(10)
not-null (optativo): Habilita la generación de lenguaje de definición de datos (DDL) para la creación de una
constraint de nulabilidad para las columnas de la clave foránea.
(11)
optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones de esta propiedad requieren o no
la adquisición de un "lock" optimista. En otras palabras, determina si debería ocurrir un incremento de versión
cuando esta propiedad esté "sucia".
(12)
lazy (optativo, por defecto, proxy): Las asociaciones de extremo único están "proxied" por defecto. lazy="no-
proxy" especifica que el valor de la propiedad debe ser capturado en forma haragana, cuando se acceda a la
variable de instancia por primera vez (requiere instrumentación bytecode en tiempo de ejecución). lazy="false"
especifica que la asociación siempre será capturada de manera ansiosa ("eager" fetching).
(13)
not-found (optativo, por defecto, exception): Especifica cómo serán manejadas las claves foráneas que se
refieran a filas ausentes. ignore tratará dicha fila ausente como si fuera una asociación nula.
(14)
entity-name (optativo): El nombre de entidad de la clase asociada.
(15)
formula (optativo): una expresión SQL que define el valor de una clave foránea computada.
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.
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).
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>.
5.1.13. one-to-one
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"
/>
(1)
name: El nombre de la propiedad.
(2)
class (optativo, por defecto, el tipo de la propiedad, determinado por reflexión): El nombre de la clase asociada.
(3)
cascade (optativo) especifica qué operaciones deben ser propagadas en cascada desde el objeto padre hacia el
objeto asociado.
(4)
constrained (optativo) especifica que una constraint de clave foránea a la clave primaria de la tabla mapeada
apunta a la tabla de la clase asociada. Esta opción afecta el orden en que save() y delete() son propagadas en
cascada, y determina si la asociación puede generar "proxies" (también es usada por la herramienta de exportación
de esquema de base de datos).
(5)
fetch (optativo, por defecto, select): Elige entre captura por outer-join y captura por selección secuencial.
(6)
property-ref: (optativo) El nombre de una propiedad de la clase asociada que está asociada a la clave primaria
de esta clase. Si no se especifica, se usa la clave primaria de la clase asociada.
(7)
access (optativo, por defecto, property): La estrategia que Hibernate debe usar para acceder al valor de la
propiedad.
(8)
formula (optativo): Casi todas las asociaciones de-uno-a-uno se mapean a la clave primaria de la entidad a la que
pertenecen, En el raro caso de que no sea así, se puede especificar alguna otra columna o expresión con la cual
asociarla usando una fórmula SQL. (vea org.hibernate.test.onetooneformula por un ejemplo).
(9)
lazy (optativo, por defecto, proxy): Por defecto, las asociaciones de extremo único usan proxies. lazy="no-
proxy" especififica que la propuedad debe ser capturada en forma haragana cuando se accede a la variable de
instancia por primera vez (requiere instrumentación bytecode de tiempo de ejecución). lazy="false" especifica
que á asociación será siempre capturada de forma "ansiosa" (eager fetching). ¡Note que si se usa
constrained="false", usar proxies es imposible e Hibernate siempre capturará en forma ansiosa!
(10)
entity-name (optativo): El nombre de entidad de la clase asociada.
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.
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:
...
<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:
Y esta asociación puede ser convertida en bidireccional agregando lo siguiente al mapeo de Person:
5.1.14. natural-id
<natural-id mutable="true|false"/>
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>
(1)
name: El nombre de la propiedad
(2)
class (optativo, por defecto, el tipo de la propiedad, determinado por reflexión): El nombre de la clase
componente (hija).
(3)
insert: ¿Aparecen las columnas mapeadas en los SQL INSERTs?
(4)
update: ¿Aparence las columnas mapeadas en los SQL UPDATEs?
(5)
access (optativo, por defecto, property): La estrategia que Hibernate debería usar para acceder al valor de la
propiedad.
(6)
lazy (optativo, por defecto, false): Especifica que este componente debería ser capturado en forma haragana
(lazy fetching) cuando se acceda a la variable de instancia por primera vez (requiere implementación bytecode en
tiempo de ejecución).
(7)
optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones de este componente requieren o
no la adquisición de un "lock" optimista. En otras palabras, determina si debería haber un incremento de versión
cuando la propiedad esté "sucia"
(8)
unique (optativo, por defecto, false): Especifica que existe una constraint de unicidad sobre todas las columnas
mapeadas del componente.
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”.
5.1.16. properties
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>
(1)
name: El nombre lógico del agrupamiento ( no es un nombre de propiedad real).
(2)
insert: ¿Aparecen las propiedades mapeadas en los comandos INSERT?
(3)
update: ¿Aparecen las propiedades mapeadas en los comandos UPDATE?
(4)
optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones de esta propiedad requieren o no
la adquisición de un "lock" optimista. En otras palabras, determina si debería ocurrir un incremento de versión
cuando esta propiedad esté "sucia".
(5)
unique (optativo, por defecto, false): Especifica que existe un constraint de unicidad sobre todas las columnas
mapeadas del componente.
<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.
<column name="firstName"/>
<column name="initial"/>
<column name="lastName"/>
</many-to-one>
5.1.17. subclass
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">
.....
</subclass>
(1)
Cada subclase
name: Eldebería
nombredeclara sus propias
(enteramente propiedades
calificado) persistentes y subclases. Se asume que las propiedades <version>
de la subclase.
e <id> serán heredadas de la clase raíz. Cada subclase en la jerarquía debe definir un valor único de discriminator-
5.1.18. joined-subclass
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">
(1)
name: El nombre enteramente calificado de la subclase.
(2)
table: El nombre de la tabla de la subclase.
(3)
proxy (optativo): Especifica una clase o interfaz a usar para la inicilización haragana de proxies.
(4)
lazy (optativo, por defecto, true): Asignar lazy="false" inhabilita el uso de captura haragana (lazy fetching).
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>
<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.
5.1.19. union-subclass
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">
.....
</union-subclass>
(1)
name: El nombre, enteramente calificado, de la subclase.
(2)
table: El nombre de la tabla de la subclase.
(3)
proxy (optativo): Especifica una clase o interfaz a usar para la inicilización haragana de proxies.
(4)
lazy (optativo, por defecto, true): Especificar lazy="false" inhabilita el uso de captura haragana (lazy
fetching).
Para información sobre los mapeos de herencias, vea Capítulo 9, Mapeo de Herencia.
5.1.20. join
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)
(1)
table: El nombre de la tabla asociada.
(2)
schema (optativo): Suplanta al nombre del esquema de base de datos especificado en el elemento raíz
<hibernate-mapping>.
(3)
catalog (optativo): Suplanta al nombre del catálogo de base de datos especificado en el elemento raíz
<hibernate-mapping>.
(4)
fetch (optativo, por defecto, join): Si se le asigna el valor pr defecto join, Hibernate usará un INNER JOIN para
capturar una asociación definida por una clase en su superclase, y utilizará un OUTER JOIN para una asociación
definida por una subclase. Si se le asigna el valor select, entonces Hibernate usará una asociación secuencial
definida en una subclase, la cual sólo será emitida cuando ocurra que una fila represente una instancia de la
subclase. Los INNER JOINS aún serán usados para capturar asociaciones definidas por la clase y sus superclases.
(5)
inverse (optativo, por defecto, false): Si se habilita, Hibernate no intentará insertar o acualizar las propiedades
definidas por esta asociación.
(6)
optional (optativo, por defecto, false): Si se habilita, Hibernate insertará una nueva fila sólo si las propiedades
definidas por esta asociación son no nulas, y siempre usará un OUTER JOIN para capturar las propiedades.
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.
<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.
5.1.21. key
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)
/>
(1)
column (optativo): El nombre de la columna de clave foránea. Esto también puede ser especficado por elementos
<column> anidados.
(2)
on-delete (optativo, por defecto, noaction): Especifica si la constraint de clave foránea tiene habilidtado el
borrado en cascada a niver de la base de datos.
(3)
property-ref (optativo): Especifica que la clave foránea se refiere a columnas que no son la clave primaria de la
tabla original. (provisto para datos anticuados/heredados solamente).
(4)
not-null (optativo): Especifica que las columnas de la clave foránea no son anulables (lo cual se sobreentiende
cuando la clave foránea es también parte de la clave primaria)
(5)
update (optativo): Especifica que la clave foránea nunca debería ser actualizada (lo cual se sobreentiende cuando
la clave foránea es también parte de la clave primaria)
(6)
unique (optativo): Especifica que la clave foránea debería tener una constraint de unicidad (lo cual se
sobreentiende cuando la clave foránea es también la clave primaria).
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>
5.1.23. import
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="ClassName" (1)
rename="ShortName" (2)
/>
(1)
class: The fully qualified class name of of any Java class.
(2)
rename (optativo, por defecto, the unqualified class name): A name that may be used in the query language.
5.1.24. any
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="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>
(1)
name: el nombre de la propiedad.
(2)
id-type: el tipo del identificador.
(3)
meta-type (optativo, por defecto, string): Cualquier tipo que sea permisible para mapear un discriminador.
(4)
cascade (optativo, por defecto none): el estilo de propagación en cascada
(5)
access (optativo, por defecto, property): La estrategia que Hibernate debería usar para acceder al valor de la
propiedad.
(6)
optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones de esta propiedad requieren o no
la adquisición de un "lock" optimista. En otras palabras, determina si debería ocurrir un incremento de versión
cuando esta propiedad esté "sucia".
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").
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
Mapeos de tipo de java.util.Date y sus subclases a tipos SQL DATE, TIME y TIMESTAMP (o equivalentes).
calendar, calendar_date
big_decimal, big_integer
class
Un mapeo de tipo java.lang.Class a VARCHAR (o la VARCHAR2 de Oracle). Una Class es mapeada con su nombre
enteramente calificado.
binary
text
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
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.
<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.
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.
<param name="default">0</param>
</typedef>
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.
</class>
Note cómo ahora las asociaciones son especificadas usando entity-name en lugar de class.
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;
}
/**
* @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).
@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;
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.
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.
<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>
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:
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).
Consejo
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">
</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"
>
(1)
name el nombre de la propiedad colección
(2)
table (optativo, por defecto, el nombre de la propiedad) el nombre de la tabla de la colección (no se usa en
asociaciones de-muchos-a-muchos).
(3)
schema (optativo) el nombre de un esquema de base de datos que suplanta al esquema declarado en el elemento
raíz.
(4)
lazy (optativo, por defecto, true) puede usarse para inhabilitar la "captura haragana" (lazy fetching) y especificar
que la asociación es siempre capturada en forma "ansiosa" (eager fetching), o para especificar que la asociación es
"súper-haragana", en donde la mayoría de las operaciones evitan inicializar la colección (prescrito para
colecciones muy grandes).
(5)
inverse (optativo, por defecto, false) marca esta colección como el extremo "inverso" de una asociación
bidireccional.
(6)
cascade (optativo, por defecto, none) les permite a las operaciones propagarse en cascada a las entidades hijas.
(7)
sort (optativo) especifica una colección ordenada con un orden natural, o una clase "comparator" dada.
(8)
order-by (optativo, a partir de JDK1.4 solamente) especifica una columna o columnas de tabla que define el
orden de iteración del Map, Set o bag, junto con un asc o desc opcional (ascendente/descendente).
(9)
where (optativo) especifica una condición SQL WHERE arbitraria, a ser usada ciando se capture o borre la
colección (útil cuando la colección sólo debe contener un subconjunto de los datos disponibles).
(10)
fetch (optativo, por defecto, select) Elije entre la captura por outer-join, select secuencial, y subselect
secuencial.
(11)
6.2.1. batch-size (optativo,
Claves foráneas porcolecciones
de las defecto, 1) especifica el "tamaño de lote" al capturar instancias de esta colección en
forma haragana.
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".
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|..."/>
(1)
column_name (obligatorio): El nombre de la columna que contiene el valor del índice de la colección.
(1)
base (optativo, por defecto, 0): El valor de columna índice correspondiente al primer elemento de la lista o array.
<map-key
column="column_name" (1)
formula="any SQL expression" (2)
type="type_name" (3)
node="@attribute-name"
length="N"/>
(1)
column (optativo): El nombre de la columna que contiene los valores de índice de la colección.
(2)
<map-key-many-to-many
formula (optativo): Una fórmula SQL usada para evaluar la clave del mapa
column="column_name" (1)
(1)
column (optativo): El nombre de la columna de clave foránea para los valores de índice de la colección.
(2)
formula (optativo): Una fórmula SQL usada para evaluar la clave foránea de la clave del mapa.
(3)
class (obligatorio): La clase de entidad usada como clave primaria del mapa.
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.
<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"
/>
(1)
column (optativo): El nombre de la columna que contiene los valores de elementos de la colección.
(2)
formula (optativo): Una fórmula SQL usada para evaluar el elemento.
(3)
type (obligatorio): El tipo de elemento de colección
<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"
/>
(1)
column (optativo): el nombre de columna del elemento clave foránea.
(2)
formula (optativo): una fórmula SQL usada para evaluar el valor del elemento clave foránea
(3)
class (obligatorio): el nombre de la clase asociada.
(4)
fetch (optativo, por defecto, join): permite capturas para esta asociación mediante un outer-join o un select
secuencial. Éste es un caso especial: para una captura total y "ansiosa" (usando un solo SELECT) de una entidad y
sus relaciones de-muchos-a-muchos con otras entidades, se debería habilitar la captura (fetch) de tipo join no
solamente en la colección misma, sino también en este atributo del elemento anidado <many-to-many>.
(5)
unique (optativo): habilita la generación de lenguaje de definición de datos (DDL) para una constraint de unicidad
en la columna de clave foránea. Esto vuelve efectivamente "de-uno-a-muchos" la multiplicidad de la asociación.
(6)
not-found (optativo, por defecto, exception): Especifica cómo se actuará cuando le falten filas a la clave
foránea. ignore tratará la fila faltante como una asociación nula
(7)
entity-name (optativo): el nombre de entidad de la clase asociada, como una alternativa a class.
(8)
property-ref: (optativo) el nombre de una propiedad, en la clase asociada que está ligada a esta clave foránea. Si
no se especifica, se usa la clave primaria de la clase asociada.
Una bag conteniendo enteros, con un orden de iteración determinado por el atributo order-by:
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"
/>
(1)
class (obligatorio): El nombre de la clase asociada.
(2)
not-found (optativo, por defecto, exception): especifica cómo se manejarán los identificadores en caché que
estén refiriéndose a columnas perdidas. ignore tratará la fila ausente como una asociación nula.
(3)
entity-name (optativo): El nombre de la entidad asociada, como alternativa a class.
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.
Valores permitidos del atributo sort son unsorted, natural y el nombre de una clase que implemente
java.util.Comparator.
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.
Las asociaciones pueden incluso ser ordenadas por algún criterio arbitrario en tiempo de ejecución, usandp un filtro
(filter()) de colección.
Una asociación bidireccional (bidirectional association) permite navegar desde ambos "extremos" de la asociación. Se
soportan dos tipos de asociación bidireccional:
one-to-many
many-to-many
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"/>
...
<class name="Item">
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.
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">
<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">
</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-
</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.
<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.
package eg;
import java.util.Set;
....
....
}
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>
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<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>
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 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_i
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.
7.1. Introducción
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.
7.2.1. de-muchos-a-uno
<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 )
7.2.2. de-uno-a-uno
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>
7.2.3. de-uno-a-muchos
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>
Pensamos que para este caso es mejor usar una tabla de asociación.
7.3.1. de-uno-a-muchos
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">
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
7.3.2. de-muchos-a-uno
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>
7.3.3. de-uno-a-uno
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>
7.3.4. 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>
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>.
7.4.2. de-uno-a-uno
<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 )
<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>
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">
<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>
7.5.2. de-uno-a-uno
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>
7.5.3. 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>
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>
entonces podemos mapear una asociación a la instancia actual (la que tiene effectiveEndDate nula) usando:
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.
return name;
}
public void setName(Name name) {
this.name = name;
}
......
......
}
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.
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.
<property name="first"/>
<property name="last"/>
</component>
</class>
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.
</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.
</class>
Los elementos compuestos pueden aparecer en consultas, usando la misma sintaxis que las entidades.
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 no es "sine qua non", pero cúmplalo de todos modos.
No se puede usar un IdentifierGenerator para generar claves compuestas. En lugar de eso, la aplicacíón debe asignar
sus propios identificadores.
Use la tag <composite-id> (con elementos <key-property> anidados) en lugar de la declaración de <id> usual. Por
ejemplo, la clase OrderLine (línea de una orden) tiene una clave primaria que depende de la clave (compuesta) de Order.
<class name="OrderLine">
<property name="name"/>
</class>
Ahora, cualquier clave foránea que se refiera a la tabla ORDERLINE, también será compuesta. Esto se debe declarar en
los mapeos para otras clases. La asociación con OrderLine sería mapeada así:
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</many-to-one>
(Note que la tag <column> es una alternativa al atributo column en todos lados.)
<set name="undeliveredOrderLines">
<key column name="warehouseId"/>
<many-to-many class="OrderLine">
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</many-to-many>
</set>
<key>
<column name="orderId"/>
<column name="customerId"/>
</key>
<one-to-many class="OrderLine"/>
</set>
Si la OrderLine misma posee una colección, también tiene una clave foránea compuesta.
<class name="OrderLine">
....
....
<list name="deliveryAttempts">
<composite-element class="DeliveryAttempt">
...
</composite-element>
</set>
</class>
<dynamic-component name="userAttributes">
<property name="foo" column="FOO" type="string"/>
<property name="bar" column="BAR" type="integer"/>
<many-to-one name="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component>
polimorfismo implícito
Es posible usar estrategias de mapeo diferentes para distintas ramas de la misma jerarquía de herencias, y después hacer
uso del polimorfismo implícito para lograr polimorfismo todo a lo largo de dicha jerarquía. Sin embargo, Hibernate no
soporta mezclar mapeos de <subclass>, and <joined-subclass> y <union-subclass> bajo el mismo elemento
<class> de la clase raíz. Sí es posible mezclar las estrategias de "una tabla por jerarquía" y de "una tabla por subclase"
bajo el mismo elemento <class>, combinando los elementos <subclass> y <join> (véase a continuación).
Es posible definir mapeos de subclass, union-subclass, y joined-subclass en documentos de mapeo separados, justo
debajo de hibernate-mapping. Esto permite extender la jerarquía de clases, simplemente agregando un nuevo archivo de
mapeo. Se debe especificar el atributo extends en el mapeo de la subclase, nombrando una superclase previamente
mapeada. Nota: en el pasado, esta característica hacía que el orden de los documentos de mapeo fuese relevante. Desde
Hibernate 3, el orden no importa cuando se usa la palabra "extends". El orden dentro de cada documento individual, aún
tiene que tener las superclases antes de las subclases.
<hibernate-mapping>
</hibernate-mapping>
Suponga que tenemos una interfaz Payment (pago), implementada por las clases CreditCardPayment, CashPayment,
ChequePayment (pago por tarjeta de crédito, efectivo y cheque respectivamente). El mapeo de "una tabla por jerarquía" se
vería así:
</class>
Se requiere exactamente una tabla. Con esta estrategia de mapeo, hay una gran limitación: las columnas declaradas por las
subclases, como CCTYPE, no pueden tener constraints NOT NULL.
</class>
Se requieren 4 tablas. Las tres tablas de subclases tienen asociaciones por clave primaria a la tabla de la superclase (de
modo que el modelo relacional es en realidad una asociación de-uno-a-uno).
Note que la implementación de Hibernate para "una tabla por subclase" no requiere una columna discriminadora. Otros
mapeadores objeto/relacionales usan una implementación diferente de "una tabla por subclase", que sí requiere una
columna discriminadora en la tabla de la superclase. El enfoque adoptado por Hibernate es mucho más difícil de
implementar, pero probablemente más correcto desde el punto de vista relacional. Si usted desea usar una columna
discriminadora con la estrategia de "una tabla por subclase", puede combinar el uso de <subclass> y <join>, como sigue:
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
La declaración optativa fetch="select" le dice a Hibernate que no capture los datos de la subclase ChequePayment
usando un outer join cuando se consulte a la superclase.
9.1.4. Mezclar "una tabla por jerarquía de clases" con "una tabla por subclase"
Incluso se pueden mezclar las estrategias de "una tabla por jerarquía de clases" con "una tabla por subclase" usando este
abordaje:
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<join table="CREDIT_PAYMENT">
<property name="creditCardType" column="CCTYPE"/>
...
</join>
</subclass>
Para cualquiera de estas estrategias de mapeo, una asociación polimórfica a la clase raíz Payment se mapea usando
<many-to-one>.
Con la estrategia de "una tabla por clase concreta" podríamos proceder de dos maneras. La primera es usar <union-
subclass>.
<class name="Payment">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="sequence"/>
</id>
...
</union-subclass>
<union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
...
</union-subclass>
</class>
Para las subclases, hay tres clases involucradas. Cada tabla define columnas para todas las propiedades de la subclase,
incluso las propiedades heredadas.
La limitación de este abordaje, es que si la propiedad está mapeada en la superclase, el nombre de la columna debe ser el
mismo en todas las tablas de subclases. (Podemos relajar este requisito en versiones futuras de Hibernate). La estrategia de
generador de identidad no está permitida para una herencia que esté usando union-subclass. Lógico, dado que la clave
primaria debe ser compartida por todas las subclases de la jerarquía participantes en la unión.
Si su superclase es abstracta, mapéela con abstract="true". Por supuesto, si no es abstracta, se necesita una tabla
adicional (por defecto sería PAYMENT en el ejemplo anterior) para almacenar instancias de la superclase.
9.1.6. Una tabla por cada clase concreta, usando polimorfismo implícito
</id>
<property name="amount" column="CREDIT_AMOUNT"/>
...
</class>
...
</class>
...
</class>
Nótese que no mencionamos explícitamente en ningún lado la interfaz Payment. También nótese que las propiedades de
Payment están mapeadas en cada una de las subclases. Si se quiere evitar la duplicación, considere usar entidades XML
(por ejemplo, [ <!ENTITY allproperties SYSTEM "allproperties.xml"> ] en la declaración de DOCTYPE y
&allproperties; en el mapeo).
La desventaja de este enfoque, es que Hibernate no generará SQL UNIONs cuando ejecute consultas polimórficas.
Según esta estrategia de mapeo, una asociación polimórfica a Payment sería normalmente mapeada usando <any>.
<column name="PAYMENT_CLASS"/>
<column name="PAYMENT_ID"/>
</any>
Hay una cosa más que resaltar, acerca de este mapeo. Como cada una de las subclases está mapeada en su propio
elemento <class> (y dado que Payment sólo es una interfaz), ¡cada una de las subclases podría fácilmente ser parte de
otra jerarquía de clases! (y aún se podrían usar consultas polimórficas contra la interfaz Payment).
...
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="amount" column="CASH_AMOUNT"/>
...
</joined-subclass>
<key column="PAYMENT_ID"/>
<property name="amount" column="CHEQUE_AMOUNT"/>
...
</joined-subclass>
</class>
De nuevo no necesitamos mencionar la interfaz Payment explícitamente. Hibernate devuelve automáticamente instancias
de, por ejemplo, CreditCardPayment y sus subclases, que también implementan Payment, pero no instancias de
NonelectronicTransaction.
9.2. Limitaciones
Hay ciertas limitaciones del abordaje a la estrategia "una tabla por clase concreta" usando polimorfismo implícito. Y hay
limitaciones un tanto menos restrictivas para los mapeos que usan <union-subclass>.
polimórfica
Estrategia de polimórfica polimórfica polimórfica
de-muchos- load()/get() polimórficos
herencia de-muchos-a-uno de-uno-a-uno de-uno-a-muchos
a-muchos
En otras palabras, los programadores en Hibernate siempre deberían pensar en términos del estado de sus objetos, y no
necesariamente en términos de qué comandos SQL están siendo ejecutados. De esto se encarga Hibernate, y sólo es
relevante para el programador cuando se trate de ajustar la perfomance del sistema.
Transitorio (en inglés, transient): un objeto es transitorio cuando ha sido instanciado usando el operador new , y no
está asociado con una sesión de Hibernate. No tiene representación persistente en la base de datos y no se le ha
asignado ningún identificador. Las instancias transitorias serán destruidas por el recolector de basura (garbage
collector), si la aplicación ya no tiene ninguna referencia a ellas. Use la sesión de Hibernate para volver persistente
un objeto (y deje que Hibernate se encargue de los comandos SQL que sean necesarios para dicha transición).
Persistente (en inglés, persistent): una instancia persistente ya tiene una representación en la base de datos, y un
valor de identificador. Aunque recién haya sido cargada o grabada, por definición ya está inscripta en el alcance de
la sesión. Hibernate detectará cualquier cambio que se efectúe sobre un objeto en estado persistente, y sincronizará
su estado con el de la base de datos cuando la unidad de trabajo se complete. Si el objeto persistente es tranformado
en transitorio, el programador no necesita ejecutar comandos UPDATE ni DELETE manualmente.
Desprendido (en inglés, detached): un objeto desprendido es un objeto que era persistente, pero cuya sesión ha sido
cerrada. La referencia al objeto aún es válida, y la instancia desprendida puede incluso ser modificada en este
estado. Una instancia desprendida puede ser reasociada a una nueva sesión en el futuro, volviéndola persistente de
nuevo (junto con todas las modificaciones que haya sufrido). Esta característica es ideal para programar unidades de
trabajo largas, que necesiten darle a la aplicación tiempo para "pensar". Las llamamos transacciones "de la
aplicación", esto es, una unidad del trabajo desde el punto de vista del usuario.
Ahora discutiremos los estados y las transiciones entre ellos (así como los métodos de Hibernate que disparan estas
transiciones) con más detalle.
Si Cat tiene un identificador autogenerado, dicho identificador se genera y se le asigna a cat cuando save() sea llamado.
Si Cat tiene un identificador asignado externamente, o una clave compuesta, dicho identificador debería serle asignado a
cat antes de invocar save(). Se puede usar persist() em lugar de save(), con la semántica definida en el borrador
temprano de EJB3.
persist() convierte una instancia transitoria en persistente. Pero no garantiza que el identificador le vaya a ser
asignado a la instancia inmediatamente: la asignación puede ocurrir al aplicársele "flush" a la sesión. persist()
también garantiza que nos ejecutará un comando INSERT si es llamado fuera de los límites de una transacción. Esto
es útil para conversaciones de largo aliento, con un contexto de sesión/persistencia extendido.
save() sí garantiza la devolución de un identificador, Si hay que ejecutar un INSERT (por ejemplo, porque el
generador es "identity" y no "sequence") el INSERT ocurre inmediatamente, sin importar si se está dentro o fuera de
una transacción. Esto es indeseable en conversaciones largas, con un contexto de sesión/persistencia extendido.
Alternativamente, se puede asignar el identificador usando una versión sustituida (overloaded) de of save().
Si el objeto que se hace persistente tiene objetos asociados, (por ejemplo, la colección "kittens", gatitos, en el ejemplo
anterior), dichos objetos pueden ser hechos persistentes en cualquier orden, a menos que exista una constraint NOT NULL
en una columna de clave foránea. El riesgo de violar una constraint de clave foránea no existe, pero sí se puede llegar a
violar una constraint NOT NULL si se les aplica save() a los objetos en el orden incorrecto.
Normalmente, usted no debe preocuparse por estos detalles, dado que, muy probablemente, usted terminará usando la
característica de Hibernate llamada persistencia transitiva para grabar los objetos asociados automáticamente. Con ella, ni
siquiera las violaciones de NOT NULL ocurren, Hibernate se hace cargo de todo. La persistencia transitiva se discute más
Dese cuenta de que load() lanzará una excepción irrecuperable si no existe la fila de base de datos correspondiente. Si la
clase es mapeada con un proxy, load() simplemente devuelve un proxy no inicializado, y no hay contacto con la base de
datos hasta que realmente se invoque un método del proxy. Este comportamiento es muy útil si se desea crear una
asociación con un objeto, sin realmente cargarlo desde la base de datos. También permite cargar múltiples instancias en
lotes, si batch-size está definido para la clase.
Cuando no se esté seguro de que exista una fila correspondiente, debería usarse el método get(), el cual consulta la base
de datos inmediatamente y devuelve null si no existe una fila correspondiente.
Incluso se puede cargar un objeto usando un SQL SELECT ... FOR UPDATE, usando LockMode. Vea la documentación de
la API para más información.
Note que ni las instancias asociadas ni las colecciones contenidas son seleccionadas FOR UPDATE (con el propósito de ser
modificadas), a menos que se especifiquen los valores lock o all en el parámetro de mapeo "cascade".
Se puede recargar un objeto y todas sus asociaciones an cualquier momento, usndo el método refresh(). Esto es muy útil
cuando se están usando triggers de DB para inicializar algunas de las propiedades del objeto.
sess.save(cat);
sess.flush(); //fuerza el SQL INSERT
sess.refresh(cat); //relee el estado (luego de que el trigger se ejecuta)
Llegados a este punto,usualmente se plantea una cuestión: ¿Cuánto carga Hibernate de la base de datos?¿Cuántos SQL
SELECTs se usan? Esto depende de la estrategia de captura (fetching strategy) y se explica en la Sección 19.1,
“Estrategias de captura (fetch)”.
10.4. Consultas
Si no se conocen los identificadores de los objetos que se está buscando, se necesita una consulta (query). Hibernate
soporta un lenguaje para consultas poderoso pero fácil de usar: HQL (las siglas en inglés de "Hibernate Query Language").
Para la creación programática de consultas, Hibernate soporta dos clases muy sofisticadas, Criteria y Example (a veces
referidos como QBC y QBE, por "query by criteria" y "query by example", respectivamente). También se puede expresar
la consulta en el lenguaje nativo de su base de datos, con soporte opcional de conversión de su resultado en objetos.
Las consultas en HQL y en SQL nativo son representadas por una instancia de org.hibernate.Query. Esta interfaz
ofrece métodos para vincular parámetros, manejo de resultsets, y la ejecución de la consulta misma. Siempre se obtiene el
Query a partir la sesión actual.
Una consulta normalmente se ejecuta invocando list(), el resultado de la consulta será cargado completamente en una
colección en memoria. Las entidades de instancia que son devueltas están en estado persistente. El método
uniqueResult() ofrece un atajo si usted ya sabe que la consulta devolverá un solo objeto. Note que las consultas que
hacen uso de "captura ansiosa" (eager fetching), normalmente devuelven duplicados del objeto raíz, con sus colecciones
inicializadas. Estos duplicados se pueden eliminar, simplemente usando un Set.
Ocasionalmente, es posible que se obtenga una mejor performace si se ejecuta la consulta usando el método iterate().
Esto es normalmente cierto si se espera que las instancias de entidad devueltas por la consulta ya estarán en la sesión, o en
el caché de 2do nivel. Si no están en el caché, iterate() será más lento que list(), y es posible que requiera varios
viajes a la base de datos para una simple consulta: normalmente 1 SELECT inicial que sólo devuelve los identificadores, y
N SELECTs adicionales que inicializan las instancias propiamente dichas.
// captura de ids
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
while ( iter.hasNext() ) {
Qux qux = (Qux) iter.next(); // captura de un objeto
// alguna comprobación complicada que no podíamos expresar en la consulta
if ( qux.calculateComplicatedAlgorithm() ) {
// borrar la isntancia actual
iter.remove();
// no necesitamos procesar el resto
break;
}
}
Las consultas de Hibernate a veces devuelven t-uplas de objetos, en cuyo caso cada t-upla se devuelve como un array.
while ( kittensAndMothers.hasNext() ) {
Object[] tuple = (Object[]) kittensAndMothers.next();
Una consulta pueden especificar una propiedad de la clase en la cláusula SELECT. Las funciones agregadas son
consideradas resultados "escalares", y no entidades en estado persistente.
while ( results.hasNext() ) {
Object[] row = (Object[]) results.next();
Color type = (Color) row[0];
Date oldest = (Date) row[1];
Integer count = (Integer) row[2];
.....
}
En Query se proveen métodos para vincular valores con parámetros nombrados, o parámetros ? al estilo JDBC. Al
contrario que con JDBC, Hibernate numera los parámetros empezando de 0. Los parámetros nombrados son
identificadores con la forma :name en la caden de la consulta. Las ventajas de los parámetros nombrados son:
los parámetros nombrados son independientes del orden en que ocurren en la cadena de la consulta
son autodocumentados
//parámetro posicional
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();
10.4.1.5. Paginación
Si se necesita establecer límtes en el resultset (el máximo número de filas que se quiere capturar / desde qué fila se desea
obtener datos), deberían usarse los siguientes métodos de la interfaz Query:
Hibernate sabe cómo traducir este límite al el SQL nativo de su base de datos.
Si su driver de JDBC soporta ResultSets navegables, la interfaz Query puede ser usada para obtener un objeto
ScrollableResults, el cual permite una navegación flexible del los resultados de la consulta.
// encuentra el primer nombre en cada página de una lista alfabética de gatos, por nombre.
firstNamesOfPages = new ArrayList();
do {
String name = cats.getString(0);
firstNamesOfPages.add(name);
}
while ( cats.scroll(PAGE_SIZE) );
}
cats.close()
Nótese que para esta funcionalidad se requiere una conexión abierta a la base de datos. y un cursor. Use
setMaxResult()/setFirstResult() si necesita funcionalidad de paginación desconectada.
Se puede definir consultas nombradas (named queries) en el documento de mapeo. Recuerde usar una sección CDATA si su
consulta contiene carateres que podrían ser interpretados como caracteres reservados de XML).
<query name="ByNameAndMaximumWeight"><![CDATA[
from eg.DomesticCat as cat
where cat.name = ?
and cat.weight > ?
] ]></query>
Query q = sess.getNamedQuery("ByNameAndMaximumWeight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();
Note que el código del programa propiamente dicho es independiente del lenguaje de consultas que se use; se pueden
definir consultas SQL nativas en metadata, o migrar consultas existentes a Hibernate colocándolas en archivos de mapeo.
También note que la declaración dentro de un elemento <hibernate-mapping> requiere un nombre globalmente único
para la consulta, mientras que la declaración de una consulta dentro de un elemento <class> se vuelve única
automáticamente al afijarle el nombre enteramente calificado de la clase, por ejemplo, eg.Cat.PorNombreYPesoMaximo.
El filtro de una colección es un tipo especial de consulta que puede aplicársele a una colección persistente, o a un array.
La cadena de la consulta puede hacer uso de this, refiriéndos el elemento actual de la colección.
La colección devuelta se considera una bag, y es una copia de a colección dada. La colección original no es modificada (lo
cual es conceptualmente opuesto a la idea de un "filtro", pero consistente en cuanto al resultado que se espera).
Observe que los filtros no requieren una cláusula FROM (aunque pueden tenerla si es necesario). Los filtros no están
limitados a devolver los elementos de colecciones mismos.
Incluso un filtro con su consulta vacía es útil, por ejemplo para cargar un subconjunto de elementos en una colección
enorme:
HQL es extremadamente poderoso, pero algunos programadores prefieren ir construyendo sus consultas dinámicamente,
usando una API orientada a objetos, en lugar de utilidar cadenas. Hibernate provee una API intuitiva, Criteria, para
estos casos:
Las APIs de Criteria y su pariente Example se discuten con más detalle en Capítulo 15, Consultas Criteria.
Se puede expresar una consulta en SQL, usando createSQLQuery() y dejar que Hibernate se haga cargo del mapeo del
resultset a objetos. Note que en cualquier momento se puede llamar session.connection() y usar la Connection de
JDBC directamente. Si decide usar la API de Hibernate, debe incluir los alias de SQL entre llaves:
Las consultas SQL pueden contener parámetros nombrados o posicionales, al igual que las consultas de Hibernate. Se
puede encontrar más información sobre las consultas SQL nativas en Capítulo 16, SQL Nativo.
A veces, este modelo de programación es ineficiente, dado que requeriría tanto un SQL SELECT (para cargar el objeto)
como un SQL UPDATE (para persistir su estado modificado) en la misma sesión. Por lo tanto, Hibernate ofrece un abordaje
alternativo, usando instancias desprendidas:
Note que Hibernate no expone una API propia para ejecutar comandos UPDATE o DELETE directamente. Hibernate es un
servicio de manejo de persistencia, y no se lo debe concebir en términos de qué comandos ejecuta. JDBC es una API
perfectamente adecuada para ejecutar comandos SQL, se puede obtener una conexión de JDBC en cualquier momento
invocando session.connection(). Más aún, la noción de "operaciones en masa" entra en conflicto con el mapeo
objeto/relacional para las aplicaciones orientadas al procesamiento de transacciones en línea. Puede ser, sin embargo,
que versiones futuras de Hibernate provean funciones especiales para procesamiento en masa. Véase Capítulo 13,
Procesamiento en lotes para posibles trucos de operación en lote.
Hibernate soporta este modelo, proveyendo "reasociación" de entidades desprendidas usando los métodos
Session.update() o Session.merge():
// en la primera sesión
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);
Si el Cat con identificador catId ya hubiera sido cargado por la segunda sesión (secondSession) cuando la sesión trataba
de reasociarlo, se habría producido una excepción.
Use update() si usted está seguro de que la sesión no contiene una instancia que ya es persistente con el mismo id,
merge() si usted desea consolidar sus modificaciones en cualquier momento, independientemente del estado de esa
instancia en la sesión. En otras palabras, update() es normalmente el primer método que se invocará en una sesión nueva,
garantizando que la reasociación de las instancias desprendidas sea la primera operación que ocurra.
La aplicación debería aplicarles update() a las instancias desprendidas dependientes de la instancia desprendida en
cuestión, si y sólo si desea que su estado también sea actualizado. Esto puede ser automatizado, por supuesto, usando
persistencia transitiva, vea la Sección 10.11, “Persistencia transitiva”.
El método lock() también le permite a la aplicación reasociar un objeto con una sesión nueva. ¡Sin embargo, la instancia
desprendida no debe haber sido modificada!
//simplemente reasocia
sess.lock(fritz, LockMode.NONE);
//primero verifique la versión, luego reasocie
sess.lock(izi, LockMode.READ);
//primero verifique la versión usando SELECT ... FOR UPDATE, luego reasocie
sess.lock(pk, LockMode.UPGRADE);
Note que lock() puede ser usado con varios LockModes, vea la documentacíón de la API y el capítulo sobre manejo de
transacciones para más informacíón. Reasociar no es el único uso que lock() tiene.
Otros modelos para unidades largas de trabajo se discuten en Sección 11.3, “Control de concurrencia optimista”.
un identificador nuevo), o bien actualice/reasocie las instancias desprendidas asociadas con el identificador actual. El
método saveOrUpdate() implementa tal funcionalidad:
// en la primera sesión
Cat cat = (Cat) firstSession.load(Cat.class, catID);
El uso y semántica de saveOrUpdate() les parece confuso a los nuevos usuarios. En primer lugar, a menos que se esté
intentando usar instancias de una sesión en otra sesión nueva, no hay necesidad de usar update(), saveOrUpdate(), ni
merge(). Hay aplicaciones enteras que jamás utilizarán ninguno de estos métodos.
si ya hay otro objeto asociado con la sesión con el mismo identificador, se produce una excepción
si el identificador del objeto es asignable, y le ha sido recientemente asignado a una instancia recién creadam se
invoca save().
si el objeto es versionado (por una <version> o <timestamp>), y la propiedad versión es el mismo valor asignado a
un objeto recién instanciado, se invoca save()
si ya hay una instancia persistente con el mismo identificador asociada con la sesión, copia el estado del objeto dado
en la instancia persistente.
si no hay ninguna instancia asociada con la sesión, intenta cargarla de la base de datos, o crear una nueva instancia
persistente.
la instancia dada (como parámetro) no se vuelve asociada con la sesión, permanece desprendida.
sess.delete(cat);
Se puede borrar objetos en cualquier orden que se desee, sin temor de causar violaciones de clave foránea. Pero sí es
posible violar constraints NOT NULL aplicadas a la columna de clave foránea, por ejemplo si se borra al padre antes que al
hijo.
El ReplicationMode determina cómo el método replicate() lidiará con conflictos que puedan existir con filas
existentes en la base de datos.
ReplicationMode.IGNORE: ignorar el objeto cuando exista una fila de base de datos con el mismo identificador.
ReplicationMode.OVERWRITE: sobrescribir cualquier fila existente en la base de datos que tenga el mismo
identificador.
ReplicationMode.EXCEPTION: lanzar una excepción si ya existe una fila en la base de datos que tenga el mismo
identificador.
ReplicationMode.LATEST_VERSION: sobrescribir la fila si su número de versión es más antiguo que el del objeto.
En caso contrario, ignorar.
Algunos casos de uso para esta característica incluyen: reconciliar datos ingresados en dos instancias distintas de base de
datos, actualizar la información de configuración del sistema durante la actualización de un producto, dar marcha atrás
(rollback) con cambios hechos durante transacciones carentes de integridad transaccional (non-ACID), y muchos más.
en org.hibernate.Transaction.commit()
en Session.flush()
todas las inserciones de entidades, en el mismo orden en que los objetos hayan sido grabados con Session.save()
todos los borrados de entidades, en el mismo orden en que los objetos hayan sido borrados usando
Session.delete()
(una excepción es que los objetos que usen la generación de identificador native serán insertados al invocar save).
A excepción de cuando se invoque flush() explícitamente, no hay absolutamente ninguna garantía acerca de cuándo los
llamados JDBC serán ejecutados por la sesión, sólo acerca del orden en que serán ejecutados. Sin embargo, Hibernate
garantiza que Query.list(..) nunca devolverá datos rancios.
Es posible cambiar el comportamiento por defecto para que el "flush" ocurra menos frecuentemente. La clase FlushMode
define tres modos diferentes: sólo provocar el "flush" al momento de llamar "commit" (y esto sólo en una transacción de la
API de Hibernate), provocar el "flush" automáticamente usando la rutina explicada, o nunca provocar el "flush" menos
que flush() sea invocado explícitamente. Este último modo es útil para unidades de trabajo largas, en donde la sesión se
mantiene abierta y desconectada por largo tiempo (véase la Sección 11.3.2, “Sesión extendida y versionado automático”).
sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // permite que las consultas devuelvan datos rancios
Durante el "flush", puede ocurrir una excepción (por ejemplo, si una operación de creación de datos viola una constraint).
Como el manejar excepciones involucra algún conocimiento del comportamiento transaccional de Hibernate, lo
discutiremos en el Capítulo 11, Transacciones y concurrencia.
Si los hijos en una relación padre/hijo fuesen de la índole "value type" (por ejemplo una colección de direcciones, o de
cadenas), su ciclo de vida dependería del padre, y no haría falta hacer nada para que la propagación en cascada se
efectuare de manera adecuada ante cambios de estado. Cuando el padre es grabado, los hijos "value type" son grabados
también; cuando se borra al padre, a los hijos también, etc. Esto inclusive funciona para operaciones como el borrado de
un hijo de una colección: Si un "value type" hijo es borrado de una colección, como los "value types" no pueden tener
referencias compartidas, ya no tiene razón se ser, e Hibernate lo borrará de la base de datos.
Ahora considere el mismo escenario, pero con objetos padre e hijos siendo entidades, no "value types" (por ejemplo,
categorías e items, o gata y gatitos). Las entidades tienen su propio ciclo de vida, soportan referencias compartidas (así que
quitar a una referncia de una colección no significa que pueda ser borrada), y por defecto no hay propagación en cascada
de una entidad a sus entidades asociadas. Hibernate no implementa el el concepto de persistir todo lo que esté al alcance
(persistence by reachability) por defecto.
Para cada operación básica de la sesión de Hibernate, incluyendo: persist(), merge(), saveOrUpdate(), delete(),
lock(), refresh(), evict(), replicate() hay un estilo de propagación en cascada correspondiente. Se se quiere
que una operación sea propagada en cascada en la dirección de una asociación, eso se debe indicar en el documento de
mapeo. Por ejemplo:
Incluso se puede usar cascade="all" para indicar que todas las operaciones deben ser propagadas en dirección de esa
asociación. El valor por defecto, cascade="none", especifica que ninguna operación debe ser propagada.
Un tipo especial de propagación en cascada, delete-orphan, se aplica sólo a las asociaciones de-uno-a-muchos, e indica
que la operación delete() debe ser aplicada a cualquier hijo cuyo padre haya sido borrado.
Recomendaciones:
Normalmente no tiene sentido habilitar la propagación en cascada para asociaciones <many-to-one> o <many-
to-many>.La propagación es usualmente útil para asociaciones <one-to-one> y <one-to-many>.
Si el ciclo del vida del hijo está ligado al del padre, conviértalo en un objeto de ciclo de vida especificando
cascade="all,delete-orphan".
En cualquier otro caso, puede que usted no necesite usar cascade en absoluto. Pero si usted calcula que deberá
trabajar a menudo con el padre y los hijos en la misma transacción, y quiere ahorrarse algo de tecleo, considere usar
cascade="persist,merge,save-update".
Mapear una asociación (ya sea de un solo valor, o de una colección) con cascade="all" marca la asociación como "del
estilo padre/hijo", en donde una grabación/actualización/borrado del padre resulta en una grabación/actualización/borrado
de los hijos.
Más aún, una mera referencia a un hijo desde un padre persistente resultará en la grabación/actualización del hijo. Lo
inverso no se da, sin embargo. Un hijo a quien el padre deje de hacer referencia no es automáticamente borrado, excepto
en el caso de una asociación de-uno-a-muchos mapeada con cascade="delete-orphan". La semántica exacta de la
relación padre/hijo es como sigue:
Si se le pasa un padre (como parámetro) a un persist(), todos sus hijos serán también pasados a persist()
Si se le pasa un padre a un merge(), todos sus hijos serán también pasados a merge()
Si se le pasa un padre a un save(), update() o saveOrUpdate(), todos sus hijos serán también pasados a
saveOrUpdate().
Si un hijo transitorio o desprendido empieza a ser referido por un padre persistente, se le pasa a saveOrUpdate()
Si un hijo deja de ser referido por un padre persistente, no pasa nada en especial: la aplicación debería borrar el hijo
explícitamente si hace falta (a menos que haya cascade="delete-orphan", en cuyo caso la clase el hijo que se ha
vuelto huérfano será borrado).
Por último, note que la propagación de operaciones en cascada se le puede aplicar a un objeto al momento de invocarlo o
al momento de efectuar "flush". Todas las operaciones, si están habilitadas, se propagan en cascada hacia todas las
entidades que estén a su alcance cuando la operación es ejecutada. Sin embargo, save-upate y delete-orphan son
transitivas hacia las entidades asociadas cuando ocurre el "flush" de la sesión.
Hibernate expone metadatos mediante las interfaces ClassMetadata y CollectionMetadata, y la jerarquía de Type.
Instancias de las interfaces de metadatos se obtienen de la SessionFactory.
Hibernate no efectúa "lock" de objetos en memoria. Si aplicación puede esperar el comportamiento que esté definido por
el nivel de aislamiento de sus transacciones de base de datos. Note que, gracias a la sesión, que es también un caché con
alcance a la transacción, Hibernate provee lecturas repetibles para la búsqueda por identificadores, y consultas de entidad
(no consultas "de reporte" que devuelvan valores escalares).
Además de versionar para lograr un control de concurrencia optimista, Hibernate también ofrece una API (mínima) para
efectuar un "lock" de filas pesimista, usando la sintaxis SELECT FOR UPDATE. El control de concurrencia optimista, y esta
API, se discuten más adelante en este capítulo.
Comenzaremos la discusión sobre el control de concurrencia en Hibernate involucrando los conceptos de configuración
(Configuration), la fábrica de sesiones (SessionFactory), y la sesión, así como las transacciones de base de datos y las
conversaciones largas.
Una sesión individual (Session), en cambio, es barata de crear, y no es segura en cuanto al acceso por múltiples threads
(no es "threadsafe"). Se espera que sea usada una sola vez, para una interacción simple o "request" (una sola solicitud, una
sola "conversación" o "unidad de trabajo"), y luego sea descartada. Una sesión no intentará obtener una conexión de
JDBC, ni una fuente de datos (Connection, Datasource) a menos que sea necesario. Por lo tanto, no consume recursos
mientras no es usada.
Para completar este panorama se debe pensar también en las transacciones de base de datos. Una transacción de base de
datos debe ser lo más corta posible, para reducir las posibilidades de conflictos de "lock" en la base de datos. Las
transacciones largas impiden que la aplicación sea "escalable" (es decir, que se adapte con facilidad a una mayor demanda
y tamaño), por no poder soportar una mayor carga de demandas concurrentes. Por tal motivo, mantener una transacción
abierta mientras el usuario "piensa" y hasta que la unidad de trabajo se complete, casi nunca es una buena idea.
¿Cuál es el alcance de una unidad de trabajo? ¿Una simple sesión de Hibernate puede abarcar varias transacciones de base
de datos, o sus alcances son similares y con una relación uno a uno? ¿Cuándo se debe abrir y cerrar una sesión, y cómo se
demarcan los límites de una transacción de base de datos?
¡Antes que nada, no emplee la práctica desaconsejada (antipattern) de una-sesión-por-operación, es decir, no abra y
cierre una sesión para cada llamado a la base de datos en un mismo thread! En una aplicación, los llamdos a la base de
datos son hechos en un orden planificado, y son agrupados en unidades de trabajo atómicas. (Nótese que esto también
significa que tener la conexión en auto-commit después de cada comando SQL es inútil en una aplicación, esta modalidad
de trabajo es más acorde con trabajo ad-hoc en una consola SQL. Hibernate inhabilita el auto-commit inmediatamente, o
espera que el servidor de aplicaciones lo haga). Las transacciones de base de datos nunca son opcionales, toda
comunicación con la base de datos debe ocurrir dentro de una transacción, ya sea para escribir o para leer datos. Como se
dijo anteriormente, el comportamiento "auto-commit" debe evitarse, dado que es improbable que un conjunto de muchas
pequeñas transacciones tenga mejor performance que una unidad de trabajo claramente definida. Esta última es también
más flexible y extensible.
El patrón más común, en una aplicación cliente/servidor multiusuario, es "una sesión por cada solicitud" (session-
per-request) . Según este modelo, una solicitud o "request" del cliente se envía al servidor (en donde está ejecutándose la
capa de persistencia Hibernate), se abre una nueva sesión, y todas las operaciones de base de datos son ejecutadas e esta
unidad de trabajo. Una vez que el trabajo se haya completado (y la respuesta para el cliente se haya preparado), la sesión
sufre un "flush" y se cierra. También se usaría una sola transacción de base de datos para atender la solicitud del cliente,
comenzándola y efectuando "commit" cuando se abra y cierre la conexión, respectivamente. La relación entre sesión y
transacción es una a una, y este modelo es perfectamente adecuado para muchas aplicaciones.
El desafío radica en la implementación. Hibernate ya trae un sistema incluido que se puede usar para simplificar el uso de
este patrón, y manejar lo que se considera la "sesión actual". Todo lo que el programador debe hacer, es comenzar una
transacción cuando haya que procesar una solicitud (request) al servidor, y finalizar la transacción antes de que la
respuesta le sea enviada al cliente. Esto se puede lograr de varias maneras: las soluciones más comunes son: -un filtro
(ServletFilter) -un interceptor basado en AOP, que tenga sus "pointcuts" en los métodos del servicio, -un contenedor
de proxies/intercepciones, etc. Una manera estándar de implementar aspectos que conciernen de una misma forma a varios
niveles y áreas de la aplicación (en inglés, "cross-cutting aspects"), es usar un contenedor de EJB, el cual puede
implementar transacciones de una manera declarativa y manejada por el contenedor mismo (CMT). Si se decide usar la
demarcación de transacciones programática, prefiérase la API de Transaction de Hibernate, que es fácil y portátil.
El código de su aplicación puede acceder a la "sesión actual" para procesar una request, simplemente invocando
sessionFactory.getCurrentSession() en cualquier lugar, y tan a menudo como haga falta. Siempre se obtendrá una
sesión que estará dentro del alcance o "scope" de la transacción de base de datos actual. Esto tiene que ser configurado, ya
sea para entornos de recursos locales o para JTA (véase la Sección 2.5, “Sesiones contextuales”.
A veces es conveniente extender los alcances de la sesión y de la transacción de base de datos hasta que la vista o "view"
le haya sido presentada al usuario. Esto es especialmente útil en aplicaciones basadas en servlets, las cuales utilizan una
fase separada de presentacíón posterior al procesamiento de la solicitud o "request". Extender la transacción de base de
datos hasta que la "view" sea presentada es fácil si se implementa un interceptor propio. Sin embargo, no es fácil de hacer
si uno se apoya en un EJBs con transacciones CMT, dado que la transacción se completará tras el return de los métodos de
los EJBs, antes de que la presentación de ninguna view hubiere comenzado. Visite el sitio de web de Hibernate para
consejos y ejemplos acerca de este patrón Open Session in View.
El patrón "una-sesión-por-solicitud" (session-per-request) no es el único concepto útil que puede usarse para diseñar
unidades de trabajo. Muchos procesos de negocios requieren toda una serie de interacciones con el usuario, entretejidas
con accesos a la base de datos. En las aplicaciones de web y corporativas, no es aceptable que una transacción de base de
datos dure a lo largo de toda la interacción con el usuario. Considere el siguiente ejemplo:
Aparece la primera ventana de diálogo, los datos que ve el usuario han sido cargados en una sesión y transacción de
base de datos en particular. El usuario es libre de modificar los objetos.
El usuario pulsa "Grabar" luego de 5 minutos, y espera que sus modificaciones sean hechas persistentes; también
espera haber sido la única persona que haya editado esa información, y que no puedan haber ocurrido otras
modificaciones conflictivas.
A esto le llamamos una "unidad de trabajo"; desde el punto de vista del usuario, una conversación (o transacción de la
aplicación). Hay varias maneras de implementar esto en su aplicación.
Una primera implementación ingenua sería manetner la sesión y la transacción de base de datos abiertas durante el tiempo
que el usuario se tome para pensar, lo cual ejerce un "lock" sobre la base de datos para impedir modificaciones
concurrentes, y garantizar aislamiento y atomicidad. Esto es, por supuesto, una práctica a evitar o "anti-patrón" (anti-
pattern), dado que los conflictos de lock no le permitirán a nuestra aplicación adaptarse a una mayor demanda por usuarios
concurrentes.
Claramente, debemos usar varias transacciones de BD para implementar la conversación. En este caso, mantener un
aislamiento entre los procesos de negocio se vuelve en parte responsabilidad de la capa de la aplicación. Una simple
conversación normalmente abarca varias transacciones de base de datos. Será atómica si sólo una de dichas transacciones
(la última) es la que almacena los datos modificados, todas las otras simplemente leen datos (por ejemplo, en una ventana
de diálogo tipo "paso a paso" o "wizard", que abarque varios ciclos solicitud/respuesta). Esto es más fácil de implementar
de lo que parece, especialmente si se utilizan las ventajas que provee Hibernate:
Versionado automático: Hiebernate puede efectuar automáticamente por usted un control de concurrencia
optimista, puede detectar automáticamente si ocurrió una modificación durante el tiempo que el usuario se tomó
para reaccionar. Usualmente, esto sólo se verifica al final de la conversación.
Objetos desprendidos: si se decide usar el patrón una sesión por solicitud (session-per-request) ya discutido, todas
las instancias cargadas se convertirán en "desprendidas" (detached) durante el tiempo que el usario se tome para
pensar. Hibernate le permite reasociar estos objetos y persistir las modificaciones; este patrón se llama una-sesión-
por-solicitud-con-objetos-desprendidos. Para aislar modificaciones concurrentes se usa versionado automático.
Sesión larga (o "extendida"): la sesión de Hibernate puede desconectarse de la conexión JDBC subyacente luego de
que la transacción haya ejecutado su commit, y reconectada cuando ocurra una nueva solicitud del cliente. Este
patrón se conoce como una-sesión-por-conversación y hace que la reasociación de objetos sea innecesaria. Para
aislar modificaciones concurrentes se usa versionado automático, y a la sesión no se le permite hacer "flush"
automático, sino explícito.
Una aplicación puede aceder en forma concurrente al mismo estado persistente en dos sesiones diferentes. Pero una
instancia de una clase persistente nunca se comparte entre dos instancias de Session. Por lo tanto, hay dos nociones
diferentes de identidad:
Identidad de JVM
foo==bar
Entonces, para objetos asociados a una sesión en particular, (esto es, en el mismo alcance o "scope" de una sesión) las dos
nociones son equivalentes, e Hibernate garantiza que la identidad JVM equivale a la identidad de BD. Sin embargo, si la
aplicación accede en forma concurrente al mismo dato, puede ocurrir que la misma identidad persistente esté contenida en
dos instancias de objeto en dos sesiones distintas. En este caso la identidad persistente o de base de datos existe, pero la
identidad de JVM no, los objetos son "diferentes". Estos conflictos se resuelven a nivel usando versionado automático
(cuando ocurren los "flush"/"commit"), usando el enfoque optmista.
Este enfoque deja que Hibernate se preocupe por la concurrencia. También provee la mejor "escalabilidad", dado que
garantizar la identidad sólo a nivel de unidades de trabajo en un thread simple no requiere un costoso "lock" ni otros
medios de sincronización. La aplicación no necesita sincronizar ningún objeto, siempre y cuando se atenga a que se usará
un solo thread por sesión. Dentro de una sesión, la aplicación puede usar == tranquilamente para comparar objetos.
Sin embargo, una aplicación que use == fuera de una sesión, se puede topar con resultados inesperados. Esto puede ocurrir
incluso en lugares insólitos, por ejemplo, si se colocan dos instancias desprendidas en el mismo Set, existe la posibilidad de
que ambas tengan la misma identidad de base de datos (es decir, que representen la misma fila) pero no la misma identidad
JVM. El programador debe sustituir los métodos equals() y hashCode() en las clases persistentes, e implementar su
propia noción de igualdad entre objetos. Sólo una advertencia: nunca use el identificador de base de datos para
implementar igualdad; use una "clave de negocios", una combinación única y normalmente inmutable de atributos. Si la
instancia transitoria es almacenada en un Set, cambiar el hashcode rompe el contrato del Set. Los atributos de las "claves
de negocio" no necesitan ser tan estables como las claves primarias de una base de datos. Sólo necesitan poder establecer,
de manera estable, diferencias o igualdad entre los objetos que estén en un Set. Vea el sitio de web de Hibernate para una
discusión más detallada sobre este tema. También note que éste no es un problema de Hibernate, sino la manera en que los
objetos de Java implementan identidad e igualdad.
Una sesión (Session) no es segura en cuanto a acceso concurrente por múltiples threads (no es "thread-safe"). Las
cosas que se supone funcionen en forma concurrente, como las HTTP requests, los session beans, o los workers de
Swing workers, causarían condiciones de conflicto por recursos conocidas como "race conditions" si la instancia de
una sesión de Hibernate fuese compartida. Si la sesión de Hibernate está contenida en una sesión de HTTP
(HttpSession, la cual se discute más adelante), se debería considerar el sincronizar el acceso a la sesión HTTP. De
otra manera, cualquier usuario que cliquee "reload" lo suficientemente rápido, es probable que termine usando la
misma sesión (de Hibernate) en dos threads ejecutándose en forma concurrente.
Si Hibernate produce una excepción, significa que hay que desandar (rollback) la transacción de base de datos, y
cerrar la sesión inmediatamente (como se discute luego en más detalle),. Si la sesión está asociada a la aplicación,
debe detenerse la aplicación. Efectuar un "rollback" de la transacción no restaura los objetos de negocio al estado en
el que estaban antes de que la transacción ocurriese. Esto significa que el estado de la base de datos y el de los
objetos de negocio sí quedan fuera de fase. Normalmente esto no es problema, porque las excepciones no son
recuperables, y de todos modos es necesario comenzar todo de nuevo luego de un "rollback".
La sesión almacena en su caché cada objeto que esté en estado persistente (habiendo vigilado y comprobado
Hibernate si su estado es "sucio"). Esto significa que crecerá para siempre, hasta provocar un error de memoria
(OutOfMemoryException) si se la deja abierta por mucho tiempo o simplemente se le cargan demasiados datos. Una
solución para esto, es invocar clear() y evict() para manejar el caché de la sesión, pero lo que más
probablemente se debería considerar es un procedimiento de base de datos almacenado (stored procedure) si se
necesitan operaciones masivas de datos. Algunas soluciones se muestran en el Capítulo 13, Procesamiento en lotes.
Mantener una sesión abierta durante toda la interacción con el usuario también aumenta la probabilidad de que los
datos se vuelvan "rancios".
Una aplicación de Hibernate puede ser ejecutada en un entorno "no manejado", "no administrado" (es decir,
autosuficiente, una simple aplicación de web o Swing), y también en entornos J2EE "administrado" (managed
environments). En un entorno "no manejado", Hibernate normalmente es responsable por su propio "pool" de conexiones.
El programador tiene que establecer los limites de las transacciones manualmente (en otras palabras, invocar los métodos
begin, commit y rollback de las transacciones él mismo). Un entorno "administrado", normalmente provee transacciones
administradas por el contenedor ("container-managed transactions o CMT por sus siglas en inglés), con la disposición de
las tranascciones definida declarativamente en los archivos de descripción de despliegue o "deployment descriptors" de los
session beans EJB, por ejemplo. En tal caso, la demarcación manual o "programática" de las transacciones no es necesaria.
Sin embargo, a veces es deseable mantener la capa de persistencia portátil entre entornos administrados y no
administrados. Utilizar la demarcación programática se puede usar.en ambos casos. Hibernate ofrece una API llamada
Transaction que se traduce en el sistema nativo de transacciones del entorno en el cual el programa haya sido
desplegado. Esta API es opcional, pero recomendamos usarla siempre, a menos que el código esté en un session bean,
dentro de un servidor con CMT.
cerrar la sesión
Efectuar el "flush" de la sesión ha sido discutido antes; ahora le daremos una mirada más detallada a la demarcación de
transacciones y al manejo de excepciones, tanto en un entorno administrado como no administrado.
Si la capa de persistencia e Hibernate se ejecuta en un entorno no-administrado, las coneiones a la base de datos
usualmente son manejadas por un "pool" de conexiones simple (es decir, no una DataSource) del cual Hibernta obtiene las
tx.commit();
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // o mostrar un mensaje de error
}
finally {
sess.close();
}
No hace falta invocar el "flush" de la sesión explícitamente: el llamaod a commit() dispara automáticamente la
sincronización (dependiendo del Modo de "flush" para la sesión. Un llamado a close() marca el fi de la sesión. La
implicancia más importante de close() es que la conexión JDBC será cedidad por la sesión. Este código Java es portátil,
y puede ejecutarse tanto en entornos administrados como no administrados.
Una solución muchio más flexible (que ya viene incorporad en Hibernate), es el manejo del contexto de "sesión actual",
como se describión anterioromente.
factory.getCurrentSession().getTransaction().commit();
}
catch (RuntimeException e) {
factory.getCurrentSession().getTransaction().rollback();
throw e; // o mostrar un mensaje de error
}
Se podrán encontrar fragmentos de código como éstos en cualquier aplicación normal. Las excepciones fatales, de sistema,
deberían ser capturadas en la capa superior. En otras plabras, el código que ejecuta los llamados a Hibernate, en la capa de
persistencia, y el código que maneja las RuntimeExceptions (y normalmente hace las tareas de limpeza y salida) están en
capas diferentes. El "manejo de contexto actual" hecho por Hibernate puede simplificar este diseño considerablemente,
todo lo que se necesita es accedeso a la SessionFactory. El manejo de excepciones se discute más adelante en este
capítulo.
Note que debería seleccionarse org.hibernate.transaction.JDBCTransactionFactory (el valor pord efecto), y, para
el segundo ejemplo, el valor "thread" para hibernate.current_session_context_class.
Si la capa de persistencia se ejecuta en un servidor de aplicaciones (por ejemplo, detrás de EJB session beans), cada
conexión de base de datos obtenida por Hibernate será automáticamente parte de la transacción global JTA. También se
puede instalar una implementación JTA autónoma, y usarla sin EJB. Hibernate ofrece dos estrategias para integración con
JTA:
Si se usan tranascciones manejadas por beans (bean-managed transactions o BMT por sus siglas en inglés), Hibernate le
dirá al servidor de aplicaciones que empiece y finalice una transaccción BMT si se usa la API de Transaction API. De
este modo, le código e manejo de transacciones para un entorno no administrado es idéntico al de un entorno
administrado.
// estilo BMT
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
tx.commit();
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // o mostrar un mensaje de error
}
finally {
sess.close();
}
Si se desea usar la sesión asociada a una transacción, es decir, la funcionalidad getCurrentSession() para una más fácil
propagación del contexto, se debe usar la API de JTA UserTransaction directamente:
tx.begin();
tx.commit();
}
catch (RuntimeException e) {
tx.rollback();
throw e; // o mostrar un mensaje de error
}
Con CMT, la demarcación de transacciones se hace en los descriptores de despliegue (dployment descriptors) del session
bean, no se hace programáticamente. Así que el código queda reducido a:
// estilo CMT
Session sess = factory.getCurrentSession();
En un entorno CMT/EJB incluso el rollback ocurre automáticamente, puesto que una RuntimeException no capturada
emitida por el método de un session bean le dice al contenedor que efectúe rollback en la transacción global. Esto
significa que no hace falta usar la API de Transaction API de Hibernate en absoluto con BMT or CMT, e igualmente
se obtiene propagación automática de la sesión "actual" asociada a la transacción.
La operación getCurrentSession() tiene una desventaja en un entorno JTA:, Hay una advertencia sobre el uso del
modo de liberación de conexiones after_statement, el cual es el valor por defecto. Debido a una tonta limitación de la
especificación JTA, para Hibernate no es posible limpiar automáticamente instancias no cerrada de ScrollableResults o
Iterator que hayan sido devueltas por scroll() o iterate(). Usted debe liberar el cursor de base de datos subyacente
invocando explícitamente cursor by calling ScrollableResults.close() o Hibernate.close(Iterator) en el bloque
finally. (Por supuesto, la mayoría de las aplicaciones pueden fácilmente evitar usar en absoluto scroll() o iterate()
en el código JTA o CMT).
Si la sesión provoca una excepción (incluida cualquier SQLException), se debería efectuar un rollback de la transacción
de base de datos inmediatamente, invocar Session.close() y descartar la instancia de la sesión. Algunos métodos de
Session no dejarán a la sesión en un estado consistente. Ninguna excepción generada por Hibernate puede ser tratada
como recuperable. Asegúrese de que la sesión será cerrada invocando close() en el bloque finally.
HibernateException, la cual envuelve la mayoría de los errores que ocurren en la capa de persistencia de Hibernate, es
una excepción del tipo "unchecked" (esto es, que no requiere captura obligatoriamente en tiempo de compilación). No lo
era en versiones anteriores de Hibernate. En nuestra opinión, no se debería forzar al programador a capturar excepciones
de bajo nivel en la capa de persistencia. En la mayoría de los sistemas, las excepciones "unchecked" son manejadas en uno
de los primeros "marcos" de la pila de invocaciones a métodos (es decir, en las capas más "altas" de la aplicación), y se le
presenta un mensaje de error al usuario de la aplicación, o se adopta algún otro curso de acción apropiado. Note que
Hibernate puede también emitir otras exceptiones, además de HibernateException. Éstas son, de nuevo, no
recuperables, y se debe adoptar las medidas apropiadas para lidiar con ellas.
Hibernate envuelve las excepciones SQLException generadas al interactuar c con la base de datos en una
JDBCException. De hecho, Hibernate intentará convertir la excepción en una subclase de JDBCException que tenga más
sentido. La SQLException subyacente está siempre disponible via JDBCException.getCause(). Hibernate convierte las
SQLException en subclases apropiadas de JDBCException usando el conversor SQLExceptionConverter que está
asociado a la fábrica SessionFactory. POr defecto, el SQLExceptionConverter es definido de acuerdo al dialecto SQL
elegido. Pero es posible enchufar una implementación a medida (ver los javadocs para la clase
SQLExceptionConverterFactory por detalles). Los subtipos estándar de JDBCException son:
LockAcquisitionException: indica un error al adquirir el nivel de "lock" necesario para efectuar la operación
solicitada.
GenericJDBCException: una excepción genérica que no cae en ninguna de las otras categorías.
Una característica extremadamente importante provista por un entorno aministrado, como EJB, la cual nunca es provista
por un entorno no administrado, es la expiración de las transacciones, o "transaction timeout". Los "timeouts" de las
transacciones que ninguna trnasacción "rebelde" ocupe indefinidamente los recursos del sistema sin devolverle respuesta
alguna al usuario. Fuera de un entorno administrado (JTA), Hibernate no puede proveer esta funcionalidad en forma
completa. Sin embargo, puede al menos controlar las operaciones de acceso a datos, asegurándose de que los "puntos
muertos" (deadlocks) y las consultas con resultados enormes estén limitadas a un tiempo definido. En un entorno
administrado, Hibernate puede delegar el "timeout" de las transacciones en JTA. Esta funcionalidad es abstraída por el
objeto Transaction de Hibernate.
// do some work
...
sess.getTransaction().commit()
}
catch (RuntimeException e) {
sess.getTransaction().rollback();
throw e; // or display error message
}
finally {
sess.close();
}
Note que setTimeout() no puede ser llamada desde un bean en CMT, en donde los "timeouts" de las transacciones son
definidos declarativamente.
El único abordaje consistente con una alta concurrencia y un alta "escalabilidad", es el control de concurrencia optimista
con versionado. El chequeo de versiones usa números de versión, o timestamps, para detectar adtualizaciones conflictivas
(y evitar que se pierdan modificaciones). Hibernate ofrece 3 enfoques distintos para escribir una aplicación que use
concurrencia optimista. Los casos de uso que mostraremos ocurren en el contexto de una conversación larga, pero el
chequeo de versiones tiene la ventaja de también prevenir la pérdida de modificaciones en transacciones de base de datos
simples.
En una implementación sin mucha ayuda de Hibernate, cada interacción con la base de datos ocurre en una nueva sesión,
y el programador es responsable de recargar todas las instancias persistentes desde la base de datos antes de manipularlas.
Este enfoque fuerza a la aplicacíón a efectuar su propio chequeo de versiones, para asegurar el aislamiento en al
conversación transaccional. Este abordaje es el menos eficiente en términos de acceso a la base de datos, y es el más
parecido a los "entity beans" de EJB.
t.commit();
session.close();
(N.del.T):"foo" y "bar", de origen incierto, son locuciones que se usan en inglés como ejemplos de nombres para
cualquier cosa, especialmente en el ámbito de la computación. Si se usa "foo" como nombre en un ejemplo, casi siempre
se espera que el siguiente nombre sea "bar".
La propiedad version se mapea usando <version>, e Hibernate incrementará el número automáticamente durante el
"flush" si la entidad está sucia.
Por supuesto, si se opera en un entorno de baja concurrencia de datos, y no se requiere un chequeo de versiones, se puede
usar este abordaje pero saltearse el chequeo de versiones. En tal caso, la estrategia por defecto para conversaciones largas
será "el último commit es el que gana". Tenga en cuenta que esto puede confundir a los usuarios de la aplicación, dado
que pueden experimentar pérdidas de actualizaciones sin mensajes de error, o sufrir la posibilidad de conflictos cuando los
cambios se sincronicen.
Claramente, el chequeo manual de versiones sólo es factible en circunstancias muy triviales, y en la mayoría de las
aplicaciones no es práctico. A menudo hay que chequear no sólo instancias simples, sino todo un árbol de objetos
modificados. Hibernate ofrece chequeo automático de versión, usando uno de estos paradigmas de diseño: "sesión
extendida", o "instancais desprendidas".
Una sola instancia de sesión y sus clases persistentes son usadas por toda la conversación (lo cual se conoce como
una-sesión-por-conversación). Hibernate chequea las instancias de las versiones cuando ocurre el "flush", emitiendo una
excepción si se detecta modificaciones concurrentes. El capturar y manejar estas excepciones queda a cargo del
programador (opciones comunes que se le dan al usuario son: sincronizar los cambios manualmente, o recomenzar la
conversación de negocios con datos que no estén "rancios").
La sesión es desconectada de toda conexión JDBC subyacente mientras se espera que el usuario complete su interacción.
Esta estrategia es la más eficiente en términos de acceso a la base de datos. La aplicación no necesita preocuparse por
chequear versiones o reasociar instancias desprendidas, ni tiene que recargar instancias en cada transacción de base de
datos.
foo.setProperty("bar");
El objeto foo todavía sabe en qué sesión fue cargado. Comenzar una nueva transacción de base de datos en una sesión
vieja, obtiene una nueva conexión y reanuda la sesión. Efectuar un "commit" de una transacción de base de datos
desconecta una sesión de la coinexión JDBC y devuelve la conexión al "pool". Tras la reconexión, para forzar un chequeo
de versión en los datos que no se estén actualizando, se puede invocar Session.lock() con LockMode.READ en cualquier
objeto que pueda haber sido actualizado por otra transacción. Usted no necesita un "lock" sobre cualquier dato que usted
esté actualizando. Usualmente, se le debe asignar FlushMode.MANUAL a una sesión extendida, de manera que, en realidad,
sólo a la última transacción de base de datos se le permita persistir todas las modificaciones hechas durante esa
conversación. Por eso, sólo esta última transacción de BD incluiría la operación flush(), y luego también el llamado de la
sesión a close() para cerrar la conversación.
Este patrón es problemático si la sesión es demasiado grande para como para ser almacenada durante el tiempo que el
usuario se toma para reaccionar. Por ejemplo, una HttpSession debería mantenerse tan chica como sea posible. Como la
sesión es también el caché de primer nivel, y contiene todos los objetos cargados, probablementa sólo podamos usar esta
estrategia en uns pocos ciclos solicitur/respuesta. Una sesión debería usarse para una sola conversación, dado que pronto
contendrá datos "pasados".
(Note que versiones anteriores de Hibernate requerían que la sesión se conectara y desconectara explícitamente. Dichos
métodos ahora son obsoletos (deprecated), y comenzar y terminar una transacción tiene el mismo efecto).
También note que se debería mantener la sesión desconectada cerca de la capa de persistencia. En otras palabras, use un
"stateful session bean" de EJB para contener la sesión en un entorno de tres capas, y no la transfiera al entorno de web (ni
siquiera la serialice para transferirla a otra capa separada) para almacenarla en una HttpSession.
El patrón de "sesión extendida" una-sesión-por-conversación, es más difícil de implementar con control automático del
contexto de sesión actual. Para esto se necesita proveer una implementación a medida de CurrentSessionContext. Vea
la Wiki de Hibernate para ejemplos:
Cada interacción con el repositorio persistente ocurre en una nueva sesión. Sin embargo, las mismas instancias persistentes
son reusadas para cada interacción con la DB. La aplicación manipula el estado de las instancias desprendidas,
orignialmente cargadas en otra sesión, y lyego las reasocia usando Session.update(), Session.saveOrUpdate(), or
Session.merge().
De nuevo, Hibernate chequeará ls versiones de instancia al ocurrir el "flush", lanzando una excepción si han ocurrido
actualizaciones conflictivas.
Se puede también invocar lock() en lugar de update(), y usar LockMode.READ (efectuando un cheque de versión,
eludiando todos los cachés) si se está seguro de que el objeto no ha sido modificado.
Se puede inhabilitar el incremento automático de versión de Hibernate para propiedades y colecciones específicas,
asignándole al atributo de mapeo optimistic-lock el valor false. Hibernate no incrementará más las versiones, si la
propiedad está sucia.
Los sistemas de base de datos anticuados a menudo son estáticos y no pueden ser modificados. U otras aplicaciones
pueden estar accediendo a dichas base de datos, sin saber cómo manejar números de versiones ni incluso timestamps. En
ambos casos, el versionado no puede basarse en una columna en particular de una tabla. Para forzar un chequeo de
versión sin un mapeo de versión o timestamp, con una comparación del estado de todos los campos en una fila, habilite
optimistic-lock="all" en el mapeo de <class>. Note que esto sólo funciona, conceptualmente, si Hibernate puede
comparar el estado nuevo con el viejo, es decir, si se usa una sola sesión larga y no una-sesión-por-solicitud-con-objetos-
desprendidos.
A veces la modificacion concurrente puede ser permitida, simepre y cuando los cambios que hayan sido efectuados no se
superpongan. Si se asigna optimistic-lock="dirty" al mapear la <class>, Hibernate sólo comparará los campos sucios
En ambos casos, sea con columnas dedicadas de versión/timestamp, o con comparación completa/de campos sucios,
Hibernate usa un solo comando UPDATE (con una cláusula WHERE apropiada) por entidad para ejecutar el chequeo de
versión y actualizar la información. Si se desa usar persistencia transitiva para la reasociación en cascada de entidades
relacionadas, es posible que Hibernate ejecute algunos UPDATEs innecesarios. Esto normalmente no es un problema, pero
puede que se ejecuten triggers on update en la base de datos, cuando no se les ha efectuado ningún cambio a las instancias
desprendidas. Se puede personalizar este comportamiento más a medida, asignando select-before-update="true" em
el mapeo de <class>, forzando a Hibernate a practicar un SELECT de la instancia para asegurarse de que realmente
hayan ocurrido cambios, antes de actualizar la fila.
Hibernate siempre usará el mecanismo de lock de la base de datos, ¡nunca "lock" de objetos en memoria!
La clase LockMode define los distintos niveles de "lock" que pueden ser adquiridos por Hibernate. Un lock se obtiene por
los mecanismos siguientes:
LockMode.UPGRADE puede ser adquirido ante una solicitud explícita del usuario, usando SELECT ... FOR UPDATE
en una base de datos que soporte dicha sintaxis.
LockMode.UPGRADE_NOWAIT puede ser adquirido ante una solicitud explícita del usuario, usando SELECT ... FOR
UPDATE NOWAIT bajo Oracle.
LockMode.READ es adquirido automáticamente cuando Hibernate lee datos bajo los niveles de aislamiento
"Repeatable Read" o "Serializable". Puede ser readquirido por solicitud específica del usuario.
LockMode.NONE representa la ausencia de "locks". Todos los objetos pasan a este modo de lock al final de una
transacción. Los objetos asociados con la sesión a través de un llamado a update() o saveOrUpdate() también
comienzan en este modo de "lock".
La "solicitud (request) explícita del usuario" se expresa de alguna de las siguientes maneras:
Un llamado a Session.lock().
Un llamado a Query.setLockMode().
Si Session.load() es invocado con UPGRADE o UPGRADE_NOWAIT, y el objeto requerido aún no había sido cargado por la
sesión, el objeto es cargado usando SELECT ... FOR UPDATE. Si se invoca load() para un objeto que ya está cargado,
con un "lock" menos restrictivo que el que se está pidiendo, Hibernate invoca lock() para dicho objeto.
Session.lock() preactica un chequeo del número de versión si el "lock" especificado es READ, UPGRADE o
UPGRADE_NOWAIT. (En el caso de UPGRADE o UPGRADE_NOWAIT, se usa SELECT ... FOR UPDATE).
Si la base de datos no soporta el lock mode solicitado, Hibernate usará un modo alternativo apropiado (en lugar de emitir
una excepción). Esto asegura que la aplicación sea portátil.
org.hibernate.ConnectionReleaseMode:
AFTER_TRANSACTION: dice que se liberen las conexiones una vez que la org.hibernate.Transaction se haya
completado.
AFTER_STATEMENT (también conocida como "liberación agresiva"): dice que las conexiones se liberen luego de la
ejecución de todos y cada uno de los comandos. Esta liberación agresiva es omitida si el comando deja recursos
abiertos que aún estén asociados con la sesión actual; actualmente la única situación en la que esto ocurre es cuando
se usa un org.hibernate.ScrollableResults.
auto (el valor por defecto): esta opción le delega la decisión al modo de liberación recibido por el método
org.hibernate.transaction.TransactionFactory.getDefaultReleaseMode(). Con JTATransactionFactory,
devuelve ConnectionReleaseMode.AFTER_STATEMENT; con JDBCTransactionFactory, devuelve
ConnectionReleaseMode.AFTER_TRANSACTION. Casi nunca es buena idea cambiar el comportamiento por
defecto, porque las fallas en relación con este valor normalmente indican errores de programación (bugs) o
suposiciones incorrectas en el código.
on_close: indica que se use ConnectionReleaseMode.ON_CLOSE. Este valor se conserva por compatibilidad hacia
atrás, pero su uso se desaconseja.
12.1. Interceptores
La interfaz Interceptor provee métodos de retorno o "callbacks" desde la sesión a la aplicación, permitiéndole a la
aplicación inspeccionar y/o manipular propiedades de un objeto persistente antes de grabarlo. Un uso posible de esto es,
escribir información de seguimiento/auditoría. Por ejemplo, el interceptor siguiente asigna automáticamente la propiedad
createTimestamp cuando se crea un Auditable, y actualiza la propiedad lastUpdateTimestamp cuando un Auditable
es actualizado.
package org.hibernate.test;
import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;
import org.hibernate.EmptyInterceptor;
import org.hibernate.Transaction;
import org.hibernate.type.Type;
public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Ty
// no hace nada
}
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] pre
String[] propertyNames, Type[] types) {
public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, T
if ( entity instanceof Auditable ) {
loads++;
}
return false;
}
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, T
if ( entity instanceof Auditable ) {
creates++;
for ( int i=0; i<propertyNames.length; i++ ) {
if ( "createTimestamp".equals( propertyNames[i] ) ) {
state[i] = new Date();
return true;
}
}
}
return false;
}
Los interceptores vienen en dos variantes: Con alcance de sesión (session-scoped), y con alcance de toda la fábrica de
sesiones (sessionfactory-scoped).
Un interceptor con alcance de sesión se especifica cuando la sesión se abra usando uno de los métodos adicionales
(overloaded) SessionFactory.openSession() que acepta un Interceptor como parámetro.
Un interceptor con alcance de SessionFactory se registra con el objeto Configuration antes de consctruir la
SessionFactory. En este caos, el interceptor provisto será a aplicado a todas las sesiones abiertas por esa fábrica de
sesiones. Esto es cierto, a menos que se use una sesión que haya sido abierta especificando explícitamente otro
interceptor. El código de los interceptores de alcance de SessionFactory debe ser seguro en cuando a acceso concurrente
(thread-safe), dado que su código será (potencialmente) usado por varias sesiones al mismo tiempo. Asegúrese de no
almacenar estado específico de una sesión.
Esencialmente todos los métodos de la interfaz Session están correlacionados con un evento. Hay un LoadEvent, un
FlushEvent, etc. (consulte la DTD del archivo de configuración XML o el paquete org.hibernate.event para una lista
completa de los tipos de evento definidos). Cuando se invoca uno de estos métodos, la sesión de Hibernate genera el
evento apropiado y lo pasa a los "listeners" (escuchas) de eventos que hayan sido configurados para ese tipo. De fábrica y
por defecto, esos "listeners" implementan el mismo procesamiento en el que esos métodos habrían resultado. De todos
modos, usted es libre de crear listener a medida, que implemente alguna de la interfaces correspondientes (por ejemplo, el
evento LoadEvent es procesado por una implementación registrada de la interfaz LoadEventListener interface), en cuyo
caso la implementación es la que se vuelve responsable de procesar todo llamado a load() que la sesíón haga.
A los efectos prácticos, los "listeners" deben ser considerados singletons, lo cual significa que serán compartidos entre
solicitudes de clientes, y no deberían almacenar estado como variables de instancia.
Un listener a medida debería implementar la interfaz apropiada para el evento que quiera procesar, y/o extender una de las
clases utilitarias de base (o incluso los listeners que ya vienen incluidos en Hibernate, dado que fueron declarados como
no-finales con ese propósito). Los listeners a medida pueden ser registrados programáticamente a través del objeto
Configuration, o especificados declarativamente en el archivo de configuración XML de Hibernate (no se soporta la
configuración declarativa mediante el archivo ".properties"). He aquí un ejemplo de un listener a medida para el evento
"load".
También se necesita una entrada de configuración, indicándole a Hibernate que use este listener, además del listener por
defecto.
<hibernate-configuration>
<session-factory>
...
<event type="load">
<listener class="com.eg.MyLoadListener"/>
<listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
</event>
</session-factory>
</hibernate-configuration>
Los listeners registrados declarativamente no pueden compartir instancias. Si la misma clase es usada por muchos
elementos <listener/>, cada referencia resultará en una instancia separada de esa clase. Se se necesita la capacidad de
compartir instancias de listener entre distintos tipos de listener, se debe usar la forma programática de registrarlos.
¿Por qué impementar una interfaz y también definir un tipo específico durante la configuración? Porque una
implementación de listener puede estar implementando más de una interfaz. Al tener que especificar también el tipo
durante el registro, se vuelve fácil habilitar/inhanilitar los listeners a medida durante la cofiguración.
La seguridad declarativa de Hibernate usualmente se maneja en una capa de "fachada de sesión" (session façade). A partit
de Hibernate3, a algunas acciones se les puede asignar permisos vía JACC, y se pueden autorizar vía JAAS. Ésta es una
funcionalidad opcional, construida encima de la arquitectura de eventos.
Primero se deben configurar los listeners de eventos, para habilitar el uso de autorizaciones JAAS.
Note que <listener type="..." class="..."/> es simplemente una forma abreviada de <event type="...">
<listener class="..."/></event> para cuando hay exactamente un listener para un tipo de evento en particular.
Los nombres de los roles son aquéllos que sean comprendidos por su proveedor JACC.
Esto fallaría. provocando un OutOfMemoryException más o menos alrededor de la línea número 50.0000. Esto se debe a
que Hibernate guarda todas las instancias de Customer en el caché de sesión a medida que las va insertando.
En este capítulo le mostramos cómo evitar este problema. Pero primero, si se está usando procesamiento en lotes (en
inglés, "batch processing"), es indispensable habilitar el uso del procesamiento en lotes JDBC, si se pretende alcanzar una
performance razonable. Asígnele un valor razonable al tamaño del lote JDBC, digamos, de 10 a 50.
hibernate.jdbc.batch_size 20
Note que Hibernate inhabilita la inserción por lotes a nivel de JDBC en forma transparente si se está usando un generador
de identificadores identiy.
Tal vez se quiera realizar este tipo de trabajo en un proceso en donde la interacción con el caché de 2do nivel esté
completamente inhabilitada:
hibernate.cache.use_second_level_cache false
Pero esto no es absolutamente necesario, dado que se puede inhabilitar el CacheMode específicamente, para anular la
interacción con el caché de 2do nivel.
tx.commit();
session.close();
tx.commit();
session.close();
tx.commit();
session.close();
Note que, en este ejemplo de código, las instancias de Customer (cliente) devueltas por la consulta son automáticamente
desprendidas. Nunca están asociadas con ningún contexto de persistencia.
Se considera que las operaciones insert(), update() y delete() definidas por la interfaz StatelessSession son
operaciones directamente a nivel de fila de la base de datos, lo cual resulta en la ejecución inmediata de un SQL INSERT,
UPDATE o DELETE, respectivamente. Por esto, su semántica es muy diferente de las operaciones save(),
saveOrUpdate() y delete() definidas por la interfaz Session.
La pseudo-sintaxis para os comandos UPDATE y DELETE es: ( UPDATE | DELETE ) FROM? NombreDeLaEntidad (WHERE
where_conditions)?. Algunos puntos a destacar:
Sólo se puede nombrar una entidad en la cláusula "from"; opcionalmente, ésta puede tener un alias. Si lo tiene,
entonces cualquier referencia a propiedades debe estar calificada usando dicho alias. Si la entidad no tiene alias,
entonces es ilegal que las propiedades estén calificadas.
En estas consultas HQL de "modificación en masa" no se puede especificar ningún joins (ni implícito, ni explícito).
Sí se pueden usar subconsultas (subqueries) en la cláusula "where", y estas subconsultas sí pueden contener joins.
A modo de ejemplo: para ejecutar un HQL UPDATE, use el método Query.executeUpdate() (el método se bautizó en
honor al método de JDBC PreparedStatement.executeUpdate()):
String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// o también: String hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
.setString( "newName", newName )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();
Por defecto, los comandos UPDATE, no afectan las propiedades version ni timestamp de las entidades involucradas; esto es
consistente con la especificación de EJB3. Sin embargo, se puede forzar a Hibernate a reinicializar adecuadamente los
valores de las propiedades version y timestamp usando un a "actualización versionada" (versioned update). Esto se
logra agregando la palabra VERSIONED luego de UPDATE keyword.
Note que los tipos de versión a medida (org.hibernate.usertype.UserVersionType) no se permiten en conjunción con
comandos update versioned.
El valor int devuelto por el método Query.executeUpdate() indica el número de entidades afectadas por la operación.
Considere que esto puede corresponder o no con el número de filas afectadas en la base de datos. Una operación HQL en
masa puede resultar en la ejecución de múltiples comandos SQL (para las joined-subclass, por ejemplo). El número
devuelto indica el número real de entidades afactadas por el comando. Volviendo al ejemplo de la joined-subclass, un
comando DELETE ejecutado contra una de las subclases podría resultar en borrados no sólo en la tabla a la cual la
subclase está mapeada, sino también borrados en la tabla "raíz", y potencialmente en otras tablas de joined-subclass más
abajo en la jerarquía de herencias.
Sólo se soporta la forma INSERT INTO ... SELECT ... ; la forma INSERT INTO ... VALUES ... no.
el comando_select puede ser cualquier consulta válida de selección HQL, con la precaución de que los tipos de
retorno deben corresponder con los tipos esperados por el insert. Actualmente, esto es verificado durante la
compilación, en lugar de relegar dicho chequeo a la base de datos. Note, sin embargo, que esto podría causar
problemas entre tipos que Hibernate considere o no "equivalentes" más que "iguales". Por ejemplo, para Hibernate
no son iguales los tipos org.hibernate.type.DateType y org.hibernate.type.TimestampType, aunque la base
de datos no diferencie entre ambos o sea capaz de manejar la conversión entre ambos.
En relación a la propiedad id, el comando de inserción ofrece dos opciones. Se puede o bien especificarla
explícitamente en la lista_de_propiedades (en cuyo caso el valor se toma del comando_select) o se puede omitir de
la lista de propiedades (en cuyo caso se usa un valor generado). Esta última opción está disponible solamente
cuando se usen generadores de id que operen dentro de la base de datos, intentar usarla con generadores del tipo
"residente en memoria" causará una excepción durante el parsing del comando. Para los efectos de esta discusión,
se consideran "generadores residentes en memoria" org.hibernate.id.SequenceGenerator (y sus subclases), y
cualquier implementación de org.hibernate.id.PostInsertIdentifierGenerator.
org.hibernate.id.TableHiLoGenerator tampoco se puede usar, porque no expone una manera facitble de
obtener sus valores en un comando "select".
Para las propiedades mapeadas como version o timestamp, el comando INSERT da dos opciones: se puede o bien
especificarlas explícitamente en la lista_de_propiedades (en cuyo caso el valor se toma del comando_select) o se las
puede puede omitir de la lista de propiedades (en cuyo caso se usa el valor semilla o "seed value" definido por
org.hibernate.type.VersionType).
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c wh
int createdEntities = s.createQuery( hqlInsert ).executeUpdate();
tx.commit();
session.close();
Este manual usa minúsculas para las palabras HQL. A algunos usuarios les parece que las consultas escritas todas en
mayúsula son más legibles, pero a nosotros esta convención nos parece fea cuando está inserta en código Java.
from eg.Cat
from Cat
La mayoría de las veces hará falta asignarle un alias, dado que querremos referirnos a Cat en otras partes de la consulta:
Esta consulta le asigna el alias cat a las instancias de Cat, de manera que podamos usar dicho alias más adelante en la
consulta. La palabra as es optativa, también podríamos escribir:
Se considera una práctica buena el nombrar los alias en las consultas usando una minúscula inicial, en concordancia con
los estándares de nombrado para variables locales en Java. (por ejemplo domesticCat).
Las frases inner join, left outer join and right outer join pueden abreviarse así:
Adicionalmente, un join calificado con la palabra "fetch" (captura) permite que las asociaciones o colecciones de valores
sean inicializados junto con sus objetos padres, usando un solo SELECT. Esto es particularmente útil en el caso de las
colecciones; efectivamente reemplaza los "outer joins" y las inicializaciones haraganas (lazy) del archivo de mapeo para
asociaciones y colecciones. Vea la Sección 19.1, “Estrategias de captura (fetch)” para más información.
A un join del tipo "fetch" normalmente no se le asignan alias, porque los objetos asociados que devuelve no deberían ser
usados en la cláusula "where", ni en ninguna otra cláusula. Además, los objetos asociados no son devueltos directamente
en el resultado de la consulta; en cambio, se puede acceder a ellos a través del objeto padre. La única razón por la que se
podría necesitar un alias, es si se está usando un join tipo "fetch" recursivo a otra colección más.
Note que la construcciones con fetch no deben ser usadas en consultas que luego invoquen iterate() (aunque sí se
puede con scroll()). Tampoco debería usarse fetch con consultas que usen setMaxResults() o setFirstResult()
(los métodos de paginación), dado que dichas operaciones se basan en el número de filas del resultado, el cual
normalmente contendrá duplicados debido a esta "captura ansiosa", y por lo tanto la cantidad de filas no será la que se
espera. Tampoco se debe usar fetch junto con condiciones "with" ad hoc. Al efectuar un join tipo "fetch" con más de una
colección, es posible crear un producto cartesiano, así que tenga cuidado en este caso. Usar joins "fetch" con múltiples
colecciones, además, da a menudo resultados inesperados con los mapeos de "bag", así que tenga cuidado acerca de cómo
formula sus consultas en este caso. Por último, note que las construcciones full join fetch y right join fetch no
tienen sentido.
Si se está usando captura haragana (lazy fetching) a nivel de las propiedades, con instrumentación bytecode, es posible
forzar a Hibernate para que capture esas propiedades haraganas inmediatamente en la primera consulta, usando fetch
all properties.
from Document doc fetch all properties where lower(doc.name) like '%cats%'
Las consultas mostradas en la sección anterior usan todas la forma explícita en donde la palabra "join" es
explícitamente usada en la cláusula "from". Ésta es la forma que se recomienda.
La forma implícita no usa la palabra "join". En lugar de eso, las asociaciones son "des-referidas" (dereferenced) usando
la notación de puntos. Los joins implícitos pueden aparecer en cualquiera de las cláusulas HQL (select, from, where).
Un join implícito se traduce en "inner joins" en el comando SQL resultante.
La propiedad especial id puede ser usada para referirse a la propiedad indentificadora de una entidad siempre y
cuando dicha entidad no haya definido otra propiedad no-identificadora también llamada "id".
Si la entidad define una propiedad indentificadora con nombre, se puede usar dicho nombre.
Las referencias a identificadores compuestos siguen las mismas relgas. Si la entidad tiene una propiedad no-identificadora
llamda "id", la propiedad identificadora compuesta sólo puede ser referida por el nombre asignado. En caso contrario, se
puede usar la propiedad especial id para referirse a la propiedad identificadora.
Nota: esto ha cambiado significativamente a partir de la versión 3.2.2. En versiones anteriores, id siempre hacía alusión a
la propiedad identificadora, sin importar el verdadero nombre. A consecuencia de esto, cuando había propiedades
no-identificadoras llamadas id, las consultas no podían referirse a ellas.
select mate
from Cat as cat
inner join cat.mate as mate
Esta consulta seleccionará los mates (en inglés, "compañeros") de otros Cats. En realidad, se puede expresar esta consulta
de una forma más compacta, como:
Las consultas pueden devolver propiedades de cualquier tipo de valor, incluidos valores de tipo "componente":
Esto es de lo más util cuando se usa en conjunción con select new map:
count(*)
En la cláusula "select" se pueden usar operadores aritméticos, concatenación, y funciones SQL reconocidas:
Las palabras distinct y all pueden ser usadas, y con la misma semántica que en SQL.
devuelve no sólo instancias de Cat, sino también de las subclases como DomesticCat. Las consultas de Hibernate pueden
nombrar cualquier clase o interfaz Java en la cláusula from. La consulta devolverá las instancias de todas las clases
persistentes que extiendan o implementen dicha clase o interfaz. La siguiente consuta devuelve todos los objetos
persistentes:
from java.lang.Object o
Note que las dos últimas consultas requerirán más de dos comandos SQL SELECT. Esto implica que un cláusula order by
no ordenaría correctamente la totalidad del conjunto de resultados, y que no se puede usar Query.scroll() para
navegarlos.
select foo
from Foo foo, Bar bar
where foo.startDate = bar.date
devolverá las instancias de Foo para las cuales exista una instancia de Bar cuya propiedad date sea igual a la propiedad
startDate de Foo. Las expresiones con "path" compuesto hacen que la cláusula "where" sea extremadamente poderosa.
Considere:
Esta consulta se traduciría en un comando SQL con varios (inner) joins. Si se escribiera algo como esto:
El operador = puede usarse para comparar no sólo propiedades, sino también instancias:
La propiedad id (en minúscula), puede usarse para hacer referencia al identificador único de un objeto. Véase la
Sección 14.5, “Referirse a la propiedad identificadora” para más información.
También se pueden usar propiedades de los identificadores compuestos. Supongamos que Person tiviera un identificador
compuesto que consistiese en country y medicareNumber. (de nuevo, véase la Sección 14.5, “Referirse a la propiedad
identificadora” para más información acerca de referirse a las propiedades identificadoras):
Del mismo modo, la propiedad especial class accede al valor discriminador de una instancia, en caso de que se esté
usando persistencia polimórfica. Un nombre de clase de Java incrustado en la cláusula "where" será traducido como su
valor de discriminador.
Tambiém se pueden usar componentes, o tipos a medida compuestos, o las propiedades de dichos tipos de
componentes/tipos. Vea la Sección 14.17, “Componentes” para más información.
Un tipo "any" tiene las propiedades especiales id y class, que permiten expresar un join de la siguiente manera (en donde
AuditLog.item es una propiedad mapeada con <any>):
Note que, en la consulta precedente, log.item.class y payment.class se estarían refiriendo a valores de columnas
completamente diferentes de la base de datos.
14.10. Expresiones
Las expresiones que se permiten en la cláusula where incluyen la mayoría de las que se podría escribir en SQL:
operadores matemáticos +, -, *, /
in, not in, between, is null, is not null, is empty, is not empty, member of and not member of
la forma de "case" simple, "Simple" case, case ... when ... then ... else ... end, y la forma de "case"
llamada "searched", case when ... then ... else ... end
Cualquier función u operador definido por EJB-QL 3.0: substring(), trim(), lower(), upper(), length(),
locate(), abs(), sqrt(), bit_length(), mod()
coalesce() y nullif()
cast(... as ...), en donde el segundo argumento es el nombre de un tipo de Hibernate, y extract(... from
...) si la base de datos subyacente soporta las funciones ANSI cast() y extract().
la función HQL index(), que se aplica a los alias de una coleción asociada indexada.
funciones HQL que aceptan expresiones tipo "path" con valor de colección: size(), minelement(),
maxelement(), minindex(), maxindex(), junto con las funciones especiales elements() e indices, las cuales
pueden ser cuantificadas usando some, all, exists, any, in.
cualqiuer función escalar SQL soportada por la base de datos, como sign(), trunc(), rtrim(), sin()
from DomesticCat cat where cat.name not between 'A' and 'B'
Del mismo modo. is null y is not null pueden ser usadas para chequear valores nulos.
Se puede usar fácilmente valores booleanos, declarando sustituciones de consulta HQL en la configuración de Hibernate:
Esto reemplazará las palabras true y false con los valores literales 1 y 0 en el SQL traducido desde este HQL.
Se puede chequear el tamaño de la colección con la propiedad especial size, o con la función especial size().
Para las colecciones indexadas, es preferible referirse a los valores mínimos y máximos usando las funciones minindex y
maxindex. Análogamente, se puede aludir a los elementos mínimo y máximo de una colección de un tipo básico utilizando
las funciones minelement y maxelement.
Las funciones SQL any, some, all, exists, in se soportan cuando se les pasa como parámetro el conjunto de
elementos o índices de una colección (éstas son las funciones elements e indices), o el resultado de una subconsulta
(véase abajo).
Note que las construcciones: size, elements, indices, minindex, maxindex, minelement, maxelement sólo pueden ser
usadas en la cláusula "where" en Hibernate3
Uno se puede referir a los elementos en las colecciones indexadas (arrays, lists, maps) por índice (sólo en la clausula
"where").
HQL también trae una función index() ya incorporada, para elementos de una asociación de-uno-a-muchos, o una
colección de valores.
Pueden ser usadas las funciones SQL escalares que la DB subyacente soporte:
Si todo esto aún no lo ha convencido, piense cuánto más largo y menos legible habría sido esta consulta en SQL:
select cust
from Product prod,
Store store
inner join store.customers cust
where prod.name = 'widget'
and store.location.name in ( 'Melbourne', 'Sydney' )
and prod = all elements(cust.currentOrder.lineItems)
Las palabras opcionales asc o desc indican orden ascendente o descendente respectivamente.
si la base de datos subyacente lo soporta (por ejemplo, MySQL no), dentro de having se permiten funciones SQL, y
dentro del order by se permiten funciones agregadas.
select cat
from Cat cat
join cat.kittens kitten
group by cat.id, cat.name, cat.other, cat.properties
having avg(kitten.weight) > 100
order by count(kitten) asc, sum(kitten.weight) desc
Note que ni la cláusula group by ni la order by pueden contener expresiones aritméticas. Y también que Hibernate no
"expande" una entidad agrupada: así que no se puede escribir group by cat si todas las propiedades de cat son
no-agregadas; hay que listar todas las propiedades no-agregadas explícitamente.
14.13. Subconsultas
Para las base de datos que soporten subconsultas (subqueries), Hibernate soporta subconsultas dentro de una consulta.
Una subconsulta debe estar rodeada por paréntesis (a menudo por un llamado a una función SQL agregada). Incluso se
permiten subconsultas correlacionadas (subconsultas que hagan referencia a un alias en la consulta exterior.
Note que las subconsultas HQL pueden ocurrir sólo en las cláusulas "select" o "where".
Note que las subconsultas también pueden utilizar sintaxis de constructor del valor de fila (row value constructor).
Vea la Sección 14.18, “Constructor del valor de fila” para más detalles.
La siguiente consulta devuelve el id de la orden, el número de items y el valor total de todas las órdenes impagas por un
cliente en particular, y dado un valor total mínimo, ordenando los resultados por valor total. Para determinar los precios,
usa el catálogo actual. El SQL resultante, efectuado contra las tablas ORDER, ORDER_LINE, PRODUCT, CATALOG y PRICE
tiene 4 "inner joins" y una subconsulta (no correlacionada).
¡Qué monstruo! En realidad, en la vida real no soy muy amigo de las subconsultas, así que mi consulta quedó más bien de
esta manera:
La consulta siguiente cuenta el número de pagos en cada estado, exceptuando los pagos que figuren como "aprobación
pendiente" (AWAITING_APPROVAL) en donde el cambio de estado más reciente haya sido efectuado por el usuario actual.
Se traduce en una consulta SQL con 2 inner joins y una subconsulta correlacionada, contra las clases PAYMENT,
PAYMENT_STATUS y PAYMENT_STATUS_CHANGE.
Si yo hubiera mapeado la colección statusChanges collection como una lista, la consulta habría sido mucho más fácil de
escribir.
La siguiente consulta usa la función isNull() de MS SQL Server para devolver todas las cuentas y pagos no efectuados a
la organización a la cual pertenece el usuario actual. Se traduce en un SQL con tres inner joins, un outer join y un
subselect contra las tablas the ACCOUNT, PAYMENT, PAYMENT_STATUS, ACCOUNT_TYPE, ORGANIZATION y ORG_USER.
Para ordenar un resultado de acuerdo al tamaño de las colecciones de hijos, use la consulta siguiente:
Si su base de datos soporta subselects, se puede poner una condición en cuanto al tamaño de la selección, en la cláusula
"where" de su consulta.
Como esta solución no puede devolver a un User que tenga 0 mensajes a causa del inner join, la siguiente forma también
es útil:
Las colecciones se pueden volver paginables, usando la interfaz Query más un filtro:
Los elementos de las colecciones pueden ser ordenados o agrupados usando un filtro de consultas:
14.17. Componentes
En HQL, se pueden usar componentes, casi en cualquiera de las mismas formas en que se puede usar "value types"
simples. Pueden aparecer en la cláusula "select":
en donde la propiedad "name" de Person es un componente. Los componentes también pueden ser usados en cláusulas
"where":
Esta sintaxis es válida, aunque un tanto locuaz. Sería bueno poder hacerla un poco más concisa y usar la sintaxis de
"constructor de valor de fila":
Otro momento en que la sintaxis de constructor de valor de fila puede ser beneficiosa, es cuando se usen
subconsultas que necesiten comparar contra valores múltiples:
Una cosa a considerar, al decidir si se quiere usar esta sintaxis, es que la consulta será dependiente del orden de las
sub-propiedades del componente en los metadatos.
(N.del.T):en inglés, "criterio" se dice "criterion", y en plural, es "criteria". La interfaz, muy usada, de Hibernate que
denomina a este tipo de consultas, se llama "Criteria". Una interfaz mucho menos conocida, "Criterion", en un paquete
del mismo nombre, casi nunca son usados directamente por el programador, porque le son agregados a un "Criteria"
ente bambalinas.
Hay un buen rango de tipos de "criterion" que ya vienen incluidos (subclases de Restrictions), pero uno en especial
permite especificar SQL directamente:
Un camino alternativo para obtener un "criterion" es obtenerlo a partir de una instancia de Property. Se puede crear una
Property invocando Property.forName().
15.4. Asociaciones
Se puede especificar constraints en entidades relacionadas, navegando a través de asociaciones usando
createCriteria().
note que el segundo createCriteria() devuelve una instancia nueva de Criteria, la cual se refiere a los elementos de
la colección kittens.
Note que las colecciones de "kittens" (gatitos) contenidas en las instancias de Cat devueltas por las dos consultas
anteriores, ¡no están pre-filtradas por el Criteria! Si desea obrener sólo los "kittens" que cumplen con el Criteria, debe usar
un ResultTransformer.
.createCriteria("kittens", "kt")
.add( Restrictions.eq("name", "F%") )
.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
.list();
Iterator iter = cats.iterator();
while ( iter.hasNext() ) {
Map map = (Map) iter.next();
Cat cat = (Cat) map.get(Criteria.ROOT_ALIAS);
Cat kitten = (Cat) map.get("kt");
}
Esta consulta capturará tanto mate como kittens mediante un "outer join". Vea la Sección 19.1, “Estrategias de captura
(fetch)” para más información.
Las propiedades versión, identificadores y asociaciones son ignoradas. Por defecto, las propiedades con valor nulo son
excluidas.
En una consulta de tipo Criteria, no es necesario que haya explícitamente "group by"s. Algunos tipos de proyección son
las denominadas proyecciones agrupadoras (grouping projections) las cuales aparecen en el SQL como cláusulas group
by.
Optativamente se le puede asignar un alias a la proyección, de manera que las Restrictions y los Orders se puedan referir a
él. He aquí dos maneras de hacer esto:
Los métodos alias() y as() simplemente envuelven una instancia de Projection dentro de otra instancia de
Projection con alias. Como atajo, se puede asignar un alias al agregar la proyección a una lista de proyecciones:
La clase DetachedCriteria permite crear una consulta por fuera del alcance de una sesión, y más tarde ejecutarla usando
alguna sesión arbitraria.
Una DetachedCriteria también puede usarse para expresar una subconsulta. Instancias de Criterion que involucren
subconsultas pueden ser obtenidas via subconsultas o Property.
Primero se debe mapear la clave natural de su entidad usando <natural-id>, y habilitando el caché de 2do nivel.
<class name="User">
<cache usage="read-write"/>
<id name="id">
<generator class="increment"/>
</id>
<natural-id>
<property name="name"/>
<property name="org"/>
</natural-id>
<property name="password"/>
</class>
Note que esta funcionalidad no ha sido concebida para usarse con entidades de clave natural mutable.
Finalmente, Restrictions.naturalId() permite hacer un uso más eficiente del algoritmo de caché.
session.createCriteria(User.class)
.add( Restrictions.naturalId()
.set("name", "gavin")
.set("org", "hb")
).setCacheable(true)
.uniqueResult();
Hibernate3 permite especificar SQL escrito a mano (incluyendo procedimientos almacenados o "stored procedures") para
todas las operaciones de creación, modificación, borrado y carga.
La consulta SQL más básica consiste en obtener una lista de valores escalares.
Estas dos consultas devolverán una List de arrays de Objects (Object[]) con valores escalares para cada columna de la
tabla CATS. Hibernate usará ResultSetMetadata para deducir el orden y tipo de los valores escalares devueltos.
Para evitar el gasto extra de llamar ResultSetMetadata, o simplemente para se más explícito acerca de qué se devuelve,
se puede usar addScalar().
Esto aún devolverá un array de Objetcs, pero ahora no usará ResultSetMetadata sino que obtendrá explícitamente las
columnas ID, NAME and BIRTHDATE como un Long, una String y un Short, respectivamente, a partir del resultado
subyacente. Esto tambíen significa que sólo serán devueltas esas 3 columnas, incluso cuando la consulta esté usando * y
pueda devolver más columnas que las 3 listadas.
Ésta es esencialmente la misma consulta que antes, pero ahora se usa ResultSetMetaData par decidir el tipo de NAME Y
BIRTHDATE, mientras que el tipo de ID se especifica explícitamente.
Cómo los java.sql.Types devueltos del ResultSetMetaData se mapean a Hibernate, es controlado por el dialecto. Si un tipo
en particular no está mapeado o no se resuelve en el tipo esperado, es posible modificarlo a medida, usando llamados a
registerHibernateType en el dialecto.
Las consultas precedentes consistían todas en devolver valores escalares, básicamente devolviendo los valores "crudos"
desede el resultset. A continuación se muestra cómo obtener objetos de entidad desde una consulta nativa, a través de
addEntity().
Suponiendo que Cat esté mapeado como clase, con las columnas ID, NAME y BIRTHDATE, las 2 consultas precedentes
devolverán una List en la cual cada elemento es una entidad Cat.
Si la entidad está mapeada a otra entidad con una asociación many-to-one (de-muchos-a-uno), es necesario devolver
también eso al efectuar la consulta nativa, de otro modo ocurrirá un error específico de base de datos del tipo "columna no
encontrada". Las columnas adicionales serán devueltas automáticamente cuando se use la notación *, pero se prefiere la
forma explícita, como en el siguiente ejemplo en donde tenemos una relación 'de-muchos-a-uno' con un Dog:
Es posible asociar Dog Dog en forma "ansiosa" a fin de evitar el posible viaje extra ida y vuelta a la base de datos para
inicializar el proxy. Esto se hace usando el método addJoin(), el cual permite efectuar el vínculo con asociaciones o
colecciones.
sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c
.addEntity("cat", Cat.class)
.addJoin("cat.dog");
En este ejemplo, los Cats devueltos tendrán su propiedad dog enteramente inicializada, sin necesitad de un viaje adicional
de ida y vuelta a la BD. Note que hemos agregado un nombre de alias ("cat") para poder especificar la propiedad de
destino del join. Es posible hacer este mismo tipo de join "ávido" para colecciones, por ejemplo, si el Cat tuviera varios
Dogs asociados de-uno-a-muchos.
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE c.I
.addEntity("cat", Cat.class)
.addJoin("cat.dogs");
Llegados a este punto, casi hemos alcanzado el límite de lo que se puede hacer con consultas nativas, antes de empezar a
mejorar su SQL para que sean usables en Hibernate. El problema comienza cuando se devuelven múltiples entidades del
mismo tipo, o cuando los alias/nombres de columna empleados por defecto no son suficientes.
En los ejemplos precedentes, se asume que los nombres de columna del resultado son los mismos que los nombres de
columna especificados en el documento de mapeo. Esto puede ser problematico cuando las consultas SQL tiene "joins"
entre varias tablas, dado que pueden aparecer los mismos nombres de columna en más de una tabla.
En el ejemplo siguiente (que muy probablemente fallará), se necesita inyectar el alias de columna:
Lo que se intenta es que esta consulta devuelva dos instancias de Cat por fila: un gato y su madre. Esto fallará, dado que
hay un conflicto de nombres, porque están mapeados a los mismos nombres de columna, y, en algunas bases de datos, los
alias de columna devueltos muy probablemente tendrán la forma "c.ID", "c.NAME", etc., lo cual no coincide con las
la cadena de la consulta SQL, con parámetros de sustitución (placeholders) para que hibernate inyecte los alias de
columna
La notación con {cat.*} y {mother.*} usada anteriormente es taquigrafía por "todas las propiedades". Alternativamente,
se puede listar las columnas explícitamente, pero incluso en ese caso dejamos que Hibernate inyecte los alias de columna
SQL para cada propiedad. El parámetro de sustitución para un alias de columna es simplemente el nombre de la propiedad
calificado por el alias de la tabla. En el ejemplo siguiente, obtenemos los Cats (gatos) y sus madres desde una tabla
diferente (cat_log) a la declarada en los metadatos de mapeo. Note que incluso se puede usar los alias de propiedad en la
cláusula "where" si se quiere.
Par la mayoría de los casos, inyección de alias es todo lo que se necesita. Pero para consultas relacionadas con mapeos
más complejos, como discriminadores de herencia o propiedades compuestas, hay algunos alias específicos que Hibernate
debe usar para preservar el correcto funcionamiento de la inyección de alias.
La tabla siguiente muestra las diferentes posibilidades al usar inyección de alias. Nota: los nombres de alias en el resultado
son ejemplos, cada alias tendrá un nombre único y probablemente diferente cuando se use.
El discriminador de una
{[aliasname].class} DISC as {item.class}
entidad
Todas las propiedades de
{[aliasname].*} {item.*}
una entidad
Una clave de colección {[aliasname].key} ORGID as {coll.key}
El elemento de una
{[aliasname].element} XID as {coll.element}
colección
Una propiedad de elemento {[aliasname].element.
NAME as {coll.element.name}
en la colección [propertyname]}
Es posible aplicarles un ResultTransformer a las consultas SQL nativas, permitiendo, por ejemplo, devolver entidades
no-manejadas.
La consulta especificó:
La consulta precedente devolverá una lista de CatDTO, la cual habrá sido instanciada y habrá inyectado los valores de
NAME y BIRTHNAME en las propiedades o campos correspondientes.
Las consultas de SQL nativas que consulten entidades que estén mapeadas como parte de una herencia, deben incluir
todas las propiedades para la clase base y las subclases.
16.1.7. Parámetros
Las consultas SQL nativas soportan parámetros tanto nombrados como posicionales:
<sql-query name="persons">
<return alias="person" class="eg.Person"/>
Los elementos <return-join> y <load-collection> se usan, respectivamente, para asociaciones de tipo "join", y para
definir consultas que inicialicen colecciones.
<sql-query name="personsWith">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
Una consulta SQL nombrada puede devolver un valor escalar. Hya que declarar el alias de la columna y el tipo de
Hibernate usando el elemento <return-scalar> element:
<sql-query name="mySqlQuery">
<return-scalar column="name" type="string"/>
<return-scalar column="age" type="long"/>
Se puede externalizar el resultado, mapeando información en un elemento <resultset> para reutilizarlo, ya sea en otras
consultas nombradas, o a través de la API setResultSetMapping().
<resultset name="personAddress">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
</resultset>
Alternativamente, se puede usar la información de mapeo del resultset contenida en los archivos hbm, directamente en el
código Java.
Con <return-property> se le puede deicr a Hibernate explícitamente qué alias de columna usar, en lugar de usar la
sintaxis {} para dejar que Hibernate inyecte sus propios alias.
<sql-query name="mySqlQuery">
person.AGE AS myAge,
person.SEX AS mySex,
FROM PERSON person WHERE person.NAME LIKE :name
</sql-query>
<return-property> también funciona con múltiples columnas. Esto soluciona la limitación de la sintaxis {}, la cual no
permite un control tan minucioso de las propiedades multicolumna.
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment">
<return-property name="salary">
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="endDate" column="myEndDate"/>
</return>
</sql-query>
Note que en este ejemplo utilizamos <return-property> en combinación con la sintaxis {} para inyectar, permitiéndole
al usuario decidir cómo prefiere referirse a columnas y propiedades.
Si su mapeo tiene un discriminador, debe usarse <return-discriminator> para especificar la columna discriminadora.
Hibernate 3 introduce soporte para consultas vía procedimientos almacenados (stored procedures) y funciones. La
mayoría de la documentación que sigue es equivalente para ambos. El procedimiento/función debe devolver un conjunto
resultado o "resultset" como primer parámetro de salida, a fin de poder trabajar con Hibernate. A continuacíón, un ejemplo
de función en Oracle 9 o superior:
Para usar esta consulta en Hibernate, hay que mapearla con una consulta "nombrada".
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
</return>
{ ? = call selectAllEmployments() }
</sql-query>
Note que los procedimientos almacenados actualmente sólo devuelven escalares o entidades. <return-join> y <load-
collection> no se soportan.
Para usar procedimientos almacenados con Hibernate, el procedimiento o función debe seguir ciertas reglas. Si no sigue
esas reglas, es incompatible con Hibernate. Si aún se desea usar estos procedimientos incompatibles, hay que ejecutarlos
via session.connection(). Las reglas son diferentes para cada base de datos, dado que cada proveedor de base de datos
tiene una semántica y una sintaxis distinta pars los procedimientos almacenados.
Las funciones deben devolver un resultado "resultset". El primer parámetro de los procedimientos almacenados
debe ser un parámeto de tipo "OUT" que devuelva un resultset. Esto se logra usando un tipo SYS_REFCURSOR en
Oracle 9 o 10. En Oracle hay que definir un tipo REF CURSOR, consulte la literatura de Oracle.
El procedimiento debe devolver un resultset. Note que, como estos servidores son capaces de devolver múltiples
resultsets y contadores de actualización, Hibernate recorrerá los resultados y tomará el primer resultado que sea
resultset. El resto se descarta.
Si usted puede habilitar SET NOCOUNT ON en su procedimiento, seguramente éste será más eficiente, pero esto no es
obligatorio.
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
</class>
El SQL es ejecutado directamente en su base de datos, así que usted es libre de utilizar cualquier dialecto que desee. Por
supuesto, usar SQL específico de una base de datos reducirá la "portabilidad" de su aplicación.
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
El orden de los parámetros posicionales es ahora esencial, dado que deben estar en la misma secuencia en que Hibernate
los espera.
Se puede ver el orden esperado, habilitando logueo a nivel "debug" en org.hibernate.persister.entity. Con esta
categoría habilitada,, Hibernate imprimirá el SQL estático que se usa para crear entidades, actualizarlas, borrarlas, etc.
(Para ver la secuencia esperada, recuerde no incluir su SQL a medida en los archivos de mapeo, dado que éste suplantará
al SQL estático generado por Hibernate).
En la mayoría de los casos, es obligatorio (o más bien, fuertemente recomendado) que los procedimientos almacenados
devuelvan el número de filas insertadas/actualizadas/borradas, porque Hibernate realiza algunos chequeos en tiempo de
ejecución para verificar el éxito del comando. Hibernate siempre registra el primer parámetro del "statement" como de
salida y numérico, para las operaciones de ABM (alta-baja-modificación):
update PERSON
set
NAME = uname,
where
ID = uid;
return SQL%ROWCOUNT;
END updatePerson;
<sql-query name="person">
<return alias="pers" class="Person" lock-mode="upgrade"/>
SELECT NAME AS {pers.name}, ID AS {pers.id}
FROM PERSON
WHERE ID=?
FOR UPDATE
</sql-query>
Esta es, simplemente, una declaración de una consulta nombrada (named query), como se discutió anteriormente. Se
puede hacer referencia a esta consulta nombrada en el mapeo de clases:
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<one-to-many class="Employment"/>
<loader query-ref="employments"/>
</set>
<sql-query name="employments">
<load-collection alias="emp" role="Person.employments"/>
SELECT {emp.*}
FROM EMPLOYMENT emp
WHERE EMPLOYER = :id
ORDER BY STARTDATE ASC, EMPLOYEE ASC
</sql-query>
Hasta se puede definir un "cargador de entidades" que cargue una coleccíón mediante captura con "join":
<sql-query name="person">
Para usar filtros, primero tienen que ser definidos y adjuntados al elemento de mapeo correspondiente. Para definir un
filtro, use el elemento <filter-def/> dentro de un elemento de <hibernate-mapping/>:
<filter-def name="myFilter">
<filter-param name="myFilterParam" type="string"/>
</filter-def>
o a una colección:
<set ...>
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
</set>
session.enableFilter("myFilter").setParameter("myFilterParam", "some-value");
Note que hay métodos en la interfaz org.hibernate.Filter que permiten el encadenamiento de filtros, muy común en
Hibernate.
Un ejemplo completo, usando datos con el patrón de programación conocido como "fecha efectiva" (effective date):
<filter-def name="effectiveDate">
<filter-param name="asOfDate" type="date"/>
</filter-def>
...
<many-to-one name="department" column="dept_id" class="Department"/>
<property name="effectiveStartDate" type="date" column="eff_start_dt"/>
<property name="effectiveEndDate" type="date" column="eff_end_dt"/>
...
<!--
Note que esto asume que los registros no-terminales tinenen una fecha de finalización efect
a la cual se le asignó el máximo valor de fecha posible en la BD, para simplificar.
-->
<filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
</class>
</class>
Luego, para asegurarse de que siempre se obtiene el registro más actual, simplemente habilite el filtro en la sesión, antes de
capturar datos de empleados:
En el HQL precedente, incluso si sólo se mencionó una condición relativa al salario en los resultados, la consulta
devolverá los empleados que con un salario mayor a un millón y, a causa del filtro habilitado, que estén actualmente
activos.
Nota: si se planea usar filtros con "outer joins" (sea a través de HQL o de estrategias de "fetch") tenga cuidado con la
dirección de la expresión de condición. Lo más seguro es configurarlo como "left outer join"; en general, ponga el
parámetro primero, seguido por el nombre o nombres de columna luego del operador.
Luego de haber sido definido, un filtro puede ser adosado a entidades y/o colecciones, cada una con su propia condición.
Eso puede ser tedioso cuando las condiciones son las mismas en todos los casos. Por esto, <filter-def/> permite definir
una condición por defecto, sea como un atributo, o como CDATA:
Entonces, esta condición por defecto será usada siempre que el filtro se adjunte a algo sin haber especificado una
condición. Note que esto significa que se puede dar una condición específica como parte de la adjunción del filtro, que
suplanta a la condición por defecto para ese caso en particular.
Hibernate soporta la API dom4j para manipular los árboles XML. Se puede escribir consultas que obtengan árboles dom4j
desde la base de datos, y lograr que cualquier modificación practicada al XML se sincronice automáticamente a la base de
datos. Incluso se puede tomar un documento XML, analizarlo usando dom4j, y escribirlo en la base de datos con
cualquiera de las operaciones básicas de Hibernate: persist(), saveOrUpdate(), merge(), delete(),
replicate() (merge aún no se soporta).
Esta habilidad tiene muchos usos, incluyendo: importación/exportación de datos, externalización de entidades via JMS o
SOAP, y producción de reportes basada en XSLT.
Un solo mapeo puede ser usado para mapear simultáneamente las propiedades de una clase y los nodos de un documento
XML a la base de datos, o, si no hay ninguna clase para mapear, sólo el documento XML.
...
</class>
Este mapeo permite acceder a los datos como si fueran un árbol dom4j, o una representación en forma de pares
nombre/valor (Maps de Java). Los nombres de las propiedades son construcciones puramente lógicas, a las que se puede
hacer referencia en consultas HQL.
Para asociaciones y asociaciones a valores simples, hay un atributo embed-xml adicional. Si se especifica embed-
xml="true", lo cua es el valor por defecto, el árbol XML para la entidad asociada (la de tipo colección o "value type")
será incrustada directamente en el árbol XML de la entidad que posee la asociación. En caso contrario, si embed-
xml="false", entonces sólo el valor del identificador al que se hace referencia aparecerá en el XML (para asociaciones
Debería tenerse cuidado de no asignarles embed-xml="true" a demasiadas asociaciones, ¡dado que XML no sabe lidiar
muy bien con recursividad!
</map>
...
</class>
en este caso, hemos decidido incrustar una colección de ids de cuentas (accounts), pero no los datos de las cuentas
propiamente dichas. La siguiente consulta HQL:
from Customer c left join fetch c.accounts where c.lastName like :lastName
<customer id="123456789">
<account short-desc="Savings">987632567</account>
<account short-desc="Credit Card">985612323</account>
<name>
<first-name>Gavin</first-name>
<initial>A</initial>
<last-name>King</last-name>
</name>
...
</customer>
<customer id="123456789">
<name>
<first-name>Gavin</first-name>
<initial>A</initial>
<last-name>King</last-name>
</name>
...
</customer>
Vamos a leer y actualizar documentos XML en la aplicación. Lo hacemos obteniendo una sesión dom4j:
tx.commit();
session.close();
tx.commit();
session.close();
Combinar esta funcionalidad con la operación replicate() es extremadamente útil para implementar la
importación/exportación de datos basados en XML:
captura por Join Hibernate obtiene la instancia o colección asociada, agregándole un OUTER JOIN al SELECT
mismo.
captura por Select: se usa un segundo SELECT para obtener la entidad o colección asociada. A menos que se
inhabilite la captura haragana (lazy fetching) especificando lazy="false", este segundo SELECT sólo se ejecutará
cuando realmente se acceda a la asociación.
captura poe Subselect: se usa un segundo SELECT para obtener todas las entidades o colecciones asociadas, que se
habían obtenido en el SELECT anterior. A menos que se inhabilite la captura haragana (lazy fetching) especificando
lazy="false", este segundo SELECT sólo se ejecutará cuando realmente se acceda a la asociación.
captura por lotes (batch fetching): es una estrategia de optimización de la captura por Select; Hibernate obtiene no
una, sino un lote (batch) de instancias de entidad o colección en un solo SELECT, especificando no una, sino una
lista de claves primarias o foráneas.
captura inmediata: la asociación. colección o atributo son capturados inmediatamente, cuando el dueño es cargado.
captura haragana de colecciones: una colección es capturada sólo cuando la aplicacíón invoca alguna operación en
esa colección (éste es el comportamiento por defecto para las colecciones).
captura por Proxy: una asociación de un solo valor se captura cuando se invoca sobre éste cualquier otro método
que no sea su getter/setter.
captura sin proxy: una asociación de un solo valor se captura cuando se accede a la variable de instancia . En
comparación con la captura por proxy, este abordaje es menos haragán (se accede a la asociación incluso si sólo se
usa el identificador), pero más transparente, dado que no hay ningún proxy visible para la aplicacíón. Este abordaje
requiere instrumentación bytecode de tiempo de compilación, y sólo raramente es necesario.
captura haragana de atributos: un atributo o asociación de valor simple, se capturan cuando se accede a la
instancia o asociación. Este abordaje requiere instrumentación bytecode de tiempo de compilación, y sólo raramente
es necesario.
Aquí tenemos dos nociones ortogonales: cuándo la asociación se captura, y cómo se captura (qué SQL se usa). ¡A no
confundir una con la otra! Se usa la palabra fetch para ajustar la eprformance. Se puede usar la palabra lazy para definir
un contrato sobre qué estará disponible y qué estará desprendido para una clase en particular.
Hibernate3 usa por defecto "select haragán" para las colecciones, y "captura haragana por proxy" para las asociaciones de
valor simple. Estos valores por defecto tienen sentido casi siempre, y para casi todas las asociaciones.
De todos modos, la captura haragana plantea un problema del cual hay que ser consciente: acceder a asociaciones
haraganas por fuera del contexto de una sesión abierta de Hibernate, resultará en una excepción. Por ejemplo.
s = sessions.openSession();
Transaction tx = s.beginTransaction();
tx.commit();
s.close();
Como la colección "permissions" no estaba inicializada cuando la sesión fue cerrada, la colección será incapaz de cargar
su estado. Hibernate no soporta la inicialización haragana de objetos desprendidos. La solución es mover el código que
lee de la colección a justo antes de que la transacción invoque "commit".
Alternativamente, podríamos usar una colección o asociación no haragana, especificando lazy="false" para el mapeo de
la asociación. De todos modos, se espera que se use inicialización haragana en casi todos los casos. Si se definen
demasiadas asociaciones no haraganas, ¡Hibernate terminará cargando toda la base de datos en memoria para cada
transacción!
Por otra parte, a menudo querremos elegir captura por join (la cual que es naturalmente no haragana) en lugar de captura
por select, para una transacción en particular. Ahora veremos cómo diseñar una estrategia de captura a medida. En
Hibernate3, los mecanismos para elegir una estrategia de captura son idénticos para las asociaciones de valor simple y para
las colecciones.
La captura por select (el comportamiento por defecto) es extremadamente vulnerable a los problemas de N+1 SELECTS.
Así que tal vez querramos habilitar la captura por join en el documento de mapeo.
Sin importar qué estrategia de captura se use, se garantiza que el árbol "no haragán" definido se cargará en memoria. Note
que esto puede resultar en que se usen varios SELECTs inmediatamente para ejecutar una consulta HQL en particular.
Normalmente no usaremos el documento de mapeo para ajustar el tipo de captura. En cambio, mentendremos el
comportamiento por defecto, y lo sustituiremos para una transacción en particular, usando left join fetch en HQL.
Esto le dice a Hibernate que capture la asociacíón en forma ansiosa, en el primer SELECT, usando un outer join. En la
API de consultas Criteria, se usaría setFetchMode(FetchMode.JOIN).
Si se deseare cambiar la estrategia de captura usada por get() or load(), simplemente habría que usar una consulta
Criteria, por ejemplo:
(Éste es el equivalente en Hibernate de lo que algunas soluciones ORM llaman un plan de captura o "fetch plan").
Una forma completamente distinta de evitar los problemas con N+1 selects es usar el caché de 2do nivel.
La captura haragana para colecciones se implementa usando la implementación para colecciones persistentes de Hibernate
mismo. Sin embargo, se necesita un mecanismo diferente para el comportamiento haragán en las asociaciones de extremo
simple. A la entidad blanco de la asociación se le debe crear un "proxy". Hibernate implementa los proxies de
inicialización haragana para objetos persistentes usando "mejora de bytecode en tiempo de ejecución" (a través de la
excelente biblioteca CGLIB).
Hibernate3 genera proxies por defecto (durante el arranque) para todas las clases persistentes, y los usa para habilitar la
captura haragana de asociaciones 'de-muchos-a-uno' (many-to-one) y de-uno-a-uno (one-to-one).
El archivo de mapeo puede declarar una interfaz para ser usada como la "interfaz de proxy" para una clase determinada,
con el atributo proxy. Hibernate usa por defecto una subclase de la clase. Tenga en cuenta que la clase a la cual se le
crea el proxy debe implementar un constructor por defecto (sin parámetros), con visibilidad por defecto o "package"
por lo menos. ¡Recomendamos este constructor para todas las clases persistentes!
Hay algunas dificultades muy comunes, de las cuales conviene estar al tanto a la hora de extender este enfoque a clases
polimórficas; por ejemplo:
Primero que nada, las instancias de la clase Cat jamás podrán ser convertidas con "cast" a DomesticCat, incluso cuando
la instancia subyacente sí es una instancia de DomesticCat:
Cat cat = (Cat) session.load(Cat.class, id); // instancia un proxy (no llama a la base de datos)
if ( cat.isDomesticCat() ) { // llama a la base de datos para inicializar el proxy
DomesticCat dc = (DomesticCat) cat; // ¡Error!
....
}
Sin embargo, esta situación no es tan mala como parece. Incluso si ahora tenemos dos referencias a objetos "proxy"
diferentes, la instancia subyacente aún es el mismo objeto:
En tercer lugar, no se puede usar un proxy CGLIB para clases final ni para clases que tengan métodos final.
Por último, si su clase persistente adquiere recursos durante la inicialización (por ejemplo, en inicializadores o
constructores por defecto), entonces dichos recursos serán también adquiridos por el proxy. La clase proxy es una
verdadera subclase de la clase persistente.
Estos problemas se deben todos a limitaciones fundamentales del modelo de herencia única de Java. Si usted quiere evitar
estos problemas, cada una de sus clases persistentes debe implementar una interfaz que declare sus métodos de negocio. Y
usted debe especificar estas interfaces en el documento de mapeo. Por ejemplo:
en donde CatImpl implementa la interfaz Cat y DomesticCatImpl implementa la interfaz DomesticCat. Así, los proxies
para instancias de Cat y DomesticCat pueden ser devueltos por load() o por iterate(). (Note que list()
normalmente no devuelve proxies.)
Las relaciones también son incializadas en forma haragana. Esto significa que toda propiedad debe ser declarada como de
tipo Cat, no CatImpl.
Hibernate detectará las clases persistentes que sustituyan (override) equals() o hashCode().
Eligiendo lazy="no-proxy" en lugar del lazy="proxy" que viene por defecto, podemos evitar los problemas asociados
con la conversión ("cast") entre tipos. Sin embargo, necesitaremos instrumentación de bytecode en tiempo de compilación,
y todas las operaciones resultarán en una inicialización de proxy inmediata.
Si se trata de acceder a una colección o proxy no inicializados fuera del alcance de una sesión, Hibernate emitirá una
LazyInitializationException. Es decir, cuando la entidad que posea la colección o que tienga una referencia al proxy
esté en un estado desprendido.
A veces necesitamos asegurarnos de que un proxy o colección sean inicializados antes de cerrar la sesión. Por supuesto,
siempre podemos forzar la inicialización invocando cat.getSex() o cat.getKittens().size(), por ejemplo. Pero esto
sería confuso para quien leyere el código, e inconveniente para cualquier código que intentare ser genérico.
Otra opción es mantener la sesión abierta hasta que todas las colecciones y proxies necesarios hayan sido cargados. En la
arquitectura de algunas aplicaciones, particularmente en donde el código que accede a Hibernate y el código que lo usa
están en distinas capas, o en distintos procesos físicos, asegurarse de que una única sesión se mantenga abierta cuando una
colección se inicialice puede llegar a ser un problema. Hay básicamente dos maneras de lidiar con esto:
En una aplicación de web, se puede usar un filtro de servlet para cerrar la sesión sólo cuando realmente se llegue al
final de la request, una vez que la presentación de la interfaz de usuario (view) ha sido completada (el patrón de
programación Open Session in View ). Por supuesto, esto crea una pesada responsabilidad en cuando a la corrección
del manejo de excepciones por parte de la infraestructura de la aplicación. Es de vital importancia que la sesión sea
cerrada y la transacción concluida antes de retornar el control al usuario, incluso cuando ocurriere una excepción
mientras se presentare la interfaz de usuario. Vea la wiki de Hibernate para ejemplos de este patrón "Open Session
in View".
En aplicaciones que tengan una capa de negocios separada, la lógica de negocio debe "preparar" todas las
colecciones que vayan a ser necesitadas por la capa de web, antes de retornar. Esto significa que la capa de
negocios debería cargar todos los datos que hagan falta para un caso en particular, y devolvérselos a la capa de
interfaz de usuario/web. Normalmente, la aplicación llama a Hibernate.initialize() para cada colección que
vaya a ser necesaria en la capa de web (este llamado ocurre antes de que la sesión se cierre), u obtiene la colección
en forma ansiosa usando una consulta de Hibernate con cláusula FETCH o con un FetchMode.JOIN en el Criteria.
Esto es normalmente más fácil si se adopta el patrón de programación Command en lugar del patrón Session
Façade.
También se puede adjuntar un objeto previamente cargado a una nueva sesión, con merge() o lock() antes de
acceder a colecciones (u otros proxies) no inicializados. No, Hibernate no debería hacer esto automáticamente,
¡puesto que ello introduciría una semántica de transaccion ad hoc!
A veces usted no querrá inicializar toda una colección muy extensa, pero sí necesitará un poco de información acerca de
ella (por ejemplo, su tamaño), o un subconjunto de sus datos.
Se puede usar un filtro de colección para obtener el tamaño de una colección sin inicializarla:
El método createFilter() se usa también para obtener eficientemente subconjuntos de una colección sin necesidad de
inicializarla toda.
Hibernate puede hace un uso eficiente de la captura por lotes, es decir que puede cargar varios "proxies" no inicializados
cuando se accede a uno. (O colecciones. La captura por lotes es una optimización del tipo de captura por "SELECT
haragán"). Hay dos maneras de ajustar la captura por lotes: en la clase, o a nivel de la colección.
La captura por lotes a nivel de la clase es más fácil de entender. Imagínese que tiene la siguiente situación en tiempo de
ejecución: Hay 25 instancias de Cat cargadas en la sesión; cada Cat tiene una referencia a su dueño, una instancia de
Person. La clase Person está mapeada con un proxy, lazy="true". Si se itera a través de todos los gatos (cats) y se
invoca getOwner() en cada uno de ellos, Hibernate por defecto ejecutará 26 comandos SELECT, para obtener los proxies
de los dueños. Se puede ajustar este comportamiento especificando un batch-size (tamño de lote) en el mapeo de
Person:
También se puede habilitar la captura en lotes de colecciones. Por ejemplo, si cada Person tiene un colección haragana de
Cats, y en un momento hay 10 personas cargadas en la sesión, iterar a través de todas esas personas generaría 10
SELECTs, uno por cada llamado a getCats(). Si se habilita la captura por lotes para las colecciones de cats en el mapeo
de Person, Hibernate pre-capturará las colecciones:
<class name="Person">
Con un a batch-size igual a 3, Hibernate cargará 3, 3, 3, 1 colecciones en 4 SELECTs. De nuevo, el valor del atributo
dependerá del número esperado de colecciones no inicializadas para una sesión en particular.
La captura por lotes es particularmente útil si se tiene un árbol anidado de items, por ejemplo, la típica "Lista de
Materiales" necesarios para construir una pieza (bill of Materials o BOM, por sus siglas en inglés). (Aunque para árboles
de "mayormente lectura" un set anidado o un "path materialziado" serían mejores opciones) .
Si una colección o proxy de valor simple haraganes tienen que ser capturados, Hibernate los carga todos, volviendo a
ejecutar la consulta original en un subselect. Esto funciona de la misma manera que la captura por lotes, pero sin la carga
"parte por parte".
Hibernate3 soporta la captura haragana de propiedades individuales. Esta técnica de optimización también se conoce
como grupos de captura (en inglés "fetch groups"). Por favor, dese cuenta de que esto es un extra más publicitario que
funcional, dado que, en la práctica, optmizar las lecturas de filas es mucho más importante que optimizar las lecturas de
columna. De todos modos, leer sólo algunas propiedades de una clase puede ser útil en algunos casos extremos, en donde
tablas anticuadas/heredadas tengan cientos de columnas y el modelo de datos no pueda ser mejorado.
Para habilitar la carga haragana de propiedades, configure el atributo lazy en mapeos de propiedad específicos:
<class name="Document">
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" length="50"/>
<property name="summary" not-null="true" length="200" lazy="true"/>
<property name="text" not-null="true" length="2000" lazy="true"/>
</class>
¡La captura haragana de propiedades requiere instrumentación bytecode de tiempo de compilación! Si sus clases
persistentes han sido mejoradas, Hibernate ignorará silenciosamente la configuración de propiedades a "lazy", y se
resignará a usar la captura inmediata usual.
<instrument verbose="true">
<fileset dir="${testclasses.dir}/org/hibernate/auction/model">
<include name="*.class"/>
</fileset>
</instrument>
</target>
Una manera diferente (y tal vez mejor) de evitar lecturas de columa innecesarias, al menos en transacciones de
sólo-lectura, es usar las capacidades de proyección (Projections) que tienen las consultas HQL y Criteria. Esto evita la
necesidad del bytecode de tiempo de compiación, y es ciertamente una solución preferible.
Se puede forzar la captura ansiosa usual de propiedades, usando fetch all properties en HQL.
Existe la opción de decirle a Hibernate qué implementación de cacheo usar, especificando el nombre de una clase que
implemente org.hibernate.cache.CacheProvider, usando la propiedad hibernate.cache.provider_class.
Hibernate trae incorporada una buena cantidad de integraciones con proveedores de cachés open-source, (listados a
continuación); además, se puede implementar un caché propio y enchufarlo como se indicó anteriormente. Nota: las
versiones hasta 3.2 exclusive usaban EhCache como su proveedor por defecto. Éste ya no es el caso a partir de 3.2.
Soporta el
Seguro para
caché de
Caché Clase del proveedor Tipo usar en
consultas
clusters
(Query)
Hashtable
(no se espera
que sea org.hibernate.cache.HashtableCacheProvider memoria sí
usado en
producción)
EHCache org.hibernate.cache.EhCacheProvider memoria, disco sí
OSCache org.hibernate.cache.OSCacheProvider memoria, disco sí
en sí
SwarmCache org.hibernate.cache.SwarmCacheProvider cluster(multicast (invalidación
de ip) de cluster)
en cluster
sí (requiere
JBoss Cache (multicast de sí
org.hibernate.cache.TreeCacheProvider sincronización
1.x ip), (replicación)
de relojes)
transaccional
en cluster sí
sí (requiere
JBoss Cache (multicast de (replicación
org.hibernate.cache.jbc2.JBossCacheRegionFactory sincronización
2 ip), o
de relojes)
transaccional invalidación)
El elemento <cache> del mapeo de una clase o colección tiene la forma siguiente:
<cache
usage="transactional|read-write|nonstrict-read-write|read-only" (1)
region="RegionName" (2)
include="all|non-lazy" (3)
/>
(1)
usage (obligatorio) especifica la estrategia de cacheo transactional, read-write, nonstrict-read-write or
read-only
(2)
region (optativo, por defecto, el nombre de rol de la clase o colección): especifica el nombre de la región del
do
caché de 2 nivel.
(3)
include (optativo, por defecto, all) non-lazy especifica que las propiedades de la entidad mapeadas con
lazy="true" no deben ser mapeadas cuando esté habilitada la captura haragana a nivel de cada atributo.
Si su aplicación necesita leer pero nunca modificar las instancias de una clase persistente, se puede usar un caché
read-only. Esta es la estrategia más simple y la de mejor performance. También es perfectamente segura de utilizar en un
cluster.
<cache usage="read-only"/>
....
</class>
Si la aplicación necesita actualizar datos, puede ser apropiado usar una caché de lecto-escritura (read-write). Esta
estrategia de cacheo nunca debería ser usada si se requiere aislamiento de transacciones serializables. Si el caché se usa en
un entorno JTA, se debe especificar la propiedad hibernate.transaction.manager_lookup_class, especificando una
estrategia para obtener la propiedad TransactionManager de JTA. En otros entornos, hay que asegurarse de que la
transacción esté completa para cuando ocurran Session.close() o Session.disconnect(). Si se quiere usar esta
estrategia en un cluster, hay que asegurarse de que la implementación subyacente de caché soporta "locking". Los
proveedores de caché que vienen ya incorporados no lo soportan.
<cache usage="read-write"/>
....
<set name="kittens" ... >
<cache usage="read-write"/>
....
</set>
</class>
Si la aplicación necesita actualizar datos, pero sólo ocasionalmente (es decir, si es improbable que dos transacciones traten
de actualizar el mismo ítem simultáneamente), y no se requiere un aislamiento de transacciones estricto, puede ser
apropiado un caché "de lecto-escritura no estricta" (nonstrict-read-write). Si el caché se usa en un entorno JTA, se
debe especificar hibernate.transaction.manager_lookup_class. En otros entornos, hay que asegurarse de que la
transacción esté completa para cuando ocurran Session.close() o Session.disconnect().
La estrategia transaccional (transactional) provee soporte para proveedores de caché enteramente transaccionales,
como JBoss TreeCache. Tales cachés sólo pueden ser usados en un entorno JTA, y se debe especificar
hibernate.transaction.manager_lookup_class.
Importante
La siguiente table muestra qué proveedores son compatibles con qué estrategias de concurrencia.
lecto-escritura no lecto-
Caché sólo-lectura transaccional
estricta escritura
Hashtable (no concebida para uso en
sí sí sí
producción)
EHCache sí sí sí
OSCache sí sí sí
SwarmCache sí sí
JBoss Cache 1.x sí sí
JBoss Cache 2 sí sí
Cuando a continuación se invoca flush(), el estado de dicho objeto será sincronizado con la base de datos. Si no se
quiere que esta sincronización ocurra, o si se está procesando una cantidad inmensa de objetos que haga necesario manejar
la memoria eficientemente, se puede usar el método evict() para quitar objetos y sus colecciones del caché de primer
nivel.
La Session también provee un método contains() para determinar si una instancia ya pertenece al caché de sesión.
Para desalojar a todos los objetos del caché de sesión, llame Session.clear()
Para el caché de 2do nivel, hay métodos definidos en SessionFactory para desalojar el estado cacheado de una instancia,
de toda una clase, de la instancia de una colección, o de un rol de colección completo.
El argumento CacheMode controla cómo una sesión en particular interactúa con el caché de 2do nivel.
CacheMode.GET: lee items del caché de 2do nivel, pero no escribe en el caché de 2do nivel excepto cuando se
actualicen datos.
items en el caché de 2do nivel, pero lee del caché de 2do nivel, elude los efectos de
CacheMode.REFRESH: escribe
do
hibernate.cache.use_minimal_puts, forzando un refresco del caché de 2 nivel para todos los items leídos de
la base de datos.
Para navegar por ls contenidos del caché de 2do nivel, o de una región de cacheo de consultas, use la API de Statistics:
Necesitará habilitar estadísticas, y, optativamente, forzar a Hibernate a que escriba las entradas de caché en un formato
más legible por seres humanos:
hibernate.generate_statistics true
hibernate.cache.use_structured_entries true
hibernate.cache.use_query_cache true
Esta configuración provoca la creación de dos nuevas regiones de caché: una que contendrá los resultados cacheados de
las consultas (org.hibernate.cache.StandardQueryCache), y la otra conteniendo la fecha y hora de las actualizaciones
más recientes hechas en las tablas "consultables" (org.hibernate.cache.UpdateTimestampsCache). Note que el caché
de consultas no cachea el estado de las entidades mismas contenidas en el resultado; cachea solamente los valores de los
identificadores, y los resultados de tipo "value type". Así que el caché de consultas siempre debería ser usado en
conjunción con el caché de 2do nivel.
A la mayoría de las consultas no les representa ninguna ventaja el ser cacheadas, así que las consultas no se cachean por
defecto. Par habilitar el cacheo, invoque Query.setCacheable(true). Este llamado permite que la consulta, al ser
ejecutada, busque resultados ya existentes, o agregue sus resultados al caché.
Si se require un control más granular sobre las políticas de expiración de los cachés, se puede especificar una región de
caché para una consulta en particular, invocando Query.setCacheRegion().
Si la consulta forzare un refresco de esta región de caché en particular, habría que invocar
Query.setCacheMode(CacheMode.REFRESH). Esto es particularmente útil para los casos en donde los datos subyacentes
pudieren haber sido actualizados por un proceso separado (por ejemplo, no por Hibernate), y le permite a la aplicación
refrescar selectivamente un resultado en particular. Esto es más eficiente que el desalojo de toda una región de cachés via
SessionFactory.evictQueries().
19.5.1. Taxonomía
colección de valores
asociaciones de-uno-a-muchos
asociaciones de-muchos-a-muchos
Esta clasificación distingue entre las varias relaciones de tablas y claves foráneas, pero en realidad no nos dice todo lo que
necesitamos saber acerca del modelo relacional. Para comprender cabalmente la estructura relacional y las características
de performance, debemos considerar también la estructura de la clave primaria que es usada por Hibernate para actualizar
o borrar colecciones de filas. Esto a su vez sugiere la siguiente clasificación:
colecciones indexadas
sets
bags
Todas las colecciones indexadas (maps, lists, arrays), tienen una clave primaria que consiste en las columnas de <key> e
<index>. En este caso, las actualizaciones a la colección son extremadamente eficientes: la clave primaria puede ser
indexada eficientemente y una fila en particular puede ser localizada eficientemente cuando Hibernate trate de actualizarla
o borrarla.
Los sets tienen una clave primaria que consiste en una <key> y columnas elemento. Esto puede ser menos eficiente para
algunos tipos de elemento de colección, particularmente los elementos compuestos, o grandes campos de texto o binarios;
la base de datos puede no ser capaz de indexar tan eficientemente una clave primaria compleja. Por otra parte, para
asociaciones de-uno-a-muchos o de-muchos-a-muchos, particularmente cuando se usen identificadores sintéticos, es muy
probable que sea igual de eficiente. (Nota aparte: si se quiere que SchemaExport realmente cree por sí solo la clave
primaria de un <set>, debe declararse not-null="true" para todas las columnas).
los mapeos <idbag> definen una clave sustituta, así que son muy eficientes de actualizar. De hecho, son el mejor caso.
Las bags son el peor caso. Como una bag permite valores de elemento duplicados y no tiene columnas índice, no se puede
definir ninguna clave primaria. Hibernate no tiene forma de distinguir entre filas duplicadas. Hibernate resuelve este
problema borrando completamente la colección (con un simple DELETE) y recreándola cada vez que algo cambia. Esto
puede llegar a ser muy ineficiente.
Note que para una asociación de-uno-a-muchos, la "clave primaria" piede no ser la clave primiaria física de la tabla en la
base de datos. Pero incluso en este caso, la clasificación recién descripta es aún útil (aún refleja cómo es que Hibernate
"localiza" filas individuales de una colección).
19.5.2. Las lists, maps, idbags y sets son las colecciones más eficientes de actualizar
Basándose en la discusión precedente, debería quedar claro que las colecciones indexadas y (normalmente) los sets
permiten el desempeño más eficiente en términos de agregar, quitar, o actualizar elementos.
Podría decirse las colecciones indexadas tienen una ventaja más sobre los sets en las asociaciones de-muchos-a-muchos o
colecciones de valores: a causa de la estructura de un Set, Hibernate nunca efectúa un UPDATE de una fila cuando un
elemento se "cambia". Los cambios en un Set siempre requieren INSERT(s) y DELETE(s) de filas individuales. Esta
consideración, de nuevo, no se aplica a las asociaciones de-uno-a-muchos.
Tras observar que los arrays no pueden ser haraganes, concluiríamos que las listas, mapas e idbags son los tipos (no
inversos) de colección con mejor performance, seguidos de cerca por los sets. Los sets son el tipo más común de colección
en Hibernate, porque la semántica de un set es la más natural para un modelo relacional.
Sin embargo, en un modelo de dominio Hibernate bien diseñado, usualmente vemos que la mayóría de las coleccioes son
en realidad asociaciones de-uno-a-muchos con inverse="true". Para estas asociaciones, el UPDATE es manejado por el
extremo 'de-muchos-a-uno' de la asociación, así que estas disquisiciones sobre la performance al actualizar colecciones
simplemente no son relevantes.
19.5.3. Las bags y lists son las colecciones inversas más eficientes
No descarte a las bags para siempre, todavía. Hay un caso en particular en el cual las bags (y también las lists) tienen
mucha mejor performance que los sets. Para una colección con inverse="true" (la relación bidireccional estándar
estándar de-uno-a-muchos, por ejemplo, ¡podemos agregar un elemento a una bag o list sin necesidad de inicializar
(mediate una captura) los elementos de la bag! Esto se debe a que Collection.add() y Collection.addAll() siempre
tienen que devolver "true" para una bag o list, al contrario de lo que ocurre con un Set. Esto puede volver el código
siguiente mucho más rápido:
Ocasionalmente, borrar elementos de una colección puede volverse extremadamente ineficiente. Hibernate no es
completamente estúpido, así que sabe que no lo tiene que hacer en el caso de una colección nueva y vacía, si uno invoca
list.clear(), por ejemplo. En este caso, Hibernate emite un solo DELETE, y listo.
Supongamos que se agrega un simple elemento a una colección de tamaño 20, y luego se quitan 2 elementos. Hibernate
emitirá un comando INSERT y dos DELETEs (a menos que la colección sea una bag). Esto es deseable.
Sin embargo, suponga que quitamos 18 elementos, dejando 2, y luego agregamos el nuevo elemento. Aquí hay dos
maneras de proceder:
borrar toda la colección (usando un solo DELETE) e insertar todos los 5 elementos actuales, uno por uno.
Hibernate no es lo suficientemente astuto como para darse cuenta de que la segunda opción probablemente sea más
rápida.(Y probablemente sea mejor así, tal comportamiento podría confundir a los triggers de la BD, etc).
Afortunadamente, podemos forzar este comportamiento (la estrategia número 2) en cualquier momento, descartando (es
decir des-referenciando) la colección original, y devolviendo una colección recientemente instanciada con todos los
elementos activos. Esto puede ser muy útil y poderoso a veces.
Por supuesto, los borrados en una pasada no son relevantes cuando tratamos con colecciones mapeadas con
inverse="true".
Hibernate también puese usar JMX para publicar mediciones, si se habilita el MBean StatisticsService MBean. Se
puede habilitar un solo MBean para todas las SessionFactorys, o uno para cada una. Vea el siguiente código para
ejemplos mínimos de configuración:
A hacer: esto no tiene sentido. En el primer caso, obtenemos y usamos el MBean dorectamente. En el segundo, debemos
dar el nombre JNDI en el cual la fábrica de sesiones está contenida antes de usarla. Usar
hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name")
Las estadísticas pueden ser reinicializadas en forma programática, usando el método clear(). Se puede mandar un
sumario al log (a nivel info). usando el método logSummary().
19.6.2. Mediciones
Hibernate provee un buen número de mediciones, desde muy básicas, hasta información especializada que sólo es
relevante en ciertos escenarios. Todos los contadores relevantes están descriptos en la API de Statistics, en tres
categorías:
Las mediciones relacionadas con el uso de la sesión en general, tales como el número de desiones abiertas, el
número de conexiones JDBC obtenidas, etc.
Mediciones relacionadas con las entidades, colecciones, consultas y cachés como un todo (es decir, mediciones
globales).
Mediciones detalladas relacionadas con una entidad, colección, consulta o región de caché en particular.
Por ejemplo, se puede chequear la proporción de veces en que se da en el blanco, no se da en el blanco, o se inserta en el
caché (en inglés: hit, miss and put ratio), para las entidades, las colecciones y las consultas, y el tiempo que, en promedio,
necesita una consulta. Tenga en cuenta que el número de milisegundos es sólo una aproximación en Java. Hibernate está
atado a la precisión de la JVM, y en algunas plataformas ésta tiene solo un grado de exactitud de 10 segundos.
Para acceder a las mediciones globales (es decir, no atadas a una entidad, caché, etc en particular) se usan simples
métodos "getter". Se puede acceder a las mediciones de una entidad, colección o región de caché en particular a través del
nombre, y a través de su representación en HQL o en SQL para las consultas. Por favor refiérase a los JavaDocs de las
APIs de Statistics, EntityStatistics, CollectionStatistics, SecondLevelCacheStatistics, y
QueryStatistics para más información. El siguiente código muestra un ejemplo simple:
EntityStatistics entityStats =
stats.getEntityStatistics( Cat.class.getName() );
long changes =
entityStats.getInsertCount()
+ entityStats.getUpdateCount()
+ entityStats.getDeleteCount();
log.info(Cat.class.getName() + " changed " + changes + "times" );
Para trabajar en todas las entidades, colecciones, consultas y regiones de caché, se pued obtener la lista de nombres de
entidades, colecciones, consultas y regiones con los siguientes métodos: getQueries(), getEntityNames(),
getCollectionRoleNames(), y getSecondLevelCacheRegionNames().
Las Herramientas de Hibernate (Hibernate Tools) actualmente incluyen plugins para el entorno ECLIPSE, así como
herramientas de Ant para efectuar ingeniería reversa de base de datos existentes.
Editor de mapeo: Un editor de archivos XML de mapeo de Hibernate, que soporta auto-compleción y resaltado de
sintaxis. También soporta auto-compleción semántica para los nombres de las clases y para los nombres de campos
y propiedades, haciéndolo mucho más versátil que un editor de XML común.
Consola: La consola es una bueva vista (view) de Eclipse. Además de una visión jarárquica general de la
configuración de la consola, también permite la ejecución de consultas HQL contra la base de datos, y nevegar los
resultados directamente en Eclipse.
Wizards para desarrollo: Con las herramientas de Hibernate Eclipse se proveen varios "wizards".
(N.del T): se les suele llamar "wizards" o hechiceros a los programas que van nagevando de una pantalla a la otra
de manera secuencial, para permitirle al usuario realizar una operación compleja guiándolo a través de unas
pocas opciones simples por vez.
Se puede usar un "wizard" para generar rápidamente archivos de configuración de Hibernate, o se se le puede hacer
una ingeniería reversa completa a un esquema de DB existente, convirtiéndolo en archivos fuente tipo POJO y
archivos de mapeo de Hibernate. El wizard para ingeniería reversa soporta patrones (templates) configurables a
medida.
Por favor refiérase al paquete Hibernate Tools y a su documentación para más información.
Por otra parte, el paquete principal de Hibernate ya trae incluida una herramienta (que puede hasta puede ser usada
instantáneamente desde dentro de Hibernate): SchemaExport, también llamada hbm2ddl.
Se debe especificar el dialecto SQL (Dialect) a través de la propiedad hibernate.dialect cuando se use esta
herramienta, dado que el DDL es altamente dependiente del proveedor de DB.
Muchos elementos de mapeo de Hibernate definen atributos opcionales llamados length, precision y scale. Se puede
configurar el largo, precisión y escala respectivamente de una columna con estos atributos.
Algunas tags también aceptan un atributo not-null (para generar una constraint NOT NULL en las columnas de la tabla),
y un atributo unique (para generar constraints de unicidad en las columnas de la tabla).
Se puede usar un atributo unique-key para agrupar columnas en una simple constraint de unicidad. Al presente, el valor
especificado del atributo unique-key no es usado para nombrar la constraint en el DDL generado, solamente para agrupar
a las columnas en el archivo de mapeo.
Un atributo index especifica el nombre de un índice a crear utilizando la columna o columnas mapeadas. Se puede agrupar
múltiples columnas en en mismo índice, simplemente especificando el mismo nombre de índice.
Un atributo foreign-key puede ser usado para sustituir el nombre generado de cualquier constraint de clave foránea.
Muvhos elementos de mapeo también aceptan un elemento <column> hijo. Esto es particularmente útil al mapear tipos
multicolumna:
El atributo default permite especificar un valor por defecto pra la columna (debería asignársele el mismo valor a la
propiedad mapeada antes de grabar una nueva instancia de la clase mapeada).
El atributo sql-type le permite al usuario sustituir el mapeo por defecto de un tipo de dato Hibernate a un tipo de dato
SQL.
...
<property name="bar" type="float"/>
</class>
...
</class>
<property name="balance">
<column name="bal">
<comment>Balance in USD</comment>
</column>
</property>
Esto resulta en comandos comment on table o comment on column en el DDL generado, cuando esto se soportada.
La herramienta SchemaExport imprime un script DDL en la salida estándar y/o ejecuta los comandos DDL.
Opción Descripción
--quiet no imprima el script en la salida estándar
--drop sólo elimine (drop) las tablas
--create sólo cree las tablas
--text n exporte a la base de datos
--output=my_schema.ddl escriba el script DDL en un archivo
--naming=eg.MyNamingStrategy seleccione una NamingStrategy (estrategia de nombrado)
--config=hibernate.cfg.xml lea la configuración de Hibernate de una archivo XML
--properties=hibernate.properties lea las propiedades de la BD de un archivo
--format formatee prolijamente el SQL generado en el script
--delimiter=; agréguele un delimitador de fin de línea al script
20.1.3. Propiedades
en hibernate.properties
<target name="schemaexport">
<taskdef name="schemaexport" classname="org.hibernate.tool.hbm2ddl.SchemaExportTask" classpathr
<schemaexport
properties="hibernate.properties"
quiet="no"
text="no"
drop="no"
delimiter=";"
output="schema-export.sql">
<fileset dir="src">
<include name="**/*.hbm.xml"/>
</fileset>
</schemaexport>
</target>
La herramienta SchemaUpdate actualizará un esquema de base de datos existente con cambios "incrementales". Note que
SchemaUpdate depende grandemente de la API de metadatos de JDBC, así que no funcionará con todos los drivers de
JDBC.
Opción Descripción
--quiet no imprima el script en la salida estándar
--text no exporte el script a la base de datos
--naming=eg.MyNamingStrategy seleccione una NamingStrategy
Opción Descripción
--properties=hibernate.properties lea las propiedades de base de datos de un archivo
--config=hibernate.cfg.xml especifique un archivo .cfg.xml
20.1.6. Utilizar Ant para las actualizaciones incrementales del esquema de base de datos
<target name="schemaupdate">
<taskdef name="schemaupdate" classname="org.hibernate.tool.hbm2ddl.SchemaUpdateTask" classpathr
La herramienta SchemaValidator validará si el esquema de BD existente "corresponde" con los archivos de mapeo. Note
que el SchemaValidator depende grandemente de la API de metadatos de JDB, así que no funcionará con todos los
drivers JDBC, Esta herramienta es extremadamente útil para tests.
Opción Descripción
--naming=eg.MyNamingStrategy selecciona una NamingStrategy
--properties=hibernate.properties lee las propiedades de base de datos de una archivo
--config=hibernate.cfg.xml especifica un archivo .cfg.xml
<target name="schemavalidate">
<taskdef name="schemavalidator" classname="org.hibernate.tool.hbm2ddl.SchemaValidatorTask" clas
<schemavalidator properties="hibernate.properties">
<fileset dir="src">
<include name="**/*.hbm.xml"/>
</fileset>
</schemavalidator>
</target>
Una de las primeras cosas que los nuevos usuarios tratan de hacer con Hibernate, es modelar una relación del tipo
padre/hijo (en inglés, "parent" y "child" respectivamente). Hay dos abordajes diferentes para esto. Por varias razones, el
abordaje más conveniente, especialmente para usuarios nuevos, es modelar tanto Padre como Hijo como clases de
entidad, con una asociación de-uno-a-muchos (<one-to-many>) del Padre al Hijo. (El enfoque alternativo es declarar el
Hijo como un elemento compuesto ( <composite-element>). Ahora bien, ocurre que la semántica por defecto de una
asociación de-uno-a-muchos (en Hibernate), es mucho más parecida a la semántica usual de una relación padre/hijo, que a
la semántica del mapeo de un elemento compuesto. Explicaremos cómo usar una asociación 'de-uno-a-muchos
bidireccional con propagación en cascada' para modelar una relación padre/hijo eficiente y elegantemente. ¡No es nada
difícil!
Cuando quitamos/agregamos un objeto de una colección, el número de versión del dueño de la colección se
incrementa.
Si un objeto quitado de una colección es una instancia de un "value type" (es decir, de un elemento compuesto),
dicho objeto dejará de ser persistente y su estado será completamente borrado de la base de datos. Del mismo modo,
agregarle una instancia de "value type" a la colección causará que su estado se vuelva inmediatamente persistente.
Por otra parte, si es una entidad lo que se quita de una colección (en una asociación de-uno-a-muchos o de-muchos-
a-muchos), ésta no será borrada por defecto. Este comportamiento es completamente consistente: ¡un cambio en el
estado interno de una entidad no debería causar que la entidad asociada se esfume! Del mismo modo, agregarle una
entidad a una colección no provoca por defecto que la entidad se vuelva persitente.
En cambio, el comportamiento por defecto es que, agregarle una entidad a una colección, simplemente crea un vínculo
entre las dos entidades, y quitársela borra dicho vínculo. Esto es muy apropiado para todos los casos, excepto para el caso
en que la vida de un hijo dependa del ciclo de vida del padre.
<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
Parent p = .....;
Child c = new Child();
p.getChildren().add(c);
session.save(c);
session.flush();
Esto es no solamente ineficiente, sino que viola cualquier constraint NOT NULL que pudiere haber en la columna
parent_id column. Podemos areglar la violación de la constraint de nulabilidad especificando not-null="true" en el
mapeo de la colección:
<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set>
La causa subyacente de este comportamiento es que el vínculo (la clave foránea parent_id) de p a c no se considera
como parte del estado del objeto Child y por lo tanto no es creado durante el INSERT. Así que la solución es volver al
vínculo que es parte del mapeo del objeto Child.
Ahora que la entidad Child está manejando el estado del vínculo, le indicamos a la colección que no actualice el vínculo
ella. Para esto se usa el atributo inverse.
Para afinar las cosas un poco más, podríamos crear un método addChild() en la clase Parent.
En forma similar, no necesitamos iterar a través de los hijos al grabar o borrar un Parent. Lo siguiente borra a p y a todos
sus hijos de la base de datos:
no borrará a c de la base de datos; sólo quitará el vínculo con p (y causará en este caso una violación de constraint NOT
NULL). Hay que borrar al (con delete()) explícitamente al Child.
Ahora, en este caso, un Child no puede realmente existir sin su padre. Entonces, si quitamos un Child de una colección,
realmente queremos que sea borrado. Para ello debemos usar cascade="all-delete-orphan".
Nota: aún cuando el mapeo de la colección especifica inverse="true", lás propagaciones en cascada sí se procesan
iterando a través de los elementos de la colección. Así que si se necesita que una colección sea grabada, borrada o
actualizada en cascada, se debe agregar el atributo "cascade" a la colección. No basta con invocar setParent().
El siguiente código actualizará tanto al padre como al hijo, e insertará un nuevo hijo (newChild).
Todo esto está muy bien en el caso de un identificador autogenerado, pero ¿qué pasas con los identificadores asignados, o
con los identificadores compuestos? Esto es más difícil, dado que Hibernate no puede usar la propiedad identificadora para
distinguir entre un objeto recientemente instanciado (con un identificador asignado por el usuario) y un objeto cargado en
una sesión previa. En este caso, Hibernate usará o bien las propiedades version/timestamp, o bien consultará el caché de
2do nivel, o, en el peor de los casos, la base de datos, para verificar si la fila existe.
21.5. Conclusión
Esto es bastante para digerir de una vez, y puede parecer confuso la primera vez. Pero en la práctica todo se acomoda y
funciona en una forma muy linda. La mayoría de las aplicaciones Hibernate utilizan el patrón padre/hijo en muchos
lugares.
En el primer párrafo mencionábamos una alternativa. En el caso de los mapeos con <composite-element> no existe
ninguno de estos problemas, que tienen exactamente la semántica de una relación padre/hijo. Desafortunadamente, existen
dos grandes limitaciones al trabajar con clases de elementos compuestos: un elemento compuesto no puede contener
colecciones, y ellos mismos sólo pueden ser hijos de una única entidad padre.
package eg;
import java.util.List;
package eg;
import java.text.DateFormat;
import java.util.Calendar;
}
public void setTitle(String string) {
_title = string;
}
}
<?xml version="1.0"?>
<hibernate-mapping package="eg">
</class>
</hibernate-mapping>
<?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>
</hibernate-mapping>
package eg;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
public BlogItem createBlogItem(Blog blog, String title, String text) throws HibernateException
public BlogItem createBlogItem(Long blogid, String title, String text) throws HibernateExceptio
item.setText(text);
finally {
session.close();
}
return result;
}
result = q.list();
tx.commit();
}
catch (HibernateException he) {
if (tx!=null) tx.rollback();
throw he;
}
finally {
session.close();
}
return result;
}
}
23.1. Empleador/Empleado
El modelo siguiente de la relación entre empleador y empleado (Employer y Employee) usa una verdadera clase de
entidad, Employment (empleo) para representar la asociacón. Esto se hace porque puede haber másde un período de
empleo para las mismas dos partes involucradas. Para modelar valores monetarios y nombres de empleados se usan
componentes.
<hibernate-mapping>
<id name="id">
<generator class="sequence">
<param name="sequence">employment_id_seq</param>
</generator>
</id>
<property name="startDate" column="start_date"/>
<property name="endDate" column="end_date"/>
</class>
</hibernate-mapping>
alter table employment_periods add constraint employment_periodsFK0 foreign key (employer_id) refer
alter table employment_periods add constraint employment_periodsFK1 foreign key (employee_id) refer
create sequence employee_id_seq
create sequence employment_id_seq
create sequence employer_id_seq
23.2. Autor/Obra
Considérese el modelo siguiente de la relación entre obra, autor y persona (Work, Author y Person respectivamente).
Representamos la relación entre Work y Author como un asociación de-muchos-a-muchos. Elegimos representar la
asociación entre Author y Person como de-uno-a-uno. Otra posibilidad sería hacer que Author extendiere Person.
<hibernate-mapping>
<property name="title"/>
<set name="authors" table="author_work">
<key column name="work_id"/>
<many-to-many class="Author" column name="author_id"/>
</set>
</class>
<property name="alias"/>
<one-to-one name="person" constrained="true"/>
</class>
</hibernate-mapping>
En este mapeo hay 4 tablas. works, authors y persons contienen las obras, autores y personas respectivamente.
author_work es una tabla de asociación que vincula a los autores con sus obras. Aquí está el esquema de tablas, tal cual
es generado por SchemaExport.
alter table authors add constraint authorsFK0 foreign key (id) references persons
alter table author_work add constraint author_workFK0 foreign key (author_id) references authors
alter table author_work add constraint author_workFK1 foreign key (work_id) references works
23.3. Cliente/Orden/Producto
Ahora consideremos un modelo de las relaciones entre cliente, orden, ítem (es decir, línea de la orden) y producto
(Customer, Order, LineItem y Product respectivamente). Hay una asociación de-uno-a-muchos entre Customer y
Order, pero ¿cómo deberíamos representar Order / LineItem / Product? Elijo mapear LineItem como una clase de
asociación que representa la asociación de-muchos-a-muchos entre Order y Product. En Hibernate, esto se denomina un
"elemento compuesto" (composite element).
El documento de mapeo:
<hibernate-mapping>
</hibernate-mapping>
customers, orders, line_items y products contienen los datos de clientes, órdenes, items de las órdenes y productos,
respectivamente. line_items también actúa como una tabla de asociación que vincula órdenes con productos.
alter table orders add constraint ordersFK0 foreign key (customer_id) references customers
alter table line_items add constraint line_itemsFK0 foreign key (product_id) references products
alter table line_items add constraint line_itemsFK1 foreign key (order_id) references orders
<class name="Person">
<id name="name"/>
<one-to-one name="address" cascade="all">
<formula>name</formula>
<formula>'HOME'</formula>
</one-to-one>
<one-to-one name="mailingAddress" cascade="all">
<formula>name</formula>
<formula>'MAILING'</formula>
</one-to-one>
</class>
<class name="Customer">
<id name="customerId"
length="10">
<generator class="assigned"/>
</id>
<key column="customerId"/>
<index column="orderNumber"/>
<one-to-many class="Order"/>
</list>
</class>
<property name="total">
<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 )
</formula>
</property>
<many-to-one name="customer"
column="customerId"
insert="false"
update="false"
not-null="true"/>
</class>
<class name="LineItem">
<property name="quantity"/>
</class>
<class name="Product">
<synchronize table="LineItem"/>
<property name="numberAvailable"/>
<property name="numberOrdered">
<formula>
( select sum(li.quantity)
from LineItem li
where li.productId = productId )
</formula>
</property>
</class>
<key>
<column name="userName"/>
<column name="org"/>
</key>
<many-to-many class="Group">
<column name="groupName"/>
<formula>org</formula>
</many-to-many>
</set>
</class>
<class name="Person"
discriminator-value="P">
<discriminator type="character">
<formula>
case
when title is not null then 'E'
when salesperson is not null then 'C'
else 'P'
end
</formula>
</discriminator>
<component name="address">
<property name="address"/>
<property name="zip"/>
<property name="country"/>
</component>
</class>
<class name="Person">
<id name="id">
<generator class="hilo"/>
</id>
</class>
<class name="Address">
<id name="id">
<generator class="hilo"/>
</id>
</class>
<class name="Account">
<id name="accountId" length="32">
<generator class="uuid"/>
</id>
</class>
Use una clase Direccion para encapsular calle, barrio, provincia, CodigoPostal. Esto alienta la reutilización
de código y simplifica la reingeniería.
Hibernate hace que las propiedades identificadoras sean opcionales, pero existe todo tipo de razones por las cuales
habría que usarlos siempre. Recomendamos que estos identificadores sean "sintéticos" (generados, sin ningún
significado de negcocios).
Identifique claves naturales para todas las entidades, y mapéelas usando <natural-id>. Implemente equals() y
hashCode() para comparar las propiedades que componen la clave natural.
No use un único documento de mapeo monolítico. Mapee com.eg.Foo en el archivo com/eg/Foo.hbm.xml. Esto
tiene sentido, particularmente en un entorno de programación en equipo.
Ésta es una buena práctica si sus consultas utilizan funciones SQL que no sean estándar de ANSI. Externalizarlas
volverá la aplicación más portátil.
Como en JDBC, siempre reemplace valores no constantes con "?". ¡Nunca use manipulación de cadenas para unir
un valor no-constante a una consulta! Mejor aún, considere usar parámetros nombrados en las consultas.
Hibernate le permite a la aplicación manejar sus conexiones JDBC, pero esto debe considerarse como un últmo
recurso. Si no puede usar los proveedores de conexiones que ya vienen incluidos, considere proveer su propia
implementación de org.hibernate.connection.ConnectionProvider.
Suponga que tiene un tipo Java, digamos de una biblioteca, que necesita ser persistido, pero no provee los métodos
de acceso necesarios para mapearlo como un componente. Usted debería considerar implementar
org.hibernate.UserType. Esta estrategia libera al código de la aplicación de implementar transformaciones desde
o hacia un tipo Hibernate.
En áreas de performance críticas de un sistema, algunos tipos de operación podrían beneficiarse al usar JDBC
directamente. Pero por favor, espere hasta estar seguro de que algo constituye un cuello de botella. Y no asuma que
JDBC es necesariamente más rápida. Si necesita utilizar JDBC directamente, puede valer la pena abrir una sesión y
usar esa conexión JDBC. De este modo, usted aún puede aprovechar la estrategia de transacciones y el proveedor
de conexiones subyacente.
De tanto en tanto, la sesión sincroniza su estado persistente con la base de datos. La performance se resentirá si este
proceso ocurre demasiado seguido. A veces se puede disminuir la cantidad de "flush" innecesarios inhabilitando el
"flushing" automático, o incluso cambiando el orden de las consultas u otras operaciones dentro de una transacción
en particular.
Cuando se usa una arquitectura de servlet/session bean, se pueden pasar objetos persistentes cargados en el session
bean desde y hacia la capa de servlets/JSP. Use una nueva sesión para servir a cada solicitud (request). Use
Session.merge() o Session.saveOrUpdate() para sincronizar objetos con la base de datos.
Las transacciones de DB tienen que ser lo más cortas posible, para que la aplicación sea escalable. Sin embargo, a
veces es necesario implementar transacciones "de aplicación" de largo aliento, una unidad de trabajo única desde el
punto de vista del usuario. Una "transacción de aplicación" puede extenderse por varios ciclos solicitud/respuesta
del cliente. Es común usar objetos desprendidos para implementar transacciones de aplicación. Una alternativa,
extremadamente apropiada en arquitecturas de dos capas, es mantener un único contacto de persistencia abierto
(una sesión) para todo el ciclo de vida de la "transacción de aplicación", y simplemente desconectarse de la
conexión JDBC al final de cada slicitud, reconectándose al comenzar la solicitud siguiente. Nunca comparta una
misma sesión a través de más de una "transacción de aplicación" o estará trabajando con datos rancios.
Esto una práctica necesaria más que una "práctica recomendada". Cuando ocurra una excepción, invoque un
rollback de la transacción, y cierre la sesión. Si no lo hace, Hibernate no puede garantizar que el estado en memoria
represente correctamente el estado persistente. Como corolario de esto, no use Session.load() para determinar si
una instancia con un identificador dado existe en la base de datos. En lugar de eso, use Session.get() o una
consulta.
Use la captura ansiosa (eager fetching) con mesura. Use proxies y colecciones haraganas para la mayoría de las
asociaciones a clases que no es probable que sean retenidas en el caché de 2do nivel. Para asociaciones a clases
cacheadas, en donde exista una probabilidad extremadamante alta de ubicarla en el caché, inhabilite explícitamente
la captura haragana usando lazy="false". Cuando, para un caso de uso en particular, sea apropiado usar una
captura por join, use una consulta con left join fetch.
use el patrón open session in view, o una fase de ensamble (assembly phase) disciplinada para evitar problemas con
datos no capturados.
Hibernate libera al programador de la tediosa tarea de escribir objetos de transferencia de datos (DTO por sus siglas
en inglés). En una arquitectura EJB tradicional, los DTOs tienen un doble propósito: uno, sortear el problema de que
los entity beans no son serializables; el otro, que implícitamente definen una fase de ensamble en donde todos los
datos usados por la interfaz de usuario o "view" tiene que ser capturados y puestos en orden en DTOs, antes de que
el control le sea devuelto a la capa de presentación. Hibernate elimina el primer propósito, pero aún se necesita una
fase de ensamble (imagínese a los métodos de negocio como si tuvieran un estricto contrato con la capa de
presentación acerca de qué datos hay disponibles en los objetos desprendidos), a menos que se esté dispuesto a
mantener la sesión abierta a través de la capa de presentación. Esto no es una limitación de Hibernate, sino un
requerimiento fundamental para un acceso seguro a datos transaccionales.
Oculte el código (Hibernate) de acceso a datos detrás de una interfaz. Combine los partrones DAO y Sesión Thread
Local. Se puede hacer incluso que algunas clases sean persistidas por JDBC escrita a mano, y asociadas a Hibernate
por medio de un UserType. (Este consejo se aplica sólo a aplicaciones "lo suficientemente largas", ¡no es adecuado
para una aplicación con 5 tablas!)
Los casos de uso bien definidos, que involucren verdaderas asociaciones de-muchos-a-muchos, son raros. En
general se necesita que la "tabla de vínculo" almacene información adicional. En ese caso, es mucho mejor usar dos
asociaciones de-uno-a-muchos a una clase de vínculo intermedia. De hecho, creemos que la mayoría de las
asociaciones son de-uno-a-muchos y de-uno-a-uno. Hay que tener cuidado al usar cualquier otro estilo de
asociación, y preguntarse si es verdaderamente necesario.
Las asociaciones unidireccionales son más difíciles de consultar. En aplicaciones grandes, casi todas las asociaciones
deben ser navegables por consultas en ambas direcciones.