hibernate - persistencia

198
HIBERNATE - Persistencia relacional para Java Idiomático Documentación de Referencia de Hibernate 3.3.1 Traductor: Gonzalo Díaz Copyright © 2004 Red Hat Middleware, LLC. 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 2 do 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 HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html 1 de 198 17/02/2009 09:25 a.m.

Upload: oliva-valle

Post on 18-Feb-2015

150 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: HIBERNATE - Persistencia

HIBERNATE - Persistencia relacional para Java Idiomático

Documentación de Referencia de Hibernate

3.3.1

Traductor: Gonzalo Díaz

Copyright © 2004 Red Hat Middleware, LLC.

Prefacio1. Introducción a Hibernate

1.1. Prefacio1.2. Parte 1 - La Primera Aplicación Hibernate

1.2.1. La primera clase1.2.2. El archivo de mapeo1.2.3. Configuración de Hibernate1.2.4. Construyendo con Ant1.2.5. Comienzo y ayudantes1.2.6. Cargar y almacenar objetos

1.3. Parte 2 - Mapear asociaciones1.3.1. Mapear la clase Person1.3.2. Una asociación unidireccional basada en un Set1.3.3. Trabajar con la asociación1.3.4. Colección de valores1.3.5. Asociaciones bidireccionales1.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ásico1.4.2. Procesamiento y presentación1.4.3. Despliegue (deploy) y test

1.5. Sumario2. Arquitectura

2.1. Generalidades2.2. Estados de una instancia2.3. Integración con JMX2.4. Soporte de JCA2.5. Sesiones contextuales

3. Configuración3.1. Configuración programática3.2. Obtener una SessionFactory3.3. Conexiones JDBC3.4. Propiedades optativas de configuración

3.4.1. Dialectos de SQL3.4.2. Captura (fetching) por Outer Join3.4.3. Streams Binarios3.4.4. Caché de 2do nivel y caché de consultas3.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 NamingStrategy3.7. Archivo de configuración XML3.8. Integración con los Servidores de Aplicación J2EE

3.8.1. Configuración de una estrategia transaccional3.8.2. SessionFactory ligada a JNDI3.8.3. Manejo del contexto actual de la sesión con JTA3.8.4. Despliegue de JMX

4. Clases Persistentes4.1. Un simple ejemplo de POJO

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

1 de 198 17/02/2009 09:25 a.m.

Page 2: HIBERNATE - Persistencia

4.1.1. Implemente un constructor sin argumentos4.1.2. Porvea una propiedad identificadora (optativo)4.1.3. Prefiera clases que no sean finales (optativo)4.1.4. Declare métodos de acceso y "mutadores" (accessors, mutators) para los campos persistentes.

4.2. Implementar herencia4.3. Implementar equals() y hashCode()4.4. Modelos dinámicos4.5. T-uplizadores4.6. Extensiones

5. Mapeo O/R básico5.1. Declaración del mapeo

5.1.1. El Doctype o "tipo de documento XML"5.1.1.1. EntityResolver

5.1.2. hibernate-mapping5.1.3. class5.1.4. id

5.1.4.1. Generator5.1.4.2. El algoritmo hi/lo5.1.4.3. El argoritmo UUID5.1.4.4. Columnas de identidad y secuencias5.1.4.5. Identificadores asignados5.1.4.6. Claves primarias assignadas por triggers

5.1.5. Generadores de identificador mejorados.5.1.6. Optimización de los generadores de identificador5.1.7. composite-id5.1.8. discriminator5.1.9. version (optativo)5.1.10. timestamp (optativo)5.1.11. property5.1.12. many-to-one5.1.13. one-to-one5.1.14. natural-id5.1.15. component, dynamic-component5.1.16. properties5.1.17. subclass5.1.18. joined-subclass5.1.19. union-subclass5.1.20. join5.1.21. key5.1.22. elementos column y formula5.1.23. import5.1.24. any

5.2. Tipos de Hibernate5.2.1. Entidades y "value types"5.2.2. "Value types" básicos5.2.3. "Value types" hechos a medida

5.3. Mapear una misma clase más de una vez5.4. Identificadores de SQL entrecomillados5.5. Alternativas de meta-datos

5.5.1. Usar marcadores de XDoclet5.5.2. Usar anotaciones de JDK 5.0

5.6. Propiedades generadas5.7. Objetos auxiliares de base de datos

6. Mapeo de Colecciones6.1. Colecciones persistentes6.2. Mapeo de colecciones

6.2.1. Claves foráneas de las colecciones6.2.2. Elementos de la colección6.2.3. Colecciones indexadas6.2.4. Colecciones de valores y asociaciones de-muchos-a-muchos6.2.5. Asociaciones de-uno-a-muchos

6.3. Mapeos de colección avanzados6.3.1. Colecciones ordenadas

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

2 de 198 17/02/2009 09:25 a.m.

Page 3: HIBERNATE - Persistencia

6.3.2. Asociaciones bidireccionales6.3.3. Asociaciones bidireccionales con colecciones indexadas6.3.4. Asociaciones ternarias6.3.5. Usar una <idbag>

6.4. Ejemplos de colecciones7. Mapeo de asociaciones

7.1. Introducción7.2. Asociaciones unidireccionales

7.2.1. de-muchos-a-uno7.2.2. de-uno-a-uno7.2.3. de-uno-a-muchos

7.3. Asociaciones unidireccionales con tablas de unión7.3.1. de-uno-a-muchos7.3.2. de-muchos-a-uno7.3.3. de-uno-a-uno7.3.4. de-muchos-a-muchos

7.4. Asociaciones bidireccionales7.4.1. de-uno-a-muchos / de-muchos-a-uno7.4.2. de-uno-a-uno

7.5. Asociaciones bidireccionales con tablas de unión7.5.1. de-uno-a-muchos / de-muchos-a-uno7.5.2. de-uno-a-uno7.5.3. de-muchos-a-muchos

7.6. Mapeos de asociacoones más complejas8. Mapeo de componentes

8.1. Objetos dependientes8.2. Colecciones de objetos dependientes8.3. Comonentes usados como índices de un Map8.4. Componentes usados como identificadores compuestos8.5. Componentes dinámicos

9. Mapeo de herencia9.1. Las tres estrategias

9.1.1. Una tabla por jerarquía de clases9.1.2. Una tabla por subclase9.1.3. Una tabla por subclase, usando un discriminador9.1.4. Mezclar "una tabla por jerarquía de clases" con "una tabla por subclase"9.1.5. Una tabla por cada clase concreta9.1.6. Una tabla por cada clase concreta, usando polimorfismo implícito9.1.7. Mezclar polimorfismo implícito con otras estrategias de mapeo de herencia

9.2. Limitaciones10. Trabajar con objetos

10.1. Estados de un objeto de Hibernate10.2. Hacer que los objetos se vuelvan persistentes10.3. Cargar un objeto10.4. Consultas

10.4.1. Ejecutar consultas10.4.1.1. Iterar resultados10.4.1.2. Consultas que devuelven T-uplas10.4.1.3. Resultados escalares10.4.1.4. Parámetros vinculados10.4.1.5. Paginación10.4.1.6. Iteración navegable10.4.1.7. Externalizar consultas nombradas

10.4.2. Filtrar colecciones10.4.3. Consultas "Criteria"10.4.4. Consultas en SQL nativo

10.5. Modificar objetos persistentes10.6. Modificar objetos desprnendidos10.7. Detección automática de estado10.8. Borrar objetos persistentes10.9. Replicar un objeto entre dos repositorios de datos distintos10.10. "Flush" de la sesión10.11. Persistencia transitiva

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

3 de 198 17/02/2009 09:25 a.m.

Page 4: HIBERNATE - Persistencia

10.12. Usar metadatos11. Transacciones y concurrencia

11.1. La sesión y el alcance (scope) de las transacciones11.1.1. Unidad de trabajo11.1.2. Conversaciones largas11.1.3.Considerar la identidad de los objetos11.1.4. Problemas comunes

11.2. Demarcación de las transacciones de base de datos11.2.1. Entornos no administrados11.2.2. Usar JTA11.2.3. Manejo de excepciones11.2.4. Expiración de transacciones

11.3. Control optimista de concurrencia11.3.1. Chequeo de versión hecho por la aplicación11.3.2. Sesión extendida y versionado automático11.3.3. Objetos desprendidos y versionado automático11.3.4. Crear un método a medida para el versionado automático

11.4. "Lock" pesimista11.5. Modos de liberación de conecciones

12. Interceptores y eventos12.1. Interceptores12.2. Sistema de eventos12.3. Seguridad declarativa de Hibernate

13. Procesamiento en lotes13.1. Inserciones en lotes13.2. Actualizaciones en lotes13.3. La interfaz StatelessSession13.4. Operaciones del tipo "Lenguaje de Modificacion de Datos" (DML-style)

14. HQL: El lenguaje de consultas de Hibernate14.1. Relevancia de mayúsculas y minúsculas14.2. La cláusula "from"14.3. Asociaciones y "joins"14.4. Formas de la sintaxis de los "joins"14.5. Referirse a la propiedad identificadora14.6. La cláusula "select"14.7. Funciones agregadas14.8. Consultas polimórficas14.9. La cláusua "where"14.10. Expresiones14.11. La cláusula "order by"14.12. La cláusula "group by"14.13. Subconsultas14.14. Ejemplos de HQL14.15. Actualizaciones y borrados en masa14.16. Consejos y trucos14.17. Componentes14.18. Sintaxis del "Constructor de Valor de Fila" (row value constructor)

15. Consultas "Criteria"15.1. Crear una instancia de Criteria15.2. Acotar el resultado15.3. Ordenar el resultado15.4. Asociaciones15.5. Captura dinámica de asociaciones15.6. Consultas "Example"15.7. Proyecciones, agregado y agrupamiento15.8. Consultas y subconsultas desprendidas15.9. Consultas por identificador natural

16. SQL nativo16.1. Usar un SQLQuery

16.1.1. Consultas escalares16.1.2. Consultas con entidades16.1.3. Manipular colecciones y asociaciones16.1.4. Devolver múltiples entidades

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

4 de 198 17/02/2009 09:25 a.m.

Page 5: HIBERNATE - Persistencia

16.1.4.1. Alias y referencias a propiedades16.1.5. Devolver entidades no administradas16.1.6. Manejar herencia16.1.7. Parámetros

16.2. Consultas SQL nombradas16.2.1. Usar return-property para especificar nombres de columna/alias explícitamente16.2.2. Usar procedimientos almacenados (stored procedures) para efectuar consultas

16.2.2.1. Reglas y limitaciones en el uso de procedimientos almacenados16.3. SQL a medida para crear, almacenar y borrar16.4. SQL a medida para cargar

17. Filtrar datos17.1. Filtros de Hibernate

18. Mapeo XML18.1. Trabajar con datos XML

18.1.1. Especificar el mapeo XML y el mapeo de la clase al mismo tiempo18.1.2. Especificar sólo un mapeo XML

18.2. Metadatos del mapeo XML18.3. Manipulación de los datos XML

19. Mejorar la performance19.1. Estrategias de captura (fetch)

19.1.1. Trabajar con asociaciones haraganas19.1.2. Ajustar las estrategias de captura19.1.3. Proxies de las asociaciones de un solo extremo19.1.4. Inicializar colecciones y proxies19.1.5. Usar la captura por lotes19.1.6. Usar la captura mediante subselects19.1.7. Usar la captura de propiedades haragana

19.2. El caché de 2do nivel19.2.1. Mepeos de caché.19.2.2. Estrategia de sólo lectura19.2.3. Estrategia de lecto/escritura19.2.4. Estrategia de lecto/escritura no estricta19.2.5. Estrategia transaccional19.2.6. Compatibilidad entre el proveedor de caché y la estrategia de concurrencia

19.3. Admninistrar los cachés19.4. El caché de consultas (query cache)19.5. Comprender la performance de las colecciones

19.5.1. Taxonomía19.5.2. Las lists, maps, idbags y sets son las colecciones más eficientes de actualizar19.5.3. Las bags y lists son las colecciones inversas más eficientes19.5.4. Borrado en una pasada

19.6. Monitorear la performance19.6.1. Monitorear una SessionFactory19.6.2. Mediciones

20. Guía de las herramientas (Toolset)20.1. Generación automática del esquema de base de datos

20.1.1. Retocar el esquema de base de datos20.1.2. Ejecutar la herramienta20.1.3. Propiedades20.1.4. Usar Ant20.1.5. Actualizaciones incrementales del esquema de base de datos20.1.6. Utilizar Ant para las actualizaciones incrementales del esquema de base de datos20.1.7. Validación del esquema de base de datos20.1.8. Usar Ant para la validación del esquema de base de datos

21. Ejemplo: Padre/Hijo21.1. Nota sobre las colecciones21.2. de-uno-a-muchos bidireccional21.3. Ciclo de vida de las propagaciones en cascada21.4. Propagaciones en cascada y unsaved-value21.5. Conclusión

22. Ejemplo: La aplicación Weblog22.1. Clases persistentes22.2. Mapeos de Hibernate

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

5 de 198 17/02/2009 09:25 a.m.

Page 6: HIBERNATE - Persistencia

22.3. Código Hibernate23. Ejemplo: Mapeos varios

23.1. Empleador/Empleado23.2. Autor/Obra23.3. Cliente/Orden/Producto23.4. Ejemplos misceláneos de asociación

23.4.1. Asociación de-uno-a-uno "con tipo"23.4.2. Ejemplo de clave compuesta23.4.3. de-muchos-a-muchos con atributo compartido de clave compuesta23.4.4. Discriminación basada en el contenido23.4.5. Asociaciones en claves alternativas

24. Prácticas recomendadas

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. Eltérmino "mapeo objeto/relacional" (ORM por sus siglas en inglés) se refiere a esta técnica de "mapear" la representaciónde los datos desde un modelo de objetos hacia un modelo de datos relacional, con un esquema de base de datos basado enSQL.

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 tiposde la base de datos), sino que también provee utilidades para consulta y captura de datos, y puede reducirconsiderablemente el tiempo que, de otra manera, habría que invertir con el manejo manual de datos mediante SQL yJDBC.

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 denegocios en procedimientos almacenados (stored procedures) en la base de datos; es más útil con modelos orientados aobjetos cuya lógica de negocio reside en la capa intermedia. Sin embargo, Hibernate puede ayudarlo a encapsular oeliminar código SQL que sea específico de un proveedor de BD, y ayudará en la tarea usual de traducir desde unarepresentació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 lossiguientes pasos:

Lea el siguiente instructivo: Capítulo 1, Introducción a Hibernate, el cual es una especie de manual coninstrucciones paso a paso. El código fuente del instructivo está incluido en la distribución descargable, en eldirectorio 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 correctospara su base de datos. Desde la consola, situado en el directorio de distribución, tipee ant eg (usando Ant), o desdeWindows, tipee build eg.

Use esta documentación de referencia como su fuente primaria de información. Considere leer Java Persistencewith Hibernate (http://www.manning.com/bauer2) si necesita más ayuda con el diseño de aplicaciones o si prefiereun instructivo paso a paso. También visite http://caveatemptor.hibernate.org y descargue la aplicación de ejemplopara Persistencia de Java con Hibernate.

Las preguntas frecuentes (FAQ, por sus siglas en inglés), son contestadas en el sitio de web de Hibernate.

En el sitio de web de Hibernate hay vínculos a demostraciones de terceros, ejemplos e instructivos.

El Área Comunitaria del sitio de web de Hibernate es un buen recurso acerca de patrones de diseño y variassoluciones 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 deproblemas, 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 connosotros en la lista de correo de programación.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

6 de 198 17/02/2009 09:25 a.m.

Page 7: HIBERNATE - Persistencia

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 conocidocomo "Sistema Empresarial JBoss de Middleware" (JEMS, por sus siglas en inglés).

Capítulo 1. Introducción a Hibernate

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 apasos pequeños, fáciles de comprender. Este instructivo se basa en otro anterior, escrito por Michael Gloegl. Todo elcó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 deambos es nuevo para usted, o no se maneja bien ellos, le recomendamos que comience con una buenaintroducción a estas tecnologías, antes de adentrarse en Hibernate. A la larga, le ahorrará tiempo yesfuerzo.

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 servletspara poder ser ejecutado. El montaje (setup) básico es el mismo que para las instrucciones a continuación.

1.2. Parte 1 - La primera aplicación Hibernate

Supongamos que tenemos una pequeña aplicación de base de datos que puede almacenar eventos a los que queremosasistir, e información acerca del anfitrión (o anfitriones) de dichos eventos. Vamos a usar una base de datos residente enmemoria, llamada HSQLDB, para evitar describir la instalación y configuración de cualquier base de datos en particular.Siéntase en libertad de usar cualquier base de datos con la que esté familiarizado.

Lo primero que tenemos que hacer es montar nuestro entorno de desarrollo, y, específicamente, instalar todas lasdependencias 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 cualsimplemente significa que para usar Hibernate podemos definir nuestras dependencias dentro de él: Hibernate mismodefine 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>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

7 de 198 17/02/2009 09:25 a.m.

Page 8: HIBERNATE - Persistencia

Nota

Básicamente, aquí estamos describiendo el archivo /tutorials/web/pom.xml. Vea el sitio de Maven paramás información.

Consejo

Aunque no es estrictamente necesario, muchos entornos visuales de progrmación (IDEs) ya cuentan conintegración con Maven para leer estos archivos POM, y automáticamente generar el proyecto por usted (locual puede ahorrar mucho tiempo y esfuerzo).

Luego creamos una clase que representa el evento que queremos almacenar en la base de datos.

1.2.1. La primera clase

Nuestra primera clase persistente es un simple JavaBean con algunas propiedades.

package org.hibernate.tutorial.domain;

import java.util.Date;

public class Event { private Long id;

private String title; private Date date;

public Event() {}

public Long getId() { return id; }

private void setId(Long id) { this.id = id; }

public Date getDate() { return date; }

public void setDate(Date date) { this.date = date; }

public String getTitle() { return title; }

public void setTitle(String title) { this.title = title; }}

Se puede ver que esta clase usa la convención estándar de JavBeans para nombrar a sus métodos "setter" y "getter"(escritura y lectura de propiedades, respectivamente). Este diseño es el recomendado - pero no es obligatorio. Hibernatepuede 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 mediantereflexión.

La propiedad identificadora o id contiene un identificador único para un evento en particular. Todas las clases de entidadpersistentes (las hay menos importantes también) necesitarán dicho id, si queremos usar a pleno las capacides deHibernate. De hecho, la mayoría de las aplicaciones (especialmente aplicaciones de web), ya necesitan distinguir objetospor id, así que esta característica debería considerarse una ventaja, más que una limitación. De todos modos, usualmenteno manipulamos directamente la identidad de un objeto, así que el método "setter" del id debería ser privado. SóloHibernate 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 suaplicación.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

8 de 198 17/02/2009 09:25 a.m.

Page 9: HIBERNATE - Persistencia

El constructor sin argumentos es obligatorio para todas las clases persistentes; Hibernate tiene que crear los objetos parausted utilizando Java Reflection. El constructor puede ser privado; sin embargo, para poder generar "proxies" en tiempo deejecución, y para la captura de datos sin la construcción de bytecode, se requiere al menos el nivel de acceso "package" opor defecto.

Ponga este archivo de código fuente Java en un directorio llamado src en el directorio de desarrollo, y dentro del paquetecorrespondiente. El directorio debería verse así:

.+lib <bibliotecas de Hibernate y de terceros>+src +events Event.java

En el paso siguiente, le vamos a informar a Hibernate acerca de esta clase persistente.

1.2.2. El archivo de mapeo

Hibernate necesita saber cómo cargar y almacenar objetos de la clase persistente. Aquí es donde entra en juego el archivode mapeo. Éste le dice a Hibernate a qué tabla en qué base de datos tiene que acceder, y qué columnas de dicha tablatiene que utilizar.

La estructura básica del archivo de mapeo se ve así:

<?xml version="1.0"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>[...]</hibernate-mapping>

Nótese que la DTD de Hibernate es bastante sofisticada. La puede usar para autocompletar elementos de mapeo yatributos de XML en su indetfaz gráfica (IDE). También puede abrir la el archivo de la DTD en su procesador de texto - esla forma más fácil de tener una visión general de todos los elementos, y de ver los valores por defecto, junto con algunoscomentatios. 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: hayclases dependientes, como veremos luego, que no son entidades de primer nivel) necesitan dicho mapeo, a una tabla en labase de datos SQL.

<hibernate-mapping>

<class name="events.Event" table="EVENTS">

</class>

</hibernate-mapping>

Hasta el momento, le hemos dicho a Hibernate cómo persistir y cargar un objeto de la clase Event en la tabla EVENTS,cada instancia representando un registro de la tabla. Ahora continuamos con el mapeo del identificador único a la claveprimaria de la tabla. Por añadidura, como no queremos preocuparnos por manipular dicho identificador, configuramos una"estrategia de generación de identificador" de Hibernate para que use una clave primaria "sustituta" (surrogate key).

<hibernate-mapping>

<class name="events.Event" table="EVENTS">

<id name="id" column="EVENT_ID"> <generator class="native"/> </id> </class>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

9 de 198 17/02/2009 09:25 a.m.

Page 10: HIBERNATE - Persistencia

</hibernate-mapping>

El elemento id es la declaracuión de la propiedad indentificadora. El atributo name="id" declara el nombre de lapropiedad Java - Hibernate usará los métodos getter y setter para acceder a ella. El atributo "column" le dice a Hibernatequé columna de la tabla EVENTS usamos como clave primaria. El elemento anidado generator especifica la estrategia parala generación de identificador. En este caso, usamos native, la cual elige la mejor estrategia dependiendo de la BD ydialecto configurados. Hibernate soporta tanto identificadores generados por la base de datos, como globalmente únicos, oasignados por la aplicación (o generados por cualquier estrategia para la cual usted haya escrito una extensión).

Finalmente, incluimos declaraciones para las propiedades persistentes en el archivo de mapeo. Por defecto, ninguna de laspropiedades de la clase se considera persistente.

<hibernate-mapping>

<class name="events.Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <generator class="native"/> </id> <property name="date" type="timestamp" column="EVENT_DATE"/> <property name="title"/> </class>

</hibernate-mapping>

Igual que con el elemento id, el atributo name del elemento property le dice a Hibernate qué métodos getter y setter usar.Así que, en este caso, Hibernate buscará getDate()/setDate(), así como getTitle()/setTitle().

¿Por qué el mapeo de la propiedad date incluye el atributo column, pero el title no? A falta del atributo column,Hibernate por defecto usa el nombre de la propiedad como nombre de columna. Esto funciona para title. Pero date esuna 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 demapeo no son, como es de esperarse, tipos Java. Tampoco son tipos SQL. A estos tipos se los llama Tipos de mapeo deHibernate, conversores que podemos traducir de tipos Java a SQL y viceversa. De nuevo, Hibernate tratará de determinarla conversión adecuada e incluso el tipo mismo, si el atributo type no se especifica en el mapeo. En algunos casos, estadetección automática (que usa Java Reflection) puede no generar el valor por defecto que usted esperaba o necesita. Ésees el caso con la propiedad date. Hibernate no puede saber si la propiedad, que es del tipo java.util.Date, debería sermapeada a una columna timestamp, o a una columna time. En este caso, mantengamos la información completa (de día yhora) 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 laclase Event. El nombre de los archivos de mapeo puede ser arbitrario, pero los sufijos hbm.xml son una convención en lacomunidad 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

Continuamos con la configuración principal de Hibernate

1.2.3. Configuración de Hibernate

Ahora tenemos ubicados una clase persistente y su archivo de mapeo. Es el momento de configurar Hibernate mismo.Antes de hacerlo, necesitamos una base de datos. HSQLBD es una base de datos basada en Java; puede ser descargada delsitio de web de HSQL DB (http://hsqldb.org/). En ralidad, usted sólo necesita hsqldb.jar de dicha descarga. Coloqueeste 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á susarchivos de datos. Ahora, haga arrancar la base de datos ejecutando: java -classpath ../lib/hsqldb.jarorg.hsqldb.Server en este directorio de datos. Usted podrá ver que arranca y está ligada a un socket TPC/IP. Ahí esdonde nuestra aplicacíón se conectará luego. Si quiere comenzar con una base de datos nueva en el transcurso de esteinstructivo, cierre la base de datos HSQL DB pulsando CTRL + C en la ventana, borre todos los archivos en el

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

10 de 198 17/02/2009 09:25 a.m.

Page 11: HIBERNATE - Persistencia

subdirectorio data/, y arranque HSQL DB nuevamente.

Hibernate es la capa de su aplicación que se conecta con esta base de datos, así que necesita información de conexión. Lasconexiones se hacen mediante un pool de conexiones JDBC, el cual también tenemos que configurar. La distribución deHibernate contiene varias herramientas de código abierto (open source) que generan pool de conexiones , pero para esteinstructivo usaremos el pool que ya viene incorporado en Hibernate. Dése cuenta de que, si quiere usar un pool deconexiones JDBC de mayor calidad (para aplicaciones ya instaladas en producción) hecho por terceros, deberá copiar lasbibliotecas que hagan falta en el classpath, y usar propiedades de conexión diferentes.

Para la configuración de Hibernate, podemos usar un simple archvo hibernate.properties, un archivohibernate.cfg.xml un tanto más sofisticado, o incluso una configuración totalmente programática. La mayoría prefierael archivo de configuración XML.

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>

<!-- datos de conexión de la BD --> <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>

<property name="connection.url">jdbc:hsqldb:hsql://localhost</property> <property name="connection.username">sa</property> <property name="connection.password"></property>

<!-- pool de conexiones JDBC (usamos el que ya viene incorporado) -->

<property name="connection.pool_size">1</property>

<!-- dialecto SQL --> <property name="dialect">org.hibernate.dialect.HSQLDialect</property>

<!-- habilita el manejo automátio de contexto de sesión por parte de Hibernate -->

<property name="current_session_context_class">thread</property>

<!-- inhabilita el caché de 2do nivel --> <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>

<!-- imprime todo el SQL ejecutado en la salida estándar --> <property name="show_sql">true</property>

<!-- borra y recrea la BD en cada arranque --> <property name="hbm2ddl.auto">create</property>

<mapping resource="events/Event.hbm.xml"/>

</session-factory>

</hibernate-configuration>

Fíjese en que este archivo de configuración XML usa una DTD distinta. Configuramos la SessionFactory de Hibernate -una fabrica global responsable de una base de datos en particular. Si tiene varias base de datos, use varias configuracionesde <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 depersistencia 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, conla ayuda de la tarea de Ant SchemaExport. Fnalmente, agregamos el o los archivo de mapeo para las clases persistentes ala configuración.

Copie este archivo en el directorio de fuentes (src), de manera que quede en la raíz del classpath. Hibernate buscaautomáticamente un arhivo llamado hibernate.cfg.xml en la raíz del classpath, al arrancar.

1.2.4. Construir con Ant

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

11 de 198 17/02/2009 09:25 a.m.

Page 12: HIBERNATE - Persistencia

Ahora vamos a construir (build) esta aplicación instructiva usando Ant. Deberá tener Ant ya instalado - obténgalo de laPágina de descarga de Ant. Aquí no vamos a discutir cómo instalar Ant. Por favor refiérase al Manual de Ant. Después dehaber instalado Ant, podemos crear el archivo de construcción de Ant (build file), que se llamará build.xml, y estarásituado directamente en el directorio de desarrollo.

Un archivo de construcción Ant básico se ve así:

<project name="hibernate-tutorial" default="compile">

<property name="sourcedir" value="${basedir}/src"/> <property name="targetdir" value="${basedir}/bin"/> <property name="librarydir" value="${basedir}/lib"/>

<path id="libraries"> <fileset dir="${librarydir}"> <include name="*.jar"/> </fileset> </path>

<target name="clean">

<delete dir="${targetdir}"/> <mkdir dir="${targetdir}"/> </target>

<target name="compile" depends="clean, copy-resources"> <javac srcdir="${sourcedir}" destdir="${targetdir}" classpathref="libraries"/> </target>

<target name="copy-resources"> <copy todir="${targetdir}"> <fileset dir="${sourcedir}"> <exclude name="**/*.java"/> </fileset> </copy>

</target>

</project>

Esto le dice a Ant que agregue todos los archivos del el directorio lib que terminen en .jar al classpath que usemos para lacompilación. También copiará todos los archivos fuente no-Java al directorio de destino (target), por ejemplo, los archivosde configuración y mapeo.. Si ejecuta Ant ahora, debería obtener la siguiente salida:

C:\hibernateTutorial\>antBuildfile: build.xml

copy-resources: [copy] Copying 2 files to C:\hibernateTutorial\bin

compile: [javac] Compiling 1 source file to C:\hibernateTutorial\bin

BUILD SUCCESSFULTotal time: 1 second

1.2.5. Arranque y ayudantes

Es hora de cargar y grabar algunos objetos Event,pero primero debemos completar la instalación (setup) con algo decódigo "infraestructural". Tenemos que hacer arrancar Hibernate. Dicho arranque incluye construir un objetoSessionFactory global, y almacenarlo en algún lugar de fácil acceso para el código de la aplicación. UnaSessionFactory puede abrir nuevos objetos Session. Cada objeto Session representa una "unidad de trabajo" de unsolo 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 aSessionFactory sea más conveniente. Veamos cómo implementarla:

package util;

import org.hibernate.*;

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

12 de 198 17/02/2009 09:25 a.m.

Page 13: HIBERNATE - Persistencia

import org.hibernate.cfg.*;

public class HibernateUtil {

private static final SessionFactory sessionFactory;

static { try { // Cree la SessionFactory para hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { // Asegúrese de loguear la excepción, dado que puede ser "tragada" System.err.println("Initial SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } }

public static SessionFactory getSessionFactory() { return sessionFactory; }

}

Esta clase no sólo produce la SessionFactory global, en un inicializador estático (invocado una sola vez por la JVMcuando la clase se carga), sino que oculta el hecho de que se emplea un singleton estático. Podría haber estado buscando laSessionFactory en el JNDI de un servidor de aplicaciones, , por ejemplo,.

Si usted le da un nombre a la SessionFactory en su archivo de configuración, Hibernate en realidad intentará vincularla aJNDI tras haber sido creada. Para omitir este código completamente, usted también podría usar "despliegue JMX" y dejarque el un contenedor habilitado para JMX instancie y construya un HibernateService en la JNDI. Estas opcionesavanzadas se discuten en la documentación de referencia de Hibernate.

Coloque HibernateUtil.java en el directorio de código fuente, en un paquete al lado de events:

.+lib <Hibernate y las bibliotecas de terceros>

+src +events Event.java Event.hbm.xml +util HibernateUtil.java hibernate.cfg.xml+databuild.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 losprogramadores prefiere Log4j. Copie log4j.properties de la distribución de Hibernate (está en el directorio etc/) a sudirectorio src, al lado de hibernate.cfg.xml. Dele un vistazo a la configuración de ejemplo, y cambie los valores sidesea 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.

1.2.6. Cargar y almacenar objetos

Finalmente podemos usar Hibernate para cargar y grabar objetos . Escribimos una clase EventManager con un métodomain().

package events;import org.hibernate.Session;

import java.util.Date;

import util.HibernateUtil;

public class EventManager {

public static void main(String[] args) {

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

13 de 198 17/02/2009 09:25 a.m.

Page 14: HIBERNATE - Persistencia

EventManager mgr = new EventManager();

if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date()); }

HibernateUtil.getSessionFactory().close(); }

private void createAndStoreEvent(String title, Date theDate) {

Session session = HibernateUtil.getSessionFactory().getCurrentSession();

session.beginTransaction();

Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate);

session.save(theEvent);

session.getTransaction().commit(); }

}

Creamos un objeto Event, y se los pasamos a Hibernate. Ahora Hibernate se encarga del SQL, y ejecuta INSERTs en labase 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 correspondenciauno-a-uno entre una sesión de Hibernate y una transacción de BD. Para "escudar" nuestro codigo respecto del sistemasubyacente de transacciones (en este caso, sólo JDBC, pero podría haber sido JTA), usamos la API para transacciones queestá disponible en la clase Session.

¿Qué hace sessionFactory.getCurrentSession()? Primero, a este método se lo puede llamar desde dondequiera, ycuantas veces se desee, una vez que obtenemos una SessionFactory. (fácilmente, gracias a la HibernateUtil). El códigogetCurrentSession() siempre devuelve la unidad "actual" de trabajo. ¿Recuerda que configuramos una opción convalor "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 elalcance (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, yla cierra por usted. Si usted llama getCurrentSession() de nuevo, obtiene una nueva sesión y comienza una nuevaunidad de trabajo. El modo de programación "ligado a threads" (thread-bound), es la forma más difundida de usarHibernate, dado que permite una distribución en capas muy flexible: el código de delimitación de transacciones puedesepararse 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 dedatos, o varias? El ejemplo precedente usa una sesión para una operación. Esto es simple casualidad, el ejemplo no es losuficientemente 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 sudelimitación. También hemos salteado cualquier manejo de errores en el ejemplo precedente.

Para ejecutar esta primera rutina, tenemos que agregar una "target" invocable al archivo de construcción de Ant.

<target name="run" depends="compile">

<java fork="true" classname="events.EventManager" classpathref="libraries"> <classpath path="${targetdir}"/> <arg value="${action}"/> </java>

</target>

El valor del argumento "action" se asigna en la línea de comandos cuando esta target se invoca.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

14 de 198 17/02/2009 09:25 a.m.

Page 15: HIBERNATE - Persistencia

C:\hibernateTutorial\>ant run -Daction=store

Después de la compilación, usted debería ver que que Hibernate arracna, y, dependiendo de su configuración, un montónde 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. Paraver los valores de dichos parámetros, o para reducir la locuacidad del archivo de log, revise su archivolog4j.properties.

Ahora, querríamos tanbién listar los eventos almacenados, así que agregamos una opción el el método principal:

if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date());}else if (args[0].equals("list")) { List events = mgr.listEvents(); for (int i = 0; i < events.size(); i++) { Event theEvent = (Event) events.get(i); System.out.println("Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()); }}

También agregamos un nuevo método listEvents() method:

private List listEvents() {

Session session = HibernateUtil.getSessionFactory().getCurrentSession();

session.beginTransaction();

List result = session.createQuery("from Event").list();

session.getTransaction().commit();

return result;}

Lo que hicimos aquí, es usar el lenguaje de consultas de Hibernate (HQL, por sus siglas en inglés) para cargar todos losobjetos Event que existen en la base de datos. Hibernate generará el código SQL que haga falta, lo enviará la base dedatos, y poblará los objetos Event con los datos que sean devueltos. Se pueden crear consultas SQL mucho más complejascon HQL, por supuesto.

Ahora, para ejecutar y chequear todo esto, siga estos pasos:

Ejecute ant run -Daction=store para almacenar algo en la base de datos y, por supuesto, para previamentegenerar el esquema de base de datos mediante hbm2ddl.

Ahora inhabilite hbm2ddl (convirtiendo la propiedad en un comentario) en el archivo hibernate.cfg.xml. Por logeneral, sólo se la deja habilitada cuando se efectúa un "unit testing continuo", pero en este caso, dejarla habilitadaborraría todo lo que usted haya almacenado hasta ese momento. (el valor de hbm2ddl="create" se traduce como"haga un DROP de todas las tablas del esquema, y recree todas las tablas cuando la SessionFactory sea construida")

Si usted ahora invocara Ant con -Daction=list, debería ver los eventos que haya almacenado hasta ese moento. Porsupuesto, 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 tipoTable not found . De todos modos, si usted sigue los pasos que acabamos de describir cuidadosamente, no tendrá esteproblema, ya que hbm2ddl crea el esquema de base de datos la primera vez, y las veces subsiguientes en que la aplicaciónrecomienza utilizan dicho esquema. Si usted en algún momento cambia algo del mapeo o del esquema, debe rehabilitarhbm2dll nuevamente para recrear la BD.

1.3. Parte 2 - Mapear asociaciones

Hemos mapeado una clase persistente a una tabla. Ahora, partiendo de esta base, agreguemos algunas asociaciones de

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

15 de 198 17/02/2009 09:25 a.m.

Page 16: HIBERNATE - Persistencia

clase. Primero, agregaremos algunas personas a nuestra aplicación, y almacenaremos una lista de eventos en los cualesparticipan.

1.3.1. Mapear la clase Person

El primer bosquejo de la clase Person es simple:

package events;

public class Person {

private Long id; private int age; private String firstname; private String lastname;

public Person() {}

// métodos "getter" y "setter" de acceso, y setter privado para 'id'

}

Cree un nuevo archivo de mapeo llamado Person.hbm.xml (y no olvide la referencia a la DTD en la parte superior):

<hibernate-mapping>

<class name="events.Person" table="PERSON">

<id name="id" column="PERSON_ID"> <generator class="native"/> </id> <property name="age"/> <property name="firstname"/> <property name="lastname"/>

</class>

</hibernate-mapping>

Por último, agreguemos el nuevo archivo de mapeo a la configuración de Hibernate.

<mapping resource="events/Event.hbm.xml"/><mapping resource="events/Person.hbm.xml"/>

Y ahora crearemos una asociación entre estas dos entidades, Obviamente, las personas pueden participar en eventos, y loseventos tienen participantes. Las cuestiones de diseño con las que tenemos que lidiar son: multiplicidad, direccionalidad, ycomportamiento de las colecciones.

1.3.2. Una asociación unidireccional basada en un Set

Agregaremos una colección de eventos a la clase Person. De este modo podremos navegar cómodamente los eventos parauna 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 esrelevante para nosotros.

Necesitamos una asociación unidireccional y de varios valores, que pueda ser implementada con un Set. Escribamos elcódigo para esto en las clases de Java, y luego mapeémoslas.

public class Person {

private Set events = new HashSet();

public Set getEvents() { return events; }

public void setEvents(Set events) { this.events = events; }

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

16 de 198 17/02/2009 09:25 a.m.

Page 17: HIBERNATE - Persistencia

}

Antes de mapear esta asociación, piense en el otro lado (los eventos). Claramente, podemos simplemente mantener estaasociación unidireccional. O si no, podríamos crear otra colección en la clase Event, si deseamos navegar en formabidireccional, es decir, anEvent.getParticipants(). Esto no es estrictamente necesario, desde un punto de vistafuncional, 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 multiplicidadde la asociación: hay "muchos valores" desde ambos lados. A esto se lo llama una asociación "de-muchos-a-muchos"(many-to-many). Por esto, usamos el mapeo many-to-many de Hibernate.

<class name="events.Person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="native"/> </id>

<property name="age"/> <property name="firstname"/> <property name="lastname"/>

<set name="events" table="PERSON_EVENT"> <key column="PERSON_ID"/> <many-to-many column="EVENT_ID" class="events.Event"/> </set>

</class>

Hibernate soporta todo tipo de mapeos de coleccíón, siendo el "set" el más común. Para una asociación "many-to-many",o relación entre entidades, se necesita una tabla de asociación. Cada registro de esta tabla simboliza un vínculo entre unapersona y un evento. El nombre de esta tabla se configura con el atributo table del elemento set. La columnaindentificadora por el lado de las personas, se define con el elemento <key>, el nombre de la columna por el lado de loseventos, con el atributo column del <many-to-many>. Usted también debe decirle a Hibernate la clase de objetos de sucolección.

El esquema de base de datos para este esquema es, entonces:

_____________ __________________ | | | | _____________ | EVENTS | | PERSON_EVENT | | | |_____________| |__________________| | PERSON | | | | | |_____________| | *EVENT_ID | <--> | *EVENT_ID | | | | EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | | TITLE | |__________________| | AGE | |_____________| | FIRSTNAME | | LASTNAME | |_____________|

1.3.3. Trabajar con asociaciones

Juntemos a alguna gente con sus eventos, en un nuevo método de EventManager:

private void addPersonToEvent(Long personId, Long eventId) {

Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction();

Person aPerson = (Person) session.load(Person.class, personId); Event anEvent = (Event) session.load(Event.class, eventId);

aPerson.getEvents().add(anEvent);

session.getTransaction().commit();}

Después de cargar una Person y un Event, simplemente modifique la colección usando los métodos normales de lascolecciones. Como puede usted ver, no hay llamados explícitos a update() o a save(), Hibernate detectaautomáticamente que la colección ha sido modificada y necesita ser actualizada. Esto se llama "dirty checkingautomático", y usted puede experimentar con ello cmabiando la propiedad "date" en cualquiera de sus objetos. Siempre ycuando se mantengan en un estado persistente (es decir, ligados a una sesión de Hibernate, por haber sido cargados o

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

17 de 198 17/02/2009 09:25 a.m.

Page 18: HIBERNATE - Persistencia

grabados en una unidad de trabajo), Hibernate monitoreará cualquier cambio y ejecutará SQL entre bambalinas. Elproceso de sincronizar el estado de lo que está en memoria con la base de datos, generalmente al final de la unidad detrabajo, se denomina "nivelar, desagotar" la sesión (en inglés, flush). En nuestro código, la unidad de trabajo termina conun "commit" (o "rollback") de la transacción de base de datos, tal como se define por la opción de configuración threadpara la clase CurrentSessionContext.

Se podría, por supuesto, cargar personas y eventos en distintas unidades de trabajo. O modificar un objeto fuera de unasesión, cuando no está en estado persistente (si lo estuvo anteriormente, este estado se llama "desprendido", en inglésdetached). Incluso una colección se puede modificar estando en este estado desprendido.

private void addPersonToEvent(Long personId, Long eventId) {

Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction();

Person aPerson = (Person) session .createQuery("select p from Person p left join fetch p.events where p.id = :pid") .setParameter("pid", personId) .uniqueResult(); // Eager fetch the collection so we can use it detached

Event anEvent = (Event) session.load(Event.class, eventId);

session.getTransaction().commit();

// fin de la primera unidad de trabajo

aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached

// comienzo de la segunda unidad de trabajo

Session session2 = HibernateUtil.getSessionFactory().getCurrentSession(); session2.beginTransaction();

session2.update(aPerson); // reasociación de aPerson, que estaba desprendida

session2.getTransaction().commit();}

El llamado a update convierte de nuevo en persistente un objeto que estaba desprendido. Se podría decir que lo liga a unanueva unidad de trabajo, de manera que cualquier modificación que se le hubiera hecho mientras estaba desprendidopueda ser grabada en la base de datos. Esto incluye cualquier modificación que se le hubiera hecho a la colección de eseobjeto 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 supropia aplicación. Por ahora, simplemente complete este ejercicio agregando una nueva acción al método main() deEventManager, e invóquelo desde la línea de comandos. Si necesita los identificadores de una persona y de un evento, elmétodo save() los devuelve (tal vez usted deba modificar alguno de los métodos previos, para que devuelvan dichoidentificador).

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 losint o String. A esas clases las llamamos value types, y sus instancias dependen de una entidad en particular. Lasinstancias 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, estosvalue types pueden ser encontrados no solamente en la JDK. De hecho, en una aplicación Hibernate todas las clases JDKson consideradas "value types", pero usted puede escribir sus propias clases dependientes, para representar una direccióno 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 dereferencias a otras entidades, pero en el código Java ambas se ven casi igual.

1.3.4. Colección de valores

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

18 de 198 17/02/2009 09:25 a.m.

Page 19: HIBERNATE - Persistencia

Agreguemos una colección de objetos "value type" a la entidad Person. Queremos almacenar direcciones de correoelectrónico (email), de manera que el tipo que usaremos es String, y la colección, de nuevo un Set.

private Set emailAddresses = new HashSet();

public Set getEmailAddresses() { return emailAddresses;}

public void setEmailAddresses(Set emailAddresses) { this.emailAddresses = emailAddresses;}

El mapeo de este Set:

<set name="emailAddresses" table="PERSON_EMAIL_ADDR">

<key column="PERSON_ID"/> <element type="string" column="EMAIL_ADDR"/></set>

La diferencia, comparada con el mapeo anterior, es la parte element, la cual le dice a Hibernate que la colección nocontiene referencias a otra entidad, sino a una colección de elementos de tipo string (al estar con minúscula nos damoscuenta de que es un tipo/conversor de Hibernate). Une vez más, el atributo table del set determina el nombre de la tablapara la colección. El elemento key define el nombre de columna para la clave foránea, El atributo column de "element"define en dónde estas cadenas van a ser almacenadas.

Echémosle un vistazo al nuevo esquema de DB

_____________ __________________ | | | | _____________ | EVENTS | | PERSON_EVENT | | | ___________________ |_____________| |__________________| | PERSON | | | | | | | |_____________| | PERSON_EMAIL_ADDR | | *EVENT_ID | <--> | *EVENT_ID | | | |___________________| | EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID | | TITLE | |__________________| | AGE | | *EMAIL_ADDR | |_____________| | FIRSTNAME | |___________________| | LASTNAME | |_____________|

Se puede ver que la clave primaria de la tabla de la colección es, en realidad, una clave compuesta, que usa ambascolumnas. Esto también implica que no puede haber direcciones de email duplicadas para una misma persona, lo cual esprecisamente la semántica de un conjunto o "Set" en Java.

Ahora podemos intentar agregar elementos a esta colección, igual que como hicimos antes al vincular personas y eventos.El código Java es el mismo:

private void addEmailToPerson(Long personId, String emailAddress) {

Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction();

Person aPerson = (Person) session.load(Person.class, personId);

// el getEmailAddresses() podría disparar una carga "haragana" de la colección aPerson.getEmailAddresses().add(emailAddress);

session.getTransaction().commit();}

Esta vez, no utilizamos una consulta con fetch para inicializar la colección. De ahí que la invocacíón de su método "getter"disparará un SELECT adicional para inicializarla, de manera que podamos agregarle un elemento. Monitoree el log deSQL e intente optimizar esto con un "eager fetch" (captura ansiosa).

1.3.5. Asociaciones bidireccionales

Acto seguido, vamos a mapear una asociación bidireccional, es decir, vamos a hacer que la asociación funcione desde

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

19 de 198 17/02/2009 09:25 a.m.

Page 20: HIBERNATE - Persistencia

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 necesitacosas como una "dirección de navegación", los datos pueden ser adquiridos de cualquier manera posible.

Primero, agregue una colección de participantes al código de la clase Event.

private Set participants = new HashSet();

public Set getParticipants() { return participants;}

public void setParticipants(Set participants) { this.participants = participants;}

Ahora, mapee también este lado de la asociación, en Event.hbm.xml.

<set name="participants" table="PERSON_EVENT" inverse="true"> <key column="EVENT_ID"/> <many-to-many column="PERSON_ID" class="events.Person"/></set>

Como puede ver, éstos son mapeos set normales en ambos documentos de mapeo. Fíjese en que los nombres de columnaen ambos documentos han sido alternados. La adición más importante es el atributo inverse="true" en el elemento setdel 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 obtenerinformació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ínculobidireccional entre las dos entidades.

1.3.6. Trabajar con vínculos bidireccionales

Primero que nada, tenga en cuenta que Hibernate no afecta la semántica normal de Java, ¿Cómo habiamos creado unvínculo entre personas y eventos en el ejemplo unidireccional? Agregando una instancia de Event a la colección dereferencias contenida en una instancia de Person. Así que, obviamente, si queremos que este vínculo funcione también ensentido inverso, tenemos que hacer lo mismo del otro lado: agregar una referencia a Person a la colección en Event. Estode "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ínculosa ambos lados. Por ejemplo, en la clase Person:

protected Set getEvents() { return events;}

protected void setEvents(Set events) { this.events = events;}

public void addToEvent(Event event) { this.getEvents().add(event); event.getParticipants().add(this);}

public void removeFromEvent(Event event) { this.getEvents().remove(event); event.getParticipants().remove(this);}

Fíjese en que los métodos "get" y "et" de la colección ahora son "protected" - esto les permite a las clases en el mismopaquete, y a las subclases, acceder a los métodos, pero les impide a todos los demás inmiscuirse con estas coleccionesdirectamente (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 deasignar correctamente las referencias en ambos lados .Hibernate, sin embargo, no tiene información suficiente como paraorganizar adecuadamente sus llamados a comandos SQL INSERT y UPDATE. Marcar uno de los lados de la asociacióncomo inverse, básicamente le dice a Hibernate que lo ignore, que lo considere como un espejo de lo que ocurre en el otro

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

20 de 198 17/02/2009 09:25 a.m.

Page 21: HIBERNATE - Persistencia

lado. Eso es todo lo que se necesita para que Hibernate resuelva todos los problemas que derivan de transformar unmodelo 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.

1.4. Parte 3 - La aplicación de web "EventManager"

Convirtamos la siguiente discusión en una pequeña aplicación de web.

Una aplicación de web Hibernate utiliza sesiones y transacciones, lo mismo que la aplicacióm autosuficiente que vimosanteriormente. Sin embargo, es conveniente usar algunos patrones de programación ("patterns"). Escribamos un servletEventManagerServlet que pueda listar los eventos alamacenados en la base de datos, y provea un formulario HTMLpara ingresar nuevos eventos.

1.4.1. Escribir el servlet básico

Cree una nueva clase en su directorio de código fuente, en el paquete events:

package events;

// Imports

public class EventManagerServlet extends HttpServlet {

// Servlet code}

Este servlet maneja requests HTTP del tipo GET solamente, así que el método que implementamos es doGet():

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

SimpleDateFormat dateFormatter = new SimpleDateFormat("dd.MM.yyyy");

try { // comienzo nuestra unidad de trabajo HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();

// procesamiento de la solicitud (request) y presentación de la página...

// fin de la unidad de trabajo HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit();

} catch (Exception ex) { HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback(); throw new ServletException(ex); }

}

El "pattern" o patrón de programacíón que estamos aplicando aquí se llama session-per-request, (una sesión por cada"solicitud" o request HTTP). Cuando una nueva request apela al servlet, se abre una nueva sesión de Hibernate medianteel 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 aplicacionesno 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 largode toda la request, es decir, que tenga un "alcance" de toda la sesión. Use getCurrentSession(), de manera que quedeautomáticamente ligada al Thread actual.

A continuación, las acciones posibles de la request son procesadas, y es generada la respuesta HTML. Enseguida nosdedicaremos 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

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

21 de 198 17/02/2009 09:25 a.m.

Page 22: HIBERNATE - Persistencia

transacción. Esto cubre el "pattern" session-per-request. En lugar de escribir código para demarcar la transacción encada request, usted debería crea un Servlet Filter. Refiérase al sitio de web de Hibernate y a la Wiki para más informaciónacerca 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.

1.4.2. Procesamiento y presentación

Implementemos el procesamiento de la solicitud y la presentación de la página.

// escribe el header encabezado HTML

PrintWriter out = response.getWriter();out.println("<html><head><title>Event Manager</title></head><body>");

// maneja las acciones

if ( "store".equals(request.getParameter("action")) ) {

String eventTitle = request.getParameter("eventTitle"); String eventDate = request.getParameter("eventDate");

if ( "".equals(eventTitle) || "".equals(eventDate) ) { out.println("<b><i>Please enter event title and date.</i></b>"); } else { createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate)); out.println("<b><i>Added event.</i></b>"); }}

// imprime la páginaprintEventForm(out);listEvents(out, dateFormatter);

// escribe el pie HTMLout.println("</body></html>");out.flush();out.close();

Concedido, este tipo de escritura de código, mezclando Java y HTML, sería inadmisible en aplicaciones más complejas,tenga en cuenta que sólo estamos ilustrando conceptos básicos de Hibernate en este instructivo. El código imprime unencabezado y un pie de página en HTML. Dentro de la página propiamente dicha, se imprime un formulario (form) para elingreso de eventos. El primer método es trivial, y sólo produce HTML:

private void printEventForm(PrintWriter out) { out.println("<h2>Add new event:</h2>"); out.println("<form>"); out.println("Title: <input name='eventTitle' length='50'/><br/>"); out.println("Date (e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>"); out.println("<input type='submit' name='action' value='store'/>"); out.println("</form>");}

El método listEvents() usa la sesión de Hibernate ligada al Thread actual para ejecutar una consulta:

private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) {

List result = HibernateUtil.getSessionFactory().getCurrentSession().createCriteria(Event.class) if (result.size() > 0) { out.println("<h2>Events in database:</h2>"); out.println("<table border='1'>"); out.println("<tr>"); out.println("<th>Event title</th>"); out.println("<th>Event date</th>"); out.println("</tr>"); for (Iterator it = result.iterator(); it.hasNext();) { Event event = (Event) it.next(); out.println("<tr>"); out.println("<td>" + event.getTitle() + "</td>"); out.println("<td>" + dateFormatter.format(event.getDate()) + "</td>"); out.println("</tr>"); } out.println("</table>"); }

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

22 de 198 17/02/2009 09:25 a.m.

Page 23: HIBERNATE - Persistencia

}

Finalmente, la acción store es despachada al método createAndStoreEvent(), el cual usa la sesión del Thread actual.

protected void createAndStoreEvent(String title, Date theDate) { Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate);

HibernateUtil.getSessionFactory().getCurrentSession().save(theEvent);}

Eso es todo. El servlet está completo. Una request al servlet será procesada en una única sesión y transacción. Tal comoocurrió en el ejemplo anterior, Hibernate puede ligar estos objetos al Thread actual de ejecución. Esto le da a usted lalibertad de acceder a la SessionFactory de cualquier manera que guste. Normalmente, usted usaría un diseño mássofisticado, 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 deHibernate para más ejemplos.

1.4.3. Despliegue (deploy) y test

Para desplegar (deploy) esta aplicación, tiene que crear un archivo ".war". Agregue la siguiente target de Ant a su archivobuild.xml:.

<target name="war" depends="compile"> <war destfile="hibernate-tutorial.war" webxml="web.xml">

<lib dir="${librarydir}"> <exclude name="jsdk*.jar"/> </lib>

<classes dir="${targetdir}"/> </war></target>

Esta target crea un archivo llamado hibernate-tutorial.war en su directorio de proyecto. Empaqueta todas lasbibliotecas y el descriptor web.xml, el cual se espera que esté en el directorio base de su proyecto.

<?xml version="1.0" encoding="UTF-8"?><web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4

<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árgueladel sitio de web de Sun y colóquela en su directorio de bibliotecas (lib). De todos modos, se la usa para compilarsolamente, 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 luegocopie el archivo hibernate-tutorial.war en el directorio webapp de Tomcat. Si no tiene Tomcat instalado, descárgueloy siga las instrucciones. No es necesario que cambie nada en la configuración de Tomcat para que este ejemplo ande.

Una vez desplegada y con Tomcat andando, accceda a la aplicación en http://localhost:8080/hibernate-tutorial/eventmanager. Asegúrese de monitorear el log de Tomcar para comprobar si Hibernate se inicializa cuando ocurre laprimera solicitud (request) a su servlet (tiene que ejecutarse el inicializado estático en HibernateUtil), y tambiénasegúrese de obtener una mensaje detallado si ocurre algún error.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

23 de 198 17/02/2009 09:25 a.m.

Page 24: HIBERNATE - Persistencia

1.5. Sumario

Este instructivo cubrió lo básico para escribir una simple aplicación Hibernate autosuficiente, y una pequeña aplicación deWeb.

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 yConcurrencia), la performance de las capturas de datos o "fetch performance" (Capítulo 19, Mejorar la performance), eluso de las API (interfaces de programación) (Capítulo 10, Trabajar con objetos) y las características de las consultas(Sección 10.4, “Consultar”).

No olvide chequear el sitio de web de Hibernate en busca de más instructivos especializados.

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 estan flexible, que soporta muchas estrategias. Vamos a mostrar los dos extremos: la arquitectura "liviana" fuerza a laaplicación a proveer sus propias conexiones JDBC y a gerenciar sus propias tranascciones. Esta arquitectura usa unsubconjunto mínimo de las APIs de Hibernate.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

24 de 198 17/02/2009 09:25 a.m.

Page 25: HIBERNATE - Persistencia

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

He aquí algunas definiciones de los objetos en los diagramas:

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 esreusable 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 repositoriopersistente. 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 objetospor identificador.

Objetos y colecciones persistentes

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 únicacaracterística notable que tienen, es que están asociados con una sesión. En cuanto la sesión se cierra, serándesprendidos, y quedarán listos para ser usados en cualquier capa de la aplicación (por ejemplo, directamente comoobjetos de transmisión de datos o "DTOs", desde y hacia la capa de presentación).

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

25 de 198 17/02/2009 09:25 a.m.

Page 26: HIBERNATE - Persistencia

Objetos y colecciones transitorios y desprendidos

Instancias de clases persistenteses que, por el momento, no están asociadas con una sesión. Pudieron haber sidoinstanciadas por la aplicacíón, y aún no haber sido asociadas con una sesión, o bien haber sido instanciadas por unasesió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 detrabajo. Abstrae a la aplicación de la transacción JDBC, JTA o CORBA subyacente. Una sesíón puede extenderse alo largo de varias transacciones en algunos casos. ¡Pero, sea como sea, el código para demarcar transacciones (yasea 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 serimplementada o extendida por el programador.

Interfaces de extensión

Hibernate ofrece varias interfaces de extensión optativoes, se pueden implementar para personalizar elcomportamiento de la capa de persistencia. Vea la documentación de la API para más detalles.

En la arquitectura "liviana", la aplicación se saltea las APIs de Transaction/TransactionFactory y/oConnectionProvider APIs para dialogar con JTA o JDBC directamente.

2.2. Estados de una instancia

Una instancia de una clase persistente puede estar en uno de tres estados diferentes, los cuales se definen con respecto aun contexto de persistencia: la sesión.

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 claveprimaria) 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 lainstancia ha sido serializada a otro proceso. Tiene una identidad persistente y, tal vez, el correspondiente registro enla base de datos. Hibernate no ofrece ninguna garantía acerca de la relación entre identida persistente e identidadJava.

2.3. Integración con JMX

JMX es el estándar de J2EE para la administración de componentes Java. Hibernate puede ser administrado via un servicioJMX estándar. Nosotros proveemos una implementación de MBean en la distribución:org.hibernate.jmx.HibernateService.

Para un ejemplo sobre cómo desplegar Hibernate como un servicio JMX en el servidor de aplicaciones JBoss, por favorvea la guía del usuario de JBoss. En el servidor de applicaciones JBoss, también se obtienen los siguientes beneficios si se

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

26 de 198 17/02/2009 09:25 a.m.

Page 27: HIBERNATE - Persistencia

despliega (deploy) usando JMX:

Manejo de sesiones: El ciclo de vida de una sesión de Hibernate puede ser automáticamente ligado al alcance(scope) de una transacción JTA. Esto significa que usted ya no necesita abrir y cerrar manualmente las sesiones, deesto se encarga el interceptor de JBoss. Tampoco debe preocuparse ya por demarcar la transacción en su código (amenos que quiera escribir usted mismo una capa de persistencia portátil, use la API Transaction de Hibernate paraeso). 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 deHibernate usando el "descriptor de despliegue" (deployment descriptor), en un archivo EAR o SAR, el cual soportalas opciones de configuración de una SessionFactory de Hibernate. De todos modos, para esto aún se debenombrar 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 laSección 3.4.6, “Estadísticas de Hibernate”

2.4. Soporte de JCA

Hibernate también puede ser configurado como un conector JCA. Por favor, vea el sitio de web para más detalles, peronote que el soporte Hibernate de JCA se considera todavía experimental.

2.5. Sesiones contextuales

La mayoría de las aplicaciones que usan Hibernate necesitan alguna forma de sesión "contextual", donde una sesióndeterminada esté en efecto a lo largo del "scope" o alacance de un contexto determinado. De todos modos, entre lasdistintas aplicaciones, la definición de lo que constituye un "contexto" suele diferir, así como la definición de quéconstituye el contexto "actual". Las aplicaciones que usaban Hibernate antes de la versión a 3.0, tendían a utilizar o bienversiones "caseras" de sesión contextual (basadas en ThreadLocal) o bien utilizaban frameworks de terceras partes (comoSpring o Pico), los cuales proveían sesiones contextuales basadas en proxies e intercepción.

Empezando con la versión 3.0.1, Hibernate incorporó el método SessionFactory.getCurrentSession(). Inicialmente,este método asumía el uso de transacciones JTA, donde la transacción JTA definía tando el alcance como el contexto de lasesión actual.El equipo de Hibernate mantiene que, dada la madurez de las numerosas implementaciones autosuficientes de JTA queexisten hoy día, la mayoría (si no todas) las aplicaciones deberían estar usando JTA para su manejo de transacciones (esténo no desplegadas en un contenedor J2EE). Basándose en eso, las sesiones contextuales JTA son todo lo que usted deberíanecesitar usar.

Sin embargo, a partir de la versión 3.1, el proceso que ocurre por detrás de SessionFactory.getCurrentSession() esconfigurable. Con este fin, una nueva interfaz de extensión (org.hibernate.context.CurrentSessionContext) y unnuevo parámetro de configuración (hibernate.current_session_context_class) han sido agregados, para permitir"enchufar" implementaciones nuevas para definir el alcance y contexto de las sesiones.

Vea los Javadocs acerca de la interfaz org.hibernate.context.CurrentSessionContext, para una descripcióndetallada del contrato que ésta implica. Define un solo método, currentSession(), por el cual la implementación esresponsable de rastrear la sesión contextual actual. Hibernate ya viene con 3 implementaciones incuidas de esta interfaz.

org.hibernate.context.JTASessionContext - las sesiones actuales son localizadas y su alcance definido poruna transacción JTA. El procesamiento aquí es exactamente el mismo que en el antiguo enfoque sólo-JTA. Vea losJavadocs para más detalles.

org.hibernate.context.ThreadLocalSessionContext - las sesiones actuales son localizadas por thread deejecución. De nuevo, vea los Javadocs para más detalles.

org.hibernate.context.ManagedSessionContext - las sesiones actuales son localizadas por thread de ejecución.Sin embargo, usted es responsable por ligar y desligar las instancias de sesión, mediante métodos estáticos en estaclase. Nunca abre, cierra o le aplica flush a una sesión.

Las dos primeras implementaciones proveen un modelo de programación del tipo "una sesión - una transacción de base de

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

27 de 198 17/02/2009 09:25 a.m.

Page 28: HIBERNATE - Persistencia

datos", también llamado session-per-request. El comienzo y fin de una sesión de Hibernate está definido por la duraciónde la transacción de base de datos. Si usted usa demarcación programática de transacciones, en simple JSE sin JTA, se leaconseja que use la API de Transaction de Hibernate, para ocultar el sistema de transacciones subyacente. Si el códigose está ejecutando en un contenedor EJB que soporte CMT, los límites de la transacción son definidos declarativamente, yusted no necesita ninguna transacción ni demarcación de operaciones en el código propiamente dicho. Refiérase alCapítulo 11, Transacciones y Concurrencia para más información y ejemplos de código.

El parámetro de configuración hibernate.current_session_context_class define cuál implementación deorg.hibernate.context.CurrentSessionContext debe ser usada. Note que, por compatibilidad con versionesanteriores, si este parámetro de configuración no se asigna, sino que se configuraorg.hibernate.transaction.TransactionManagerLookup en su lugar, Hibernate usará elorg.hibernate.context.JTASessionContext. Típicamente, el valor de este parámetro simplemente nombra la clase deimplementación a usar; para las tres implementaciones de fábrica existen los respectivos nombres cortos: "jta", "thread" y"managed".

Capítulo 3. Configuración

Como Hibernate está diseñado para operar en varios entornos diferentes, hay un gran número de parámetros deconfiguración. Afortunadamente, la mayoría tiene valores por defecto razonables, e Hibernate es distribuido con unarchivo hibernate.properties de ejemplo en el directorio etc/, que muestra varias de las opciones. Simplementecoloque este ejemplo en su classpath y modifíquelo a medida.

3.1. Configuración programática

Una instancia de org.hibernate.cfg.Configuration representa un conjunto completo de mapeos desde los tipos Javade una aplicación, hacia una base de datos SQL. org.hibernate.cfg.Configuration se usa para construir unaorg.hibernate.SessionFactory inmutable. Los mapeos son compilados a partir de los varios archivos de mapeo XML.

Se puede ontener una instancia de org.hibernate.cfg.Configuration instanciándola directamente, y especificando losarchivos de mapeo XML. Si los archivos de mapeo están en el classpath, utilice addResource():

Configuration cfg = new Configuration() .addResource("Item.hbm.xml") .addResource("Bid.hbm.xml");

Una alternativa (a veces preferible), es especificar la clase mapeada, y dejar que Hibernate encuentre el documento demapeo él solo:

Configuration cfg = new Configuration() .addClass(org.hibernate.auction.Item.class) .addClass(org.hibernate.auction.Bid.class);

Entonces Hibernate buscará archivos de mapeo llamados /org/hibernate/auction/Item.hbm.xml y /org/hibernate/auction/Bid.hbm.xml en el calsspath. Este enfoque elimina la necesidad de "hardcodear" los nombres de archivo.

org.hibernate.cfg.Configuration también permite especificar propiedades de configuración:

Configuration cfg = new Configuration() .addClass(org.hibernate.auction.Item.class) .addClass(org.hibernate.auction.Bid.class) .setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect") .setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test") .setProperty("hibernate.order_updates", "true");

Ésta no es la única manera de pasarle propiedades de configuración a Hibernate. Entre las muchas otras opciones, estánéstas:

Pasarle una instancia de java.util.Properties a Configuration.setProperties().

Colocar un archivo llamado hibernate.properties en un directorio raíz del classpath.

Configurar propiedades "de sistema" (System) usando java -Dproperty=value.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

28 de 198 17/02/2009 09:25 a.m.

Page 29: HIBERNATE - Persistencia

Incluir elementos <property> en hibernate.cfg.xml (se discute más adelante).

hibernate.properties es el enfoque más fácil, si lo que se busca es empezar rápido.

La idea de org.hibernate.cfg.Configuration es ser un objeto que viva durante el "tiempo de arranque" (startup), paraser descartado luego, una vez que la SessionFactory haya sido creada.

3.2. Obtener una SessionFactory

Una vez que todos los mapeos hayan sido revisados por org.hibernate.cfg.Configuration, la aplicación debe obteneruna fábrica o "SessionFactory" para las instancias de org.hibernate.Session. Esta fábrica será compartida por todos losthreads que accedan a la aplicacíón.

SessionFactory sessions = cfg.buildSessionFactory();

Hibernate sí le permite a la aplicación instanciar más de una org.hibernate.SessionFactory. Esto es útil si se estáusando más de una base de datos.

3.3. Conexiones JDBC

Usualmente, usted querrá que la org.hibernate.SessionFactory cree y administre el "fondo común" (pool) de sesionespor usted. Si se adopta este enfoque, abrir una org.hibernate.Session es tan simple como escribir:

Session session = sessions.openSession(); // abre una nueva sesión

No bien se haga algo que requiera acceso a la base de datos, una nueva conexión JDBC será obtenida del "pool".

Para que esto funcione, necesitamos pasarle algunas propiedades de conexión JDBC a Hibernate. Todos los nombres ysemántica de las propiedades Hibernate están definidos en la clase org.hibernate.cfg.Environment. Vamos a describirlos 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 ustedconfigura las siguientes propiedades:

Table 3.1. Hibernate JDBC Properties

Nombre de la propiedad Propósitohibernate.connection.driver_class clase del driver JDBC

hibernate.connection.url URL de JDBC

hibernate.connection.username usuario de la base de datos

hibernate.connection.password clave del usuario de la base de datos

hibernate.connection.pool_size número máximo de conexiones en el "pool"

El algoritmo que ya viene incluido en Hibernate para el "pooling" de conexiones es, sin embargo, bastante rudimentario.Fue concebido más que nada para ayudarlo a dar los primeros pasos, no para ser usado en un sistema de producción, nisiquiera para un test de performance. Ustde debería usar un "pool" de algún tercero para asegurar mayor redimiento yestabilidad. Simplemente reemplace la propiedad hibernate.connection.pool_size con propiedades específicas de laherramienta de "pooling" de su elección. Esto desactivará el "pooling" interno de Hibernate. Por ejemplo, usted podríaelegir C3P0.

C3P0 es una biblioteca para pool de conexiones distribuida junto con Hibernate en el direactorio lib. Hibernate usará suorg.hibernate.connection.C3P0ConnectionProvider para efectuar el "pooling" de conexiones, si usted les asignavalores a las propiedades que empiezan con hibernate.c3p0.*. Si prefiere usar Proxool, refiérase a las propiedadesempaquetadas en hibernate.properties, o al sitio de web de Hibernate para mayor información.

He aquí un ejemplo de un hibernate.properties para C3P0:

hibernate.connection.driver_class = org.postgresql.Driver

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

29 de 198 17/02/2009 09:25 a.m.

Page 30: HIBERNATE - Persistencia

hibernate.connection.url = jdbc:postgresql://localhost/mydatabasehibernate.connection.username = myuserhibernate.connection.password = secrethibernate.c3p0.min_size=5hibernate.c3p0.max_size=20hibernate.c3p0.timeout=1800hibernate.c3p0.max_statements=50hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect

Para usar "pooling" dentro de un servidor de aplicaciones, se debería configurar Hibernate de manera tal, que siempreobtenga las conexiones desde una fuente de datos javax.sql.Datasource registrada usando JNDI. Se necesitarán por lomenos las siguientes propiedades:

Table 3.2. Propiedades para fuente de datos de Hibernate

Nombre de la propiedad Propósitohibernate.connection.datasource nombre JNDI de la fuente de datos

hibernate.jndi.url URL del proveedor de JNDI (optativo)

hibernate.jndi.class clase del InitialContextFactory de JNDI (optativo)

hibernate.connection.username usuario de la base de datos (optativo)

hibernate.connection.password clave del usuario de base de datos (optativo)

He aquí un ejemplo de un archivo hibernate.properties para configurar la fuente de datos JNDI en un servidor deaplicaciones:

hibernate.connection.datasource = java:/comp/env/jdbc/testhibernate.transaction.factory_class = org.hibernate.transaction.JTATransactionFactoryhibernate.transaction.manager_lookup_class = org.hibernate.transaction.JBossTransactionManagerLookuhibernate.dialect = org.hibernate.dialect.PostgreSQLDialect

Las conexiones JDBC que se obtengan de una fuente de datos JNDI participarán automáticamente de las transacciones"manejadas por el contenedor" del servidor de aplicaciones.

También se pueden agregar propiedades de conexión arbitrarias, afijando "hibernate.connection" al nombre de lapropiedad de conexión. Por ejemplo, usted puede especificar una propiedad de conexión charSet (juego de caracteres),usando "hibernate.connection.charSet".

Usted también puede definir su propia estrategia de plugin para obtener conexiones JDBC, implementando la interfazorg.hibernate.connection.ConnectionProvider, y especificando su propia implementación a medida en la propiedadhibernate.connection.provider_class.

3.4. Propiedades optativas de configuración

Hay varias otras propiedades que controlan el comportamiento de Hibernate en tiempo de ejecución. Todas son optativas,y tienen valores por defecto razonables.

Advertencia: algunas de estas propiedades existen "a nivel de sistema" solamente. A las propiedades a nivel de sistemasólo se les puede asignar valores via java -Dproperty=value o hibernate.properties. No se les puede asignar valoresusando las técnicas descritas anteriormente.

Tabla 3.3. Porpiedades de Configuration de Hibernate

Nombre de la propiedad Propósito

hibernate.dialect

El nombre de clase de un org.hibernate.dialect.Dialect que le permita aHibernate generar SQL optimizado para una BD relacional en particular.

por ejemplo classname.completo.del.dialecto

En la mayoría de los casos, Hibernate será capaz de elegir la implementación dedialecto correcta, basándose en los metadatos devueltos por el driver JDBC.

Tabla 3.4. Propiedades Hibernate de JDBC y Conexión

Nombre de la propiedad Propósito

hibernate.jdbc.fetch_size Un valor distinto de 0 determina el tamaño de la captura o "fetch" JDBC (llamaa Statement.setFetchSize()).

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

30 de 198 17/02/2009 09:25 a.m.

Page 31: HIBERNATE - Persistencia

Nombre de la propiedad Propósito

hibernate.jdbc.batch_size

Un valor distinto de 0 habilita el uso de actualizacionesn en lotes (batchupdates) de JDBC2 por parte de Hibernate.

valores posibles se recomienda entre 5 y 30

hibernate.jdbc.batch_versioned_data

Asígnele true a esta propiedad, si su driver JDBC devuelve conteos de fila alejecutar executeBatch() (normalmente, es seguro hacerlo). Entonces,Hibernate usará "batched DML" (lenguaje de creación de datos en lotes) paradatos a los que se les haya asignado automátiacmente número de versión. Elvalor por defecto es false.

valores posibles true | false

hibernate.jdbc.factory_class

Selecciona un org.hibernate.jdbc.Batcher hecho a medida. La mayoría delas aplicaciones no necesita configurar esta propiedad.

por ejemplo classname.of.BatcherFactory

hibernate.jdbc.use_scrollable_resultset

Habilita el uso de resultados JDBC2 navegables (scrollable resultsets) por partede Hibernate. Esta propiedad sólo es necesaria cuando se emplean conexionesJDBC provistas por el usuario. De otro modo, Hibernate usa los metadatos dela conexión.

valores posibles true | false

hibernate.jdbc.use_streams_for_binary

Indica que se usarán streams al leer o escribir código binario, o tiposserializables desde o hacia JDBC. *propiedad a nivel de sistema*

valores posibles true | false

hibernate.jdbc.use_get_generated_keys

Habilita el uso del PreparedStatement.getGeneratedKeys() de JDBC3para capturar claves generadas en forma nativa luego de insertar. Requiere quea Requires JDBC3+ driver y a JRE1.4+, le sea asignado "false" si el driver queusted está usando tiene problemas con los generadores de identificadores. Pordefecto, intenta determinar las capacidades del driver usando los metadatos deconexión.

valores posibles true|false

hibernate.connection.provider_class

El nombre de clase de unorg.hibernate.connection.ConnectionProvider hecho a medida que leprovea conexiones JDBC a Hibernate.

por ejemplo classname.of.ConnectionProvider

hibernate.connection.isolation

Determina el nivel de aislamiento de las transacciones JDBC. Revisejava.sql.Connection para averiguar qué valores tiene sentido asignar aquí,pero note que la mayoría de las BD no soportan todos los niveles deaislamiento (isloation levels), y algunas definen niveles no estándar adicionales.

por ejemplo 1, 2, 4, 8

hibernate.connection.autocommit

Habilita la autocomisión (autocommit) para las conexiones JDBC en pool (nose recomienda).

valors posibles true | false

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

31 de 198 17/02/2009 09:25 a.m.

Page 32: HIBERNATE - Persistencia

Nombre de la propiedad Propósito

hibernate.connection.release_mode

Especifica cuándo Hibernate debería liberar conexiones. Por defecto, unaconexión JDBC es retenida hasta que la sesión es cerrada explícitamente odesconectada. Para fuentes de datos JTA en un servidor de aplicaciones, sedebería usar after_statement para liberar conexiones agresivamente luegode cada llamado a JDBC. Para conexiones que no sean JTA, a menudo tienesentido liberar las conexiones al final de cada transacción, usandoafter_transaction. auto elegirá after_statement para las estrategias JTAy CMT (manejadas por el contenedor), y after_transaction para lasestrategias transaccionales JDBC.

valores posibles auto (el valor por defecto) | on_close | after_transaction| after_statement

Note que este valor sólo afecta sesiones devueltas porSessionFactory.openSession. Para las que se hayan obtenido usandoSessionFactory.getCurrentSession, lo que importa para determinar elmodo de liberación de conexiones, es la implementación que se hayaconfigurado de CurrentSessionContext. Vea la Sección 2.5, “SesionesContextuales”

hibernate.connection.<nombreDeLaPropiedad>

Le pasa el valor de propiedad JDBC <propertyName> aDriverManager.getConnection().

hibernate.jndi.<nombreDeLaPropiedad>

Le pasa la propiedad <nombreDeLaPropiedad> a laInitialContextFactory de JNDI.

Tabla 3.5. Propiedades del caché de Hibernate

Nombre de la propiedad Propósito

hibernate.cache.provider_class

El nombre de clase de un CacheProvider hecha a medida.

por ejemplo classname.of.CacheProvider

hibernate.cache.use_minimal_puts

Optimiza el caché de 2do nivel para minimizar escrituras, al costo derealizar lecturas más frecuentemente. Esta configuración es más útilcon cachés en "cluster", y en Hibernate3 se habilita por defecto concachés en cluster.

valores posibles true|false

hibernate.cache.use_query_cache

Habilita el caché de consultas. Cada consulta (query) debe serindividualmente configurada como "cacheable".

valores posibles true|false

hibernate.cache.use_second_level_cache

Puede ser usado para inhabilitar completamente el caché de 2do nivel,el cual está habilitado por defecto para toda clase que especifique unmapeo <cache>

valores posibles true|false

hibernate.cache.query_cache_factory

El nombre de una interfaz QueryCache personalizada, que por defectoes StandardQueryCache, la cual ya viene de fábrica.

por ejemplo classname.of.QueryCache

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

32 de 198 17/02/2009 09:25 a.m.

Page 33: HIBERNATE - Persistencia

Nombre de la propiedad Propósito

hibernate.cache.region_prefix

Un prefijo a usar con los nombres de región de caché de 2do nivel.

por ejemplo prefix

hibernate.cache.use_structured_entries

Fuerza a Hibernate a almacenar datos en el caché de 2do nivel en unformato más legible.

valores posibles true|false

Tabla 3.6. Propiedades para la configuración de transaciones en Hibernate

Nombre de la propiedad Propósito

hibernate.transaction.factory_class

El nombre de clase de la TransactionFactory a usar conHibernate (por defecto, JDBCTransactionFactory).

por ejemplo classname.of.TransactionFactory

jta.UserTransaction

El nombre JNDI usado por JTATransactionFactory paraobtener una UserTransaction JTA del servidor deaplicaciones.

por ejemplo jndi/composite/name

hibernate.transaction.manager_lookup_class

El nombre de clase de un TransactionManagerLookup.Obligatorio cuando se utiliza caché a nivel de la JVM, o se usael generador hilo en un entorno JTA.

por ejemplo classname.of.TransactionManagerLookup

hibernate.transaction.flush_before_completion

Si está habilitado, a la sesión le será automátiamente hecho un"flush" antes de la fase de compleción de la transacción. Espreferible usar el manejo de transacciones que ya vieneincorporado, y el manejo automático de contexto de sesiones;ver laSección 2.5, “Sesiones contextuales”.

valores posibles true | false

hibernate.transaction.auto_close_session

Si está habilitado, la sesión será automáticamente cerradadurante la fase de compleción de la transacción. Es preferibleusar el manejo de transacciones que ya viene incorporado, y elmanejo automático de contexto de sesiones; ver laSección 2.5, “Sesiones contextuales”.

valores posibles true | false

Tabla 3.7. Propiedades Misceláneas

Nombre de la propiedad Propósito

hibernate.current_session_context_class

Provee una estrategia personalizada para determinar el alcance(scope) respecto de cuál es la sesión "actual". Véase la Sección 2.5,“Sesiones Contextuales” para más información sobre cuáles son lasopciones que ya vienen incorporadas.

valores posibles jta | thread | managed | la clasepersonalizada

3.4.1. Dialectos de SQL

Siempre se debería configurar la propiedad hibernate.dialect al valor correcto. de la subclase deorg.hibernate.dialect.Dialect que corresponda a su BD. Si se especifica un dialecto, Hibernate usará valores pordefecto adecuados para otras de las propiedades listadas anteriormente, ahorrándole el esfuerzo de especificarlas

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

33 de 198 17/02/2009 09:25 a.m.

Page 34: HIBERNATE - Persistencia

manualmente.

Tabla 3.8. Dialectos SQL de Hibernate (hibernate.dialect)

Base de datos relacional DialectoDB2 org.hibernate.dialect.DB2Dialect

DB2 AS/400 org.hibernate.dialect.DB2400Dialect

DB2 OS390 org.hibernate.dialect.DB2390Dialect

PostgreSQL org.hibernate.dialect.PostgreSQLDialect

MySQL org.hibernate.dialect.MySQLDialect

MySQL with InnoDB org.hibernate.dialect.MySQLInnoDBDialect

MySQL with MyISAM org.hibernate.dialect.MySQLMyISAMDialect

Oracle (any version) org.hibernate.dialect.OracleDialect

Oracle 9i/10g org.hibernate.dialect.Oracle9Dialect

Sybase org.hibernate.dialect.SybaseDialect

Sybase Anywhere org.hibernate.dialect.SybaseAnywhereDialect

Microsoft SQL Server org.hibernate.dialect.SQLServerDialect

SAP DB org.hibernate.dialect.SAPDBDialect

Informix org.hibernate.dialect.InformixDialect

HypersonicSQL org.hibernate.dialect.HSQLDialect

Ingres org.hibernate.dialect.IngresDialect

Progress org.hibernate.dialect.ProgressDialect

Mckoi SQL org.hibernate.dialect.MckoiDialect

Interbase org.hibernate.dialect.InterbaseDialect

Pointbase org.hibernate.dialect.PointbaseDialect

FrontBase org.hibernate.dialect.FrontbaseDialect

Firebird org.hibernate.dialect.FirebirdDialect

3.4.2. Captura (fetch) por Outer Join

Si su base de datos soporta outer joins del estilo ANSI, Oracle o Sybase, la captura con outer joins a menudo aumentará laperformance, limitando la cantidad de viajes de ida y vuelta a la BD (probablemente a costa de un mayor trabajoefectuado por la base de datos misma). La captura por outer joins permite que todo un árbol (graph) de ojetos conectadospor asociaciones de-uno-a-muchos, de-muchos-a-uno, de-muchos-a-muchos, y de-uno-a-uno sea capturado de una solavez, con un solo comando SQL SELECT.

La captura mediante outer joins puede ser inhabilitada globalmente asignándole el valor 0. a la propiedadhibernate.max_fetch_depth. Al asignar un valor de 1 o superior, se permiten las capturas (fetch) con outer joins paraasociaciones que hayan sido mapeadas como de-una-a-una o de-muchas-a-una con fetch="join".

Vea Sección 19.1, “Estrategias de captura (fetch)” para más información.

3.4.3. Streams binarios

Oracle limita el tamaño de los arrays de tipo byte que pueden ser pasados desde y hacia el driver JDBC. Si usted deseausar 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.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

34 de 198 17/02/2009 09:25 a.m.

Page 35: HIBERNATE - Persistencia

3.4.4. Caché de 2do nivel y caché de consultas.

Las propiedades con prefijo hibernate.cache le permiten usar un sistema de caché de 2do nivel con alcance (scope) deproceso o de cluster. Vea Sección 19.2, “El caché de 2do nivel” para más detalles.

3.4.5. Sustituciones en el lenguaje de consultas.

Se pueden definir nuevos símbolos para las consultas de Hibernate usando hibernate.query.substitutions. Porejemplo:

hibernate.query.substitutions true=1, false=0

causrá que los símbolos true y false sean traducidos como valores enteros literates en el SQL que se genere.

hibernate.query.substitutions toLowercase=LOWER

permitiría renombrar la propiedad LOWER (pasar a minúsculas) de SQL

3.4.6. Estadísticas de Hibernate

Si se habilita hibernate.generate_statistics, Hibernate expondrá un buen número de mediciones que son útiles alajustar (tuning) el rendimiento de un sistema en marcha, a través de SessionFactory.getStatistics(). Hibernateincluso puede ser configurado para exponer dichas estadísticas via JMX. Lea el Javadoc de las interfaces enorg.hibernate.stats para más información.

3.5. Logueo (logging, bitácora)

Hibernate utiliza la librería llamada "Fachada simple de logueo para Java" (Simple Logging Facade for Java o SLF4J porsus siglas en inglés), para loguear varios eventos de sistema. SLF4J puede dirigir la salida de su actividad de logueo avarios "frameworks" de logueo: NOP, Simple, log4j 1.2, JDK 1.4, JCL o logback), dependiendo de la vinculación elegida.Para configurar el logueo adecuadamente, necesitará slf4j-api.jar en su classpath, así como el archivo de jar de supreferencia (en el caso de log4j, slf4j-log4j12.jar). Vea la documentación de SLF4J para más detalles. Para usar log4jtambién necesitará colocar un archivo log4j.properties en su classpath. Un archivo de propiedades a modo de ejemploviene distribuido con Hibernate en el directorio src/.

Le recomendamos fuertemente que se familiarice con los mensajes de log de Hibernate. Se ha invertido mucho trabajo enlograr que el logueo en Hibernate sea lo más detallado posible, sin volverlo ilegible. Es una herramienta esencial para ladetección y resolución de problemas. Las categorías más interesantes son las siguientes:

Table 3.9. Hibernate Log Categories

Category (categoría) Función

org.hibernate.SQLLoguea todo el "lenguaje de modificación de datos" (DML) de SQL a medida que éstees ejecutado.

org.hibernate.type Loguea todos los parámetros JDBC

org.hibernate.tool.hbm2ddlLoguea todos los comandos SQL de definición de datos (DDL) que hayan sidoejecutados

org.hibernate.prettyLoguea el estado de todas las entidades (con un máximo de 20) asociadas con unasesión al momento de aplicarle "flush".

org.hibernate.cache Loguea toda la actividad del caché de 2do nivel

org.hibernate.transaction Loguea toda actividad relacionada con transacciones

org.hibernate.jdbc Loguea toda adquisición de recursos JDBC

org.hibernate.hql.ast.AST Loguea todo AST de HQL y SQL durante la revisión (parsing) de las consultas

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

35 de 198 17/02/2009 09:25 a.m.

Page 36: HIBERNATE - Persistencia

Category (categoría) Funciónorg.hibernate.secure Loguea todas las autorizaciones para requests JAAS

org.hibernateLoguea todo (un montón de información, pero muy útil a para detectar y solucionarproblemas).

Al desarrollar aplicaciones con Hibernate, se debería trabajar casi siempre con debug habilitado paraorg.hibernate.SQL, o, alternativamente, habilitar la propiedad hibernate.show_sql enabled.

3.6. Implementar una NamingStrategy

La interfaz org.hibernate.cfg.NamingStrategy permite especificar un estándar de nombrado o "NamingStrategy" paralos objetos de base de datos y elementos del esquema.

Usted puede proveer reglas para generar automáticamente los identificadores a partir de identificadores Java, o paraprocesar los nombres de tabla y columna "lógicos" dados en el archivo de mapeo, y convertirlos en nombres de tabla ycolumna "físicos". Esta característica reduce la locuacidad de los documentos de mapeo, eliminando el "ruido" provocadopor la repetición de prefijos TBL_, por ejemplo. La estrategia que Hibernate usa por defecto es bastante parca.

Se puede especificar una estrategia diferente, llamando a Configuration.setNamingStrategy() antes de agregarmapeos.

SessionFactory sf = new Configuration() .setNamingStrategy(ImprovedNamingStrategy.INSTANCE) .addFile("Item.hbm.xml") .addFile("Bid.hbm.xml") .buildSessionFactory();

org.hibernate.cfg.ImprovedNamingStrategy es una estrategia que ya viene de incorporada, y puede ser un punto departida útil para algunas aplicaciones.

3.7. Archivo de configuración XML

Un enfoque alternativo de configuración, es especificar la configuración completa en un archivo llamadohibernate.cfg.xml. Este archivo puede usarse en reemplazo del archivo hibernate.properties, o, si ambos estánpresentes, para suplantar propiedades de éste.

Se espera que el archivo de configuración XML esté por defecto en la raíz de su CLASSPATH. He aquí un ejemplo:

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<!-- una instancia de SessionFactory listada como /jndi/name --> <session-factory name="java:hibernate/SessionFactory">

<!-- propiedades --> <property name="connection.datasource">java:/comp/env/jdbc/MyDB</property>

<property name="dialect">org.hibernate.dialect.MySQLDialect</property> <property name="show_sql">false</property> <property name="transaction.factory_class"> org.hibernate.transaction.JTATransactionFactory </property>

<property name="jta.UserTransaction">java:comp/UserTransaction</property>

<!-- archivos de mapeo --> <mapping resource="org/hibernate/auction/Item.hbm.xml"/> <mapping resource="org/hibernate/auction/Bid.hbm.xml"/>

<!-- configuración del caché -->

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

36 de 198 17/02/2009 09:25 a.m.

Page 37: HIBERNATE - Persistencia

<class-cache class="org.hibernate.auction.Item" usage="read-write"/> <class-cache class="org.hibernate.auction.Bid" usage="read-only"/> <collection-cache collection="org.hibernate.auction.Item.bids" usage="read-write"/>

</session-factory>

</hibernate-configuration>

Como se puede ver, la ventaja de este enfoque es externalizar la configuración de los nombres de los acrhivos de mapeo.hibernate.cfg.xml también es más conveniente para ajustar los valores del caché. Note que usar hibernate.cfg.xml ohibernate.properties es indistinto, excepto por los beneficios mencionados de usar una sintaxis XML.

Con la configuración XML, hacer arrancar a Hibernate es tan simple como:

SessionFactory sf = new Configuration().configure().buildSessionFactory();

Se puede elegir un archivo distinto de configuración, usando:

SessionFactory sf = new Configuration() .configure("catdb.cfg.xml") .buildSessionFactory();

3.8. Integración con los Servidores de Aplicación J2EE

Hibernate tiene los siguientes puntos de integración con la infraestructura J2EE

Fuentes de datos manejadas por el contenedor: Hibernate puede usar conexiones JDBC manejadas por elcontenedor (container-mamaged), provistas a través de JNDI. Usualmente, un TransactionManager compatiblecon JTA y un ResourceManager se hacen cargo del manejo de transacciones (CMT), especialmente transccionesdistribuidas a lo largo de varias fuentes de datos. Por supuesto, usted también puede demarcar los límites de sustransacciones programáticamente (BMT) o usar la API de transacciones de Hibernate (Transaction) para mantenersu 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 lastransacciones JTA. Simplemente obtenga la SessionFactory de JNID (haciendo un "lookup"), y de ahí obtenga lasesión actual. Deje que Hibernate se haga cargo de aplicarle "flush" y de cerrar la sesión cuando la transacción JTAse 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), puedeelegir desplegar Hibernate como un "Managed MBean" Esto le ahorra el códgo de arranque de una línea queconstruye 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 disponibleantes de que Hibernate arranque, etc).

Dependiendo del entorno, usted puede tener que asignar el valor "true" a la opción de configuraciónhibernate.connection.aggressive_release, si su servidor de aplicaciones produce excepciones de "contención deconexiones" ("connection containment" exceptions).

3.8.1. Configuración de una estrategia transaccional

La sesión de Hibernate es independiente de cualquier sistema de demarcación de transacciones presente en la arquitecturade su sistema. Si usted deja que Hibernate use JDBC directamente a través de un "pool" de conexiones, usted puedecomenzar y terminar sus transacciones llamando a la API de JDBC. Si usted ejecuta su aplicación en un servidor deaplicaciones J2EE, es posible que prefiera usar transaccioes manejadas por bean (bean-managed) e invocar la API de JTAy UserTransaction según haga falta.

Para mantener su código portable entre estos dos entornos (y otros), le recomedamos emplear la API Transaction deHibernate, la cual envuelve y oculta el sistema subyacente. Va a tener que especificar una clase "fábrica" de instancias deTransaction, configurando la propiedad de Hibernate hibernate.transaction.factory_class.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

37 de 198 17/02/2009 09:25 a.m.

Page 38: HIBERNATE - Persistencia

É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 estecontexto (por ejemplo, un método de un Session EJB), en caso contrario, crea una nueva transción, y se utilizantransacciones manejadas por bean.

org.hibernate.transaction.CMTTransactionFactory

delega a transacciones JTA manejadas por el contenedor

Usted también puede definir sus propias estrategias transaccionales (por un servicio de transacciones CORBA, porejemplo).

Algunas características de Hibernate (por ejemplo, caché de 2do nivel, sesiones contextuales con JTA, etc) requieren acesoal TransactionManager de JTA en un entorno administrado. En un servidor de aplicaciones, usted tiene que especificarcómo Hibernate obtendrá una referencia al TransactionManager, dado que en J2EE no hay un mecanismo estándar yúnico.

Tabla 3.10. TransactionManagers de JTA

Fábrica de transacciones Servidor de aplicacionesorg.hibernate.transaction.JBossTransactionManagerLookup JBoss

org.hibernate.transaction.WeblogicTransactionManagerLookup Weblogic

org.hibernate.transaction.WebSphereTransactionManagerLookup WebSphere

org.hibernate.transaction.WebSphereExtendedJTATransactionLookup WebSphere 6

org.hibernate.transaction.OrionTransactionManagerLookup Orion

org.hibernate.transaction.ResinTransactionManagerLookup Resin

org.hibernate.transaction.JOTMTransactionManagerLookup JOTM

org.hibernate.transaction.JOnASTransactionManagerLookup JOnAS

org.hibernate.transaction.JRun4TransactionManagerLookup JRun4

org.hibernate.transaction.BESTransactionManagerLookup Borland ES

3.8.2. SessionFactory ligada a JNDI

Una fábrica de sesiones (SessionFactory) ligada a JNDI puede simplificar la búsqueda y obtención (lookup) de nuevassesiones. Note que esto no se relaciona con las Datasource, también ligadas a JNDI, ¡ambas simplemente comparten elmismo repositorio de registro (registry)!

Si usted desea mantener la fábrica de sesiones ligada a un "espacio de nombre" o "namespace" JNDI, especifique unnombre (por ejemplo, java:hibernate/SessionFactory) usando la propiedad hibernate.session_factory_name. Siesta propiedad es omitida, la fábrica de sesiones no quedará ligada a JNDI. (Esto es especialmente útil en entornos que pordefecto 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 yhibernate.jndi.class para instanciar un contexto inicial. Si no son especificadas, se usa el InitialContext pordefecto.

Hibernate colocará la fábrica de sesiones (SessionFactory) automáticamente en JNDI en cuanto se invoquecfg.buildSessionFactory(). Esto significa que este llamado estará presente, por lo menos, en algún código de arranque(o clase utilitaria) de su aplicación, a menos que usted usa el despliegue JMX, con HibernateService (lo cual se discuteluego).

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

38 de 198 17/02/2009 09:25 a.m.

Page 39: HIBERNATE - Persistencia

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 otroentorno, un singleton estático. Para escudar su aplicación de estos detalles, también le recomendamos que oculte el códigoque 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 arrancarHibernate (capítulo 1).

3.8.3. Manejo del contexto actual de la sesión con JTA

La manera más fácil de manejar sesiones y transacciones es el manejo automático que Hibernate hace de la "sesiónactual". Vea la discusión en Sesiones actuales. Usando el contexto de sesión "jta", si no hay ninguna sesión de Hibernateasociada con la transacción JTA actual, se creará una y se la asociará con la transacción la primera vez quesessionFactory.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ónfinalice, y las conexiones JDBC serán agresivamente liberadas después de cada comando. Esto permite que las sesionessean manejadas por el ciclo de vida de la transacción JTA al cual están asociadas, manteniendo al código libre de ese tipode 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 loslímites de la transacción. Si su aplicación se está ejecutando en un contenedor de EJB, se prefiere utilizar demarcacióndeclarativa de transacciones con CMT (siglas en inglés de "transacciones manejadas por el contenedor").

3.8.4. Despliegue de JMX

La línea cfg.buildSessionFactory() aín tiene que ser ejecutada en algún lado para meter una fábrica de sesiones enJNDI. Esto se puede lograr o bien con un bloque inicializador estático en una clase utilitaria (como el que está enHibernateUtil) o bien desplegando Hibernate como un "servicio administrado" (managed service),

Hibernate es distribuido con org.hibernate.jmx.HibernateService para el despliegue en servidores de aplicacionescon capacidad JMX, como JBoss. El despliegue y configuración son específicos de la marca del servidor. He aquí unjboss-service.xml de ejemplo para for JBoss 4.0.x:

<?xml version="1.0"?><server>

<mbean code="org.hibernate.jmx.HibernateService" name="jboss.jca:service=HibernateFactory,name=HibernateFactory">

<!-- Servicios requeridos --> <depends>jboss.jca:service=RARDeployer</depends> <depends>jboss.jca:service=LocalTxCM,name=HsqlDS</depends>

<!-- Ligar el servicio Hibernate a JNDI -->

<attribute name="JndiName">java:/hibernate/SessionFactory</attribute>

<!-- fuente de datos --> <attribute name="Datasource">java:HsqlDS</attribute> <attribute name="Dialect">org.hibernate.dialect.HSQLDialect</attribute>

<!-- integración de transacciones --> <attribute name="TransactionStrategy">org.hibernate.transaction.JTATransactionFactory</attribut <attribute name="TransactionManagerLookupStrategy">org.hibernate.transaction.JBossTransactionMa

<attribute name="FlushBeforeCompletionEnabled">true</attribute> <attribute name="AutoCloseSessionEnabled">true</attribute>

<!-- opciones de captura --> <attribute name="MaximumFetchDepth">5</attribute>

<!-- caché de 2do nivel --> <attribute name="SecondLevelCacheEnabled">true</attribute> <attribute name="CacheProviderClass">org.hibernate.cache.EhCacheProvider</attribute> <attribute name="QueryCacheEnabled">true</attribute>

<!-- Logueo --> <attribute name="ShowSqlEnabled">true</attribute>

<!-- archivos de mapeo -->

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

39 de 198 17/02/2009 09:25 a.m.

Page 40: HIBERNATE - Persistencia

<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 archivoJAR, 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ónacerca del servicio JMX y el despliegue de EJB.

Capítulo 4. Clases Persistentes

Las clases persistentes son clases de una aplicación que implementen las entidades de un problema de negocios (porejemplo, "Cliente" y "Orden" en una aplicación de e-commerce). No todas las instancias de una clase persistente seconsidera que estén en un "estado persistente". Una instancia pude, en cambio, ser transitoria (transient) o desprendida(detached).

Hibernate trabaja mejor si estas clases siguen un formato muy simple, conocido como el modelo de programación de"objeto Java liso y llano" o POJO (Plain Old Java Object) por sus siglas en inglés. Sin embargo, ninguna de estas reglas esestrictamente 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, porejemplo.

4.1. Un simple ejemplo de POJO

La mayoría de las aplicaciones Java requieren una clase persistente que represente felinos:

package eg;import java.util.Set;import java.util.Date;

public class Cat { private Long id; // identificador

private Date birthdate; private Color color; private char sex; private float weight; private int litterId;

private Cat mother; private Set kittens = new HashSet();

private void setId(Long id) { this.id=id; } public Long getId() { return id; }

void setBirthdate(Date date) { birthdate = date; } public Date getBirthdate() { return birthdate; }

void setWeight(float weight) { this.weight = weight; } public float getWeight() { return weight; }

public Color getColor() { return color; } void setColor(Color color) {

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

40 de 198 17/02/2009 09:25 a.m.

Page 41: HIBERNATE - Persistencia

this.color = color; }

void setSex(char sex) { this.sex=sex; } public char getSex() { return sex; }

//litter=en inglés, camada void setLitterId(int id) { this.litterId = id; } public int getLitterId() { return litterId; }

void setMother(Cat mother) { this.mother = mother; } public Cat getMother() { return mother; }

//kittens=gatitos void setKittens(Set kittens) { this.kittens = kittens; } public Set getKittens() { return kittens; }

// addKitten no es requerido por Hibernate public void addKitten(Cat kitten) { kitten.setMother(this); kitten.setLitterId( kittens.size() ); kittens.add(kitten); }}

Aquí hay 4 reglas principales a seguir:

4.1.1. Implemente un constructor sin argumentos

Cat tiene un constuctor sin argumentos. Todas las clases persistentes deben tener un constuctor por defecto (el cual puedeno ser público) de manera qu Hibernate pueda instanciarlas usando Constructor.newInstance(). Recomendamosfuertemente un constructor por defecto, que tenga al menos visibilidad package para la generación del "proxy" enHibernate.

4.1.2. Provea una propiedad indentificadora (optativo)

Cat tiene una propiedad llamada id. Esta propiedad corresponde a la columna de clave primaria de la base de datos. Lapropiedad 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ónsobre 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

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

41 de 198 17/02/2009 09:25 a.m.

Page 42: HIBERNATE - Persistencia

persistentes. Mas aún, le recomendamos que use un tipo anulable (es decir, no primitivo).

4.1.3. Prefiera clases que no sean finales (optativo)

Una característica central de Hibernate, los representantes o proxies, depende de que la clase persistente no sea final, o deque 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 proxiespara la captura por asociaciones perezosas (lazy association fetching), lo cual limitará sus opciones de ajuste deperformance.

También se debería evitar declarar métodos public final en las clases no finales. Si se quiere usar clases con métodospú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 persistendirectamente sus variables de instancia. Nosotros creemos que es mejor proveer un nivel de aislamiento entre el esquemarelacional y la estructura interna de datos de la clase. Por defecto, Hibernate persiste propiedades del tipo JavaBean, yreconoce nombres de método de la forma getAlgo, isAlgo y setAlgo. Si es necesario, usted puede revertir esto, ypermitir 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 parget/set que tenga acceso por defecto (package) o privado.

4.2. Implementar herencia

Una subclase también debe cumplir con la primera y la segunda regla. Hereda su identificador de la superclase Cat.

package eg;

public class DomesticCat extends Cat { private String name;

public String getName() { return name; } protected void setName(String name) { this.name=name; }}

4.3. Implementar equals() y hashCode()

Usted debe reemplazar (override) los métodos equals() y hashCode() si:

planea poner instacias de clases persistentes en un Set (lo cual es la manera recomendada de representar cualquierasociación con un lado "muchos"), y

planea usar recuperación (reattachment) de instancias desprendidas

Hibernate garantiza la equivalencia de la identidad persistente (el registro de base de datos) y la identidad de Java, sólodentro del alcance de una sesión en particular. Así que, tan pronto como mezclamos instancias que hayan sido obtenidasde distintas sesiones, debemos implementar equals() y hashCode() si deseamos tener una semántca con sentido para losSets.

La manera más obvia es implementar equals()/hashCode() comparando el valor identificador de ambos objetos. Si elvalor es el mismo, ambos deben ser el mismo registro de la base de datos, siendo por lo tanto iguales (si ambos sonagregados al Set., sólo tendremos un elemento en el Set.). ¡Desafortunadamente, no podemos utilizar ese enfoque con losidentificadores generados! Hibernate sólo asignará identificadores los objetos que hayan sido creados, y una instancia quehaya 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

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

42 de 198 17/02/2009 09:25 a.m.

Page 43: HIBERNATE - Persistencia

basados en el identificador) cambiarían, rompiendo el contrato del Set. Vea el sitio de web de Hibernate para unadiscusión completa de este problema. Note que esto no es un problema de Hibernate, sino parte de la semántica normal deJava 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 quieredecir, que estos métodos comparen solamente propiedades que formen la clave de negocio, una clave que identificaría anuestro objeto en el mundo real (una clave candidata natural).

public class Cat {

... public boolean equals(Object other) { if (this == other) return true; if ( !(other instanceof Cat) ) return false;

final Cat cat = (Cat) other;

if ( !cat.getLitterId().equals( getLitterId() ) ) return false; if ( !cat.getMother().equals( getMother() ) ) return false;

return true; }

public int hashCode() { int result; result = getMother().hashCode(); result = 29 * result + getLitterId(); return result; }

}

Note que la clave de negocio no necesita ser tan sólida como una clave primaria de la base de datos. (vea Sección 11.1.3,“Considerar la identidad de un objeto”). Propiedades que sean únicas o inmutables, usualmente son buenas candidatas a"clave de negocio".

4.4. Modelos dinámicos

Advierta que las siguientes características se consideran experimentales, y pueden cambiar en el futuro cercano

Las entidades persistentes no tienen que ser representadas, en tiempo de ejecución, necesariamente, com clases POJO uobjetos JavaBean. Hibernate también soporta modelos dinámicos (usando Maps de Maps en tiempo de ejecución), y larepresentación de entidades como árboles DOM4J. Con este enfoque, no se escriben clases, simplemente archivos demapeo.

Por defecto, Hibernate trabaja en el modo POJO normal. Se puede configurar un modo de representación de entidad pordefecto para una fábrica de sesiones en particular, usando la opción de configuración default_entity_mode (veaTabla 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 declaraun entity-name en lugar de (o además de) el nombre de la clase:

<hibernate-mapping>

<class entity-name="Customer">

<id name="id" type="long" column="ID"> <generator class="sequence"/> </id>

<property name="name" column="NAME" type="string"/>

<property name="address" column="ADDRESS" type="string"/>

<many-to-one name="organization" column="ORGANIZATION_ID" class="Organization"/>

<bag name="orders" inverse="true" lazy="false" cascade="all"> <key column="CUSTOMER_ID"/> <one-to-many class="Order"/> </bag>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

43 de 198 17/02/2009 09:25 a.m.

Page 44: HIBERNATE - Persistencia

</class>

</hibernate-mapping>

Note que, incluso si las asociaciones son declaradas usando nombres de clases de destino, el tipo de destino de unaasociació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 clienteMap david = new HashMap();david.put("name", "David");

// Crear una organizaciónMap foobar = new HashMap();foobar.put("name", "Foobar Inc.");

// Vincular a ambosdavid.put("organization", foobar);

// Grabar a amboss.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 clasesde 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 serfácilmente normalizado y saneado, permitiendo agregar una implementación apropiada de modelo de dominio encima, másadelante.

Los modos de representación de entidad también pueden ser configurados por sesión:

Session dynamicSession = pojoSession.getSession(EntityMode.MAP);

// Crea un clienteMap 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 deSession API, no de SessionFactory. De esta manera, la nueva sesión comparte la misma conexión JDBC subyacente, yotra información de contexto. Esto vuelve innecesario invocar flush() y close() en la sesión secundaria, y le deja elmanejo 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 particularde un fragmento de datos, dado el org.hibernate.EntityMode de dicha representación. Si un determinado fragmento dedatos se concibe como una estructura de datos, entonces un t-uplizador es lo que sabe cómo crear dicha estructura. Porejemplo, para el modo de entidad "POJO", el t-uplizador correspondiente sabe cómo crear el POJO mediante suconstructor, y cómo acceder a las propiedades de POJO usando los métodos de acceso. Hay dos t-uplizadores de altonivel, representados por las interfaces org.hibernate.tuple.entity.EntityTuplizer y

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

44 de 198 17/02/2009 09:25 a.m.

Page 45: HIBERNATE - Persistencia

org.hibernate.tuple.component.ComponentTuplizer. Los EntityTuplizers see encargan de manejar los"contratos" recién descritos para las entidades, mientras que los ComponentTuplizers hacen lo propio para loscomponentes.

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 ustednecesita que se utilice una estrategia de generación de proxies distinta de la que viene de fábrica. Las definiciones det-uplizer van adjuntas a la entidad o mapeo de componentes que están destinadas a manejar. Volviendo al ejemplo de laentidad "cliente" (customer).

<hibernate-mapping> <class entity-name="Customer"> <!-- Reemplaza el t-uplizer de modo de entidad dynamic-map por el de entity-mode -->

<tuplizer entity-mode="dynamic-map" class="CustomMapTuplizerImpl"/>

<id name="id" type="long" column="ID"> <generator class="sequence"/> </id>

<!-- otras propiedades-->

... </class></hibernate-mapping>

public class CustomMapTuplizerImpl extends org.hibernate.tuple.entity.DynamicMapEntityTuplizer {

// suplanta al método buildInstantiator() para "enchufar" nuestro mapeo hecho a medida ... protected final Instantiator buildInstantiator(org.hibernate.mapping.PersistentClass mappingInf return new CustomMapInstantiator( mappingInfo ); }

//suplanta a generateMap() para devolver nuestro map hecho a medida private static final class CustomMapInstantiator extends org.hibernate.tuple.DynamicMapInstanti protected final Map generateMap() { return new CustomMap(); } }}

4.6. Extensiones

A HACER: documentar el framework de extensiones de usuario y paquetes proxy.

Capítulo 5. Mapeo O/R básico

5.1. Declaración del mapeo

Los mapeos objeto/relacionales generalmente se definen en un documento XML. El documento de mapeo ha sidodiseñado para ser legible y editable a mano. El lenguaje de mapeo es "Javacéntrico", lo cual significa que los mapeos seconstruyen en torno a declaraciones de clases persistentes, no de tablas.

Note que, aunque muchos usuarios de Hibernate eligen escribir el XML a mano, existe un buen número de herramientaspara generar el documento de mapeo, como: XDoclet, Middlegen y AndroMDA.

Comencemos con un mapeo de ejemplo:

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

45 de 198 17/02/2009 09:25 a.m.

Page 46: HIBERNATE - Persistencia

<hibernate-mapping package="eg">

<class name="Cat" table="cats" discriminator-value="C">

<id name="id"> <generator class="native"/> </id>

<discriminator column="subclass" type="character"/>

<property name="weight"/>

<property name="birthdate" type="date" not-null="true" update="false"/>

<property name="color" type="eg.types.ColorUserType" not-null="true" update="false"

<property name="sex" not-null="true" update="false"/>

<property name="litterId" column="litterId" update="false"/>

<many-to-one name="mother" column="mother_id" update="false"/>

<set name="kittens" inverse="true" order-by="litter_id"> <key column="mother_id"/> <one-to-many class="Cat"/> </set>

<subclass name="DomesticCat" discriminator-value="D"> <property name="name" type="string"/> </subclass>

</class>

<class name="Dog"> <!-- acá podría ir un mapeo para Perro --> </class>

</hibernate-mapping>

(N.del T): "eg" son las siglas de "exempli gratia", una locución latina que en inglés hace las veces de "por ejemplo". A lolargo 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 documentoque son usados por Hibernate en tiempo de ejecución. El documento de mapeo también contiene algunos atributosoptativos adicionales, y elementos que afectan los esquemas de BD exportados por herramientas de exportación deesquemas (por ejemplo, el atributo not-null).

5.1.1. El Doctype o "tipo de documento XML"

Todos los mapeos XML deberían declarar el doctype que se muestra. La DTD real puede ser encontrada en la URLmencionada, 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 sudeclaració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 leerlos 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 qiecomience con http://hibernate.sourceforge.net/; el resolver intenta resolver estas entidades mediante elclassloader 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 protocolode URL classpath://; el resolver intentará resolver esas entidades a través de (1) el classloader del contexto dethread actual, y (2), el classloader que haya cargado las clases de Hibernate.

Un ejemplo de utilización de un espacio de nombre (namespace) hecho a medida:

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

46 de 198 17/02/2009 09:25 a.m.

Page 47: HIBERNATE - Persistencia

<?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 estemapeo se refiere pertenecen al esquema o catálogo indicado. Si se especifican, los nombres de tabla serán calificados conel 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 noespecifiquen un atributo cascade. El atributo auto-import nos permite, por defecto, usar un nombre de clase nocalificado 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 atodas 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 declases y colecciones.

(6)auto-import (optativo - por defecto true): Especifica si se puede usar nombres de clase no calificados (de clasesen 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 documentode mapeo.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

47 de 198 17/02/2009 09:25 a.m.

Page 48: HIBERNATE - Persistencia

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 muestraanteriormente. Sin embargo, mapear sólo una clase persistente (o jerarquía de clases) por archivo de mapeo es unacostumbre más establecida (y lo que algunas herramientas esperan). Por ejemplo, Cat.hbm.xml, Dog.hbm.xml, o, si se usaherencia, Animal.hbm.xml.

5.1.3. class

Se puede declarar una clase persistente usando el elemento class.

<class name="ClassName" (1) table="tableName" (2)

discriminator-value="discriminator_value" (3) mutable="true|false" (4) schema="owner" (5) catalog="catalog" (6) proxy="ProxyInterface" (7)

dynamic-update="true|false" (8) dynamic-insert="true|false" (9) select-before-update="true|false" (10) polymorphism="implicit|explicit" (11) where="arbitrary sql where condition" (12)

persister="PersisterClass" (13) batch-size="N" (14) optimistic-lock="none|version|dirty|all" (15) lazy="true|false" (16) entity-name="EntityName" (17)

check="arbitrary sql check condition" (18) rowid="rowid" (19) subselect="SQL expression" (20) abstract="true|false" (21) node="element-name"/>

(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 subclasesindividuales, 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 especificarel nombre de la clase misma.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

48 de 198 17/02/2009 09:25 a.m.

Page 49: HIBERNATE - Persistencia

(8)dynamic-update (optativo, por defecto false): Especifica que los comandos SQL UPDATE deberían ser generadosen 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 entiempo 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 unUPDATE SQL a menos que esté seguro de que en realidad se está modificando algún objeto. En algunos casos (enrealidad, sólo cuando un objeto transitorio ha sido asociado con una nueva sesión usando update()), esto significaque Hibernate ejecutará un comando SQLSELECT adicional, para determinar si un en realidad un UPDATE esnecesario.

(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 estaclase.

(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 poridentificador.

(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 mapeadamuchas veces (potencialmente, a distintas tablas), y permite mapeos de entidades qie estén representados porMaps a nivel de Java, En estos casos, se debería proveer un nombre arbitrario explícito para la entidad. VeaSecció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 usardurante la generación automática del esquema.

(19)rowid (optativo): Hibernate puede usar los vulgarmente llamados ROWIDs, en aquellas DB que lo soporten. Porejemplo, en Oracle, Hibernate puede usar la columna adicional rowid para actualizar más rapidamente, si ustedconfigura esta opción a rowid. Un ROWID es un detalle de implementación, y representa la ubicación física deuna 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 sise quiere tener una vista en lugar de una tabla, pero no se cuenta con una vista en la base de datos. Vea másadelante para más información.

(21)abstract (optativo): Usado para marcar superclases abstractas en jerarquías de <union-subclass>.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

49 de 198 17/02/2009 09:25 a.m.

Page 50: HIBERNATE - Persistencia

Es perfectamente aceptable que la clase persistente nombrada sea une interfaz. En ese caso, se debe declarar la claseimplementadora usando el elemento <subclass>. Se puede persistir cualquier clase anidada (inner class) estática. Sedeberí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 aHibernate realizar algunas optimizaciones menores.

El atributo optativo proxy permite la inicialización perezosa de instancias persistentes de la clase. Inicialmente, Hibernatedevolverá proxies CGLIB, que implementan la interfaz mencionada. El objeto persistente propiamente dicjo será cargadocuando 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 laclase misma. Polimorfismo explícito significa que instancias de esta clase sólo serán devueltas por consulta que nombren aesta clase, y que las consultas que nombren a esta clase devolverán sólo instancias de subclases que hayan sido mapeadoscomo <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 cualpermite 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 puedeespecificar su propia subclase de org.hibernate.persister.EntityPersister, o hasta puede proveer unaimplementación completamente nueva de la interfaz org.hibernate.persister.ClassPersister que persista, porejemplo, valiéndose de una llamada a un procedimiento almacenado de base de datos, o usando serialización a archivosplanos, o LDAP. Vea org.hibernate.test.CustomPersister para un ejemplo simple (de "persistencia" a unaHashtable).

Note que las asignaciones de valores dynamic-update y dynamic-insert no son heredadas por la subclases, así quepueden ser especificadas en los elementos <subclass> o <joined-subclass>. Estos valores pueden incrementar laperformance 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" dela base de datos sea invocado innecesariamente si usted está revinculando todo un árbol de instancias desprendidas a unasesión.

Si usted habilita dynamic-update, tendrá las siguientes opciones de "locking" optimista

version verifica las columnas de versión/timestamp

all verifica todas las columnas

dirty verifica las columnas que hayan cambiado, permitiendo algunas modificaciones concurrentes

none no usa "locking" optimista

Recomendamos muy fuertemente que se utilicen las columnas de versión/timestamp para locking optimista con Hibernate.Ésta es la estrategia óptima en lo que a performace se refiere, y es la única estrategia que maneja correctamente lasmodificaciones 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 estransparente a nivel de base de datos (note que algunas base de datos no soportan vistas correctamente, especialmenteactualizaciones 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 unaexpresió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>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

50 de 198 17/02/2009 09:25 a.m.

Page 51: HIBERNATE - Persistencia

Declara las tablas contra las cuales se ha de sincronizar esta entidad, asegurándose de que el auto-flush ocurracorrectamente. 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 clasestambién tendrán una propiedad del estilo JabaBeans conteniendo el didentificador único de una instancia determinada. Elelemento <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 unainstancia ha sido recientemente instanciada (no grabada), distinguiéndola de las instancias desprendidas que fuerongrabadas 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 lapropiedad.

Si falta el atributo name , se asume que la clase no posee propiedad indentificadora.

El atributo unsaved-value casi nunca se usa en Hibernate3.

Hay una declaración alternativa de <composite-id>, para permitur el acceso a datos de formato anticuado, con clavecompuesta. 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 laclase persistente. Si hace falta algún parámetro para inicializar la instancia del generador, se pasa usando el elemento<param>.

<id name="id" type="long" column="cat_id">

<generator class="org.hibernate.id.TableHiLoGenerator"> <param name="table">uid_table</param> <param name="column">next_hi_value_column</param> </generator></id>

Todos los generadores implementan la interfaz org.hibernate.id.IdentifierGenerator. Es una interfaz muy simple;

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

51 de 198 17/02/2009 09:25 a.m.

Page 52: HIBERNATE - Persistencia

algunas aplicaciones podrían elegir proveer su propia implementación a medida. De todos modos, Hibernate provee unavariedad de implementaciones que ya vienen incluidas. Los siguientes son los apodos mnemónicos para estos generadoresya 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. Elidentificador 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. Elidentificador 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 yuna columna (por defecto, hibernate_unique_key y next_hi respectivamente) como fuente de los valores "hi". Elalgorithmo 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 unasecuencia (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 ladirecció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 laestrategia 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 yobteniendo 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 valoren sí, pero lo combina con el método de JDBC3 getGeneratedKeys para devolver el valor final, como parte delcomando INSERT. Esta estrategia sólo es soportada, que sepamos, por los drivers de Oracle 10g diseñados paraJDK1.4. Note que los comentarios en estos comandos están inhabilitados, debido a un error en los drivers de Oracle.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

52 de 198 17/02/2009 09:25 a.m.

Page 53: HIBERNATE - Persistencia

5.1.4.2. El algoritmo hi/lo

Los generadores hilo y seqhilo proveen dos implementaciones alternativas del algoritmo hi/lo, un enfoque muy comúnpara generar identificadores. La primera implementación requiere una tabla de base de datos especial que almacene elsiguiente valor "hi" disponible. La segunda, usa una secuencia al estilo de Oracle (si esto se soporta).

<id name="id" type="long" column="cat_id"> <generator class="hilo"> <param name="table">hi_value</param>

<param name="column">next_value</param> <param name="max_lo">100</param> </generator></id>

<id name="id" type="long" column="cat_id"> <generator class="seqhilo">

<param name="sequence">hi_value</param> <param name="max_lo">100</param> </generator></id>

Desafortunadamente, usted no puede usar hilo cuando le provee su propia conexión (Connection) a Hibernate. CuandoHibernate 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.

5.1.4.3. El algoritmo UUID

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 Javano se puede obtener la dirección MAC o direcciones de memoria, así que esto es lo mejor que se puede lograr, sin usarJNI.

5.1.4.4. Columnas de indentidad y secuencias

Con las BD que soportan columnas de identidad (DB2, MySQL, Sybase, MS SQL), se puede usar la generación de clavesidentity. Con las que soportan secuencias (DB2, Oracle, PostgreSQL, Interbase, McKoi, SAP DB) se puede usar el valorsequence. Ambas estrategias requieren el uso de 2 comandos SQL para insertar un objeto nuevo.

<id name="id" type="long" column="person_id">

<generator class="sequence"> <param name="sequence">person_id_sequence</param> </generator>

</id>

<id name="id" type="long" column="person_id" unsaved-value="0">

<generator class="identity"/>

</id>

La estrategia native produce un desarrollo más portátil entre distintas plataformas (cross-platform), ya que elige entreidentity, sequence e hilo, dependiendo de las capadidades de la DB subyacente.

5.1.4.5. Identificadores asignados

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 delobjeto. 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>.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

53 de 198 17/02/2009 09:25 a.m.

Page 54: HIBERNATE - Persistencia

Elegir el generador assigned, hace que Hibernate use unsaved-value="undefined", forzándolo a determinar si unainstancia es transitoria o desprendida, a menos que haya una propiedad "version" o "timestamp", o usted definaInterceptor.isUnsaved().

5.1.4.6. Claves primarias asignadas por triggers

Para esquemas de DB anticuados/heredados solamente (Hibernate no genera DDL con triggers).

<id name="id" type="long" column="person_id">

<generator class="select"> <param name="key">socialSecurityNumber</param> </generator>

</id>

(N del T): el número de seguridad social o SSN es un número único asignado por el Estado a cada persona en EE.UU.

En el ejemplo precedente, hay un valor de propiedad único llamado socialSecurityNumber, definido por la clase, comoclave natural, y una clave primaria en la tabla llamada person_id, cuyo valor es generado por un trigger que selecionadicho número.

5.1.5. Generadores de identificador mejorados

A partir de la versión 3.2.3, hay 2 nuevos generadores que representan un replanteo de 2 aspectos diferentes de lageneración de identificadores. El primer aspecto, es la portabilida de base de datos, el segundo es la optimización (no tenerque consultar a la base de datos cad vez que se requiera un valor de identificador nuevo). Estos dos nuevos generadores (apartir de la versión 3.3.x) han sido concebidos con el objetivo de reemplazar a algunos de los generadores nombradosanteriormente De todos modos, siguen incluyéndose en los lanzamientos actuales, y se puede hacer referencia a ellos viaFQN.

El primero de estos nuevos generadores es el org.hibernate.id.enhanced.SequenceStyleGenerator, el cual ha sidoconcebido como reemplazo del generador sequence, y como un generador con una portabilidad superior a native(porque native (generalmente) elije entre identity y sequence, las cuales tienen en general semánticas distintas, lo cualpuede causar sutiles problemas aplicaciones que consideren portabilidad).org.hibernate.id.enhanced.SequenceStyleGenerator, sin embargo, consigue la portabilidad de una maneradiferente. Elije entre usar una tabla o una secuencia en la base de datos para almacenar sus valores incrementados,dependiendo de las capacidades del dialecto que esté siendo usado. La diferencia entre esto y native, es que elalmacenamiento basado en tablas y el basado en secuencias tienen exactamente la misma semántica (de hecho, lassecuencias son exactamente lo que Hibernate trata de emular con sus generadores badados en tablas). Este generador tienevarios parámetros de configuración:

sequence_name (optativo, por defecto, hibernate_sequence): El nombre de la secuencia (o tabla) a ser usada.

initial_value (optativo, por defecto, 1): El valor inicial a ser devuelto por la tabla/secuencia. En términos de lacreació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. Enté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 derespaldo, incluso si el dialecto soporta secuencias?

value_column (optativo, por defecto, next_val): ¡Sólo relevante para estructuras de tabla! El nombre de lacolumna en la tabla usada para almacenar el valor.

optimizer (optativo, por defecto, none): Vea Sección 5.1.6, “Optimización de los generadores de identificador”

El segundo de estos nuevos generadores es org.hibernate.id.enhanced.TableGenerator, el cual ha sidoprimariamente concebido en reemplazo del generador table, (aunque, en realidad, funciona mucho más como unorg.hibernate.id.MultipleHiLoPerTableGenerator). Secundariamente, ha sido concebido como unareimplementación de org.hibernate.id.MultipleHiLoPerTableGenerator que utiliza la noción de optimizadores"enchufables" (pluggable). En esencia, este generador define una tabla que es capaz de contener múltiples valores de

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

54 de 198 17/02/2009 09:25 a.m.

Page 55: HIBERNATE - Persistencia

incremento distintos, usando múltiples registros con claves diferentes. Este generador tiene varios parámetros deconfiguración:

table_name (optativo, por defecto, hibernate_sequences): El nombre de la tabla a usar.

value_column_name (optativo, por defecto, next_val): El nombre de la columna en la tabla, que será usada paracontener 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 deincremento usar.

segment_value (optativo, por defecto, default): El valor de "clave de segmento" del cual queremos extraervalores de incremento para este generador.

segment_value_length (optativo, por defecto, 255): Usado para la generaciónde esquemas; el tamaño del campode 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”

5.1.6. Optimización de los generadores de identficador

Para los identificadores que almacenan valores en la base de datos, es ineficiente acudir a la base de datos cada una de lasveces en que se necesita generar un nuevo valor de identificador. En lugar de ello, lo ideal es agrupar un buen número deellos en la memoria, y solamente acudir a la base de datos cuando se ese grupo de valores se haya agotado. Ésta es lafunció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 seefectú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 debase de datos para este optmizador sean secuenciales. Los valores devueltos desde la estructura de base de datospara este optmizador indican el "número de grupo"; el increment_size (tamaño del incremento) se multiplica porel 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 dedatos. Aquí, sin embargo, simplemente almacenamos en valor inicial para el "próximo grupo" en la estructura debase 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|."

<key-property name="propertyName" type="typename" column="column_name"/> <key-many-to-one name="propertyName class="ClassName" column="column_name"/> ......</composite-id>

En una tabla con clave compuesta, se puede mapear varias de sus propiedades como identificadores. El elemento<composite-id> acepta mapeos de propiedades <key-property> y mapeos como elementos hijos.

<composite-id> <key-property name="medicareNumber"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

55 de 198 17/02/2009 09:25 a.m.

Page 56: HIBERNATE - Persistencia

<key-property name="dependent"/></composite-id>

Su clase persistente debe reemplazar equals() y hashCode() para implementar igualdad entre identificadorescompuestos. También debe implementar Serializable.

Desafortunadamente, este abordaje de los identificadores significa que el objeto persistente es su propio identificador. Nohay otro "puntero" al objeto que no sea el objeto mismo. Se debe instanciar el objeto persistente mismo, y poblarle suspropiedades identificadores antes de poder cargarlo en estado persistente asociado con una clave primaria. A este enfoquelo 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 identificadorusadas dentro del <composite-id> son duplicadas tanto en la clase persistente como en una clase identificadora separada.

<composite-id class="MedicareId" mapped="true"> <key-property name="medicareNumber"/> <key-property name="dependent"/></composite-id>

(N.del.T): "Medicare" es el nombre en inglés del seguro estatal de salud en EE.UU. y otros países.En este ejemplo, tanto la clase del identificador compuesto, MedicareId, como la clase de la entidad misma tienenpropiedades llamadas medicareNumber y dependent. La clase identificadora debe reemplazar equals() y hashCode() eimplementar 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 mapeoscontenidos 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 identificadorcompuesto.

Vamos a describir una tercera manera, aún más conveniente de enfocar el tema de los modificadores compuestos, endonde el identificador compuesto es implementado como una clase componente en Sección 8.4, “Componentes comoidentificadores 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 elidentificador compuesto (ver capítulo 9).

access (optativo, por defecto, property): La estrategia que Hibernate debería usar para acceder al valor de laspropiedades.

class (optativo, por defecto, el tipo de propiedad determinado por reflexión): La clase componente usada comoidentificador compuesto (ver la sección siguiente).

Este tercer abordaje, un compoennte identificador, es el que recomendamos para casi todas las aplicaciones.

5.1.8. discriminator (discriminador)

El elemento <discriminator> se requiere para la persistencia polimórfica que use la estrategia de mapeo de "una tablapor 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)

/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

56 de 198 17/02/2009 09:25 a.m.

Page 57: HIBERNATE - Persistencia

(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 discriminadorcuando 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 unidentificador 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. Permitediscriminación basada en contenido.

Los verdaderos valores de la columna del discriminador son especificados por el atributo discriminator-value de loselementos <class> y <subclass>.

El atributo force solamente es útil si la tabla contiene valores de discriminador "adicionales" que no están mapeados auna 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 deuna fila.

<discriminator formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end" type="integer"/>

5.1.9. version (optativo)

El elemento <version> element es optativo e indica que la tabla contiene datos con versión. Esto es particularmente útil sise 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 deversió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)access (optativo, por defecto, property): La estrategia que Hibernate debería usar para obtener el valor de lapropiedad.

Los números de versión pueden ser de los tipos de Hibernate long, integer, short, timestamp o calendar.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

57 de 198 17/02/2009 09:25 a.m.

Page 58: HIBERNATE - Persistencia

Una propiedad de versión o timestamp nunca debería ser nula para una instancia desprendida, de manera que Hibernatepueda identificar cualquier instancia con una versión o timestamp nulos como transitoria, sin importar qué otras estrategiasde unsaved-value se hayan usado. Declarar una propiedad versión o timestamp como anulable es un forma fácil deevitar problemas con la revinculación transitiva en Hibernate, ¡especialmente útil para quienes usen identificadoresasignados o claves compuestas!

5.1.10. timestamp (optativo)

El elemento optativo <timestamp> indica que la tabla contiene datos marcados con fecha y hora. Esto apunta a ser unaalternativa 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 lapropiedad.

(4)unsaved-value (optativo, por defecto, null): Una propiedad de versión, que indica que una instancia ha sidorecientemente instanciada (no grabada, "transitoria"), distinguiéndola de una instancia desprendida, que haya sidocargada o grabada por una sesión previa (undefined especifica que debería usarse el valor de la propiedadindentificadora).

(5)source (optativo, por defecto, vm): ¿De dónde debería Hibernate obtener el valor de la timestamp? ¿De la base dedatos, o de la JVM actual? Las timestamps originadas en la base de datos son costosas, porque Hibernate debeconsultar la base de datos para determinar el "próximo" valor, pero serán más seguras en entornos de cluster. Notetambién, que no se sabe si todos los dialectos soportan esta obtención de timestamps de la base de datos, mientrasque 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 labase de datos. Vea la discusión de propiedades generadas.

Note que <timestamp> equivale a <version type="timestamp">. Y <timestamp source="db"> equivale a <versiontype="dbtimestamp">

5.1.11. property

El elemento <property> declara una propiedad pesistente de la clase, estilo JavaBeans.

<property name="propertyName" (1) column="column_name" (2)

type="typename" (3)

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

58 de 198 17/02/2009 09:25 a.m.

Page 59: HIBERNATE - Persistencia

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 datosmapeada. 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 loscomandos SQL UPDATE y/o INSERT. Asignarles false a las dos permite una propiedad puramente "derivada", cuyovalor es inicializado solamente por alguna otra propieadad que se mapea a la(s) misma(s) columna(s), o por untrigger, u otra aplicación.

(5)formula (optativo): una expresión SQL que define el valor de una propiedad computada. Las propiedadescomputadas 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 estapropiedad.

(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 deejecución).

(8)unique (optativo): Habilita la generación del lenguaje de definición de datos (DDL) para una constraint única paralas 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 denulabilidad para las columnas.

(10)optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones al valor de esta propiedadrequieren o no un "lock" optimista. En otras palabras, determina si pueden ocurrir incrementos de versión cuandola propiedad está "sucia".

(11)generated (optativo, por defecto, never): Especifica que el valor de esta propiedad es en realidad generado por la

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

59 de 198 17/02/2009 09:25 a.m.

Page 60: HIBERNATE - Persistencia

base de datos. Vea la discusión de propiedades generadas.

typename puede ser:

El nombre de un tipo básico de Hibernate (por ejemplo integer, string, character, date, timestamp,float, binary, serializable, object, blob).

El nombre de una clase de Java con un tipo básico por defecto (por ejemplo int, float, char,java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob).

El nombre de una clase de Java serializable.

El nombre de un tipo de Java hecho a medida (por ejemplo, com.mipaquete.MiTipoPersonalizado).

Si no se especifica un tipo, Hibernate usará reflexión en la propiedad nombrada, para adivinar el tipo Hibernate correcto.Hibernate intentará interpretar el nombre de la clase devuelta por el método "getter" de la propiedad, usando las reglas 2, 3y 4, en ese orden. De todos modos, esto no siempre es suficiente. En algunos casos, aún se necesita el atributo type (porejemplo, 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á alcampo directamente, usando reflexión. Usted puede especificar su propia estrategia de acceso, nombrando una clase queimplemente la interfaz org.hibernate.property.PropertyAccessor.

Las propiedades derivadas son una característica especialmente poderosa. Estas propiedades son de sólo lectura, pordefinición. Son computadas en el momento en que se cargan. Se declara dicha computación como un comando SQL, quese 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 elejemplo, customerId). También note que puede usar elementos <formula> anidados si no le gusta especificar el valorcomo 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 modelorelacional 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 tablade 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"

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

60 de 198 17/02/2009 09:25 a.m.

Page 61: HIBERNATE - Persistencia

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 losobjetos asociados.

(5)fetch (optativo, por defecto, select): Elije entre la captura (fetching) por outer-join y la captura por selecciónsecuencial.

(6)update, insert (optativo, por defecto, true) especifica que las columnas mapeadas deben incluirse en el loscomandos SQL UPDATE y/o INSERT. Asignarles false permite una propiedad puramente "derivada", cuyo valor esinicializado 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 lapropiedad.

(9)unique (optativo): Habilita la generación de lenguaje de definición de datos (DDL) para la creación de unaconstraint única para la columna de clave foránea. También permite que ésta sea el blanco referido por unaproperty-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 unaconstraint 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 nola adquisición de un "lock" optimista. En otras palabras, determina si debería ocurrir un incremento de versióncuando 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 lavariable 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 serefieran a filas ausentes. ignore tratará dicha fila ausente como si fuera una asociación nula.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

61 de 198 17/02/2009 09:25 a.m.

Page 62: HIBERNATE - Persistencia

(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 objetoasociado. 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, ycombinaciones de nombres de operacion separados por comas, por ejemploc ascade="persist,merge,evict" ocascade="all,delete-orphan". Vea la Sección 10.11, “Persistencia transitiva” para una explicación completa. Noteque las asociaciones a un solo valor (de-muchos-a-uno y de-uno-a-uno) no soportan el borrado de huérfanos.

Una declaración many-to-one típica se ve tan simple como esto:

<many-to-one name="product" class="Product" column="PRODUCT_ID"/>

El atributo property-ref debe ser usado solamente para datos heredados/anticuados en donde la clave foránea apunte auna clave única de la tabla asociada que no es la clave primaria. Éste es un modelo relacional feo. Por ejemplo, supongaque la clase Product class tiene un número de serie único, que no es la clave primaria. (El atributo unique controla enHibernate la generación de DDL con la herramienta SchemaExport).

<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>

Entonces, el mapeo para OrderItem podría usar:

<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>

De todos modos, esto se desaconseja.

Si la clave única referida comprende múltiples propiedades de la entidad asociada, usted debería mapear las propiedadesreferidas dentro de un elemento llamado <properties>.

Si la clave única referida es la propiedad de un componente, se puede especificar un "path de propiedades":

<many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/>

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.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

62 de 198 17/02/2009 09:25 a.m.

Page 63: HIBERNATE - Persistencia

(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 elobjeto asociado.

(4)constrained (optativo) especifica que una constraint de clave foránea a la clave primaria de la tabla mapeadaapunta a la tabla de la clase asociada. Esta opción afecta el orden en que save() y delete() son propagadas encascada, y determina si la asociación puede generar "proxies" (también es usada por la herramienta de exportaciónde 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 primariade 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 lapropiedad.

(8)formula (optativo): Casi todas las asociaciones de-uno-a-uno se mapean a la clave primaria de la entidad a la quepertenecen, En el raro caso de que no sea así, se puede especificar alguna otra columna o expresión con la cualasociarla 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 deinstancia por primera vez (requiere instrumentación bytecode de tiempo de ejecución). lazy="false" especificaque á asociación será siempre capturada de forma "ansiosa" (eager fetching). ¡Note que si se usaconstrained="false", usar proxies es imposible e Hibernate siempre capturará en forma ansiosa!

(10)entity-name (optativo): El nombre de entidad de la clase asociada.

Hay dos variantes de asociación de-uno-a-uno:

asociaciones por clave primaria.

asociaciones por clave foránea única.

Las asociaciones por clave primaria no necesitan una columna extra en la tabla; si dos filas están relacionadas por estaasociación, entonces las dos filas en las respectivas tablas comparten el mismo valor de clave primaria. ¡Así que si ustedquiere que dos objetos estén relacionados por la misma asociación de clave primaria, tiene que asegurarse de que se lesasigne el mismo valor de identificador!

Para una asociación por clave primaria, agréguele los siguientes mapeos a Employee y Person, respectivamente.

<one-to-one name="person" class="Person"/>

<one-to-one name="employee" class="Employee" constrained="true"/>

Ahora, debemos asegurarnos de que las claves primarias de las filas relacionadas en las tablas PERSON y EMPLOYEEson iguales. Usamos una estrategia especial de Hibernate para asegurarnos de que las claves primarias de filas relacionadassean iguales: un a estrategia de generación de identificador llamada foreign:

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

63 de 198 17/02/2009 09:25 a.m.

Page 64: HIBERNATE - Persistencia

<class name="person" table="PERSON">

<id name="id" column="PERSON_ID"> <generator class="foreign"> <param name="property">employee</param> </generator> </id>

... <one-to-one name="employee" class="Employee" constrained="true"/></class>

Entonces, a una instancia recientemente creada de Person se le asigna el mismo valor de clave primaria que a la instanciade Employee referida por la propiedad employee.

Alternativamente, una clave foránea que apunte a una constraint única de Employee a Person, puede expresarse como:

<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>

Y esta asociación puede ser convertida en bidireccional agregando lo siguiente al mapeo de Person:

<one-to-one name="employee" class="Employee" property-ref="person"/>

5.1.14. natural-id

<natural-id mutable="true|false"/>

<property ... /> <many-to-one ... /> ......</natural-id>

Aunque recomendamos el uso de clave sustitutas como claves primarias, aún se deberían identificar claves naturales paratodas las entidades. Una clave natural es una propiedad o combinación de propiedades que sea única y no nula. Si tambiénes inmutable, mejor todavía. Mapee las propiedades de la clave natural dentro del elemento <natural-id>, e Hibernategenerará 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 naturalde 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).

5.1.15. component, dynamic-component

El elemento <component> mapea propiedades de un objeto hijo a columnas de la tabla de la clase padre. Los componenesa 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>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

64 de 198 17/02/2009 09:25 a.m.

Page 65: HIBERNATE - Persistencia

(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 clasecomponente (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 lapropiedad.

(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 entiempo de ejecución).

(7)optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones de este componente requieren ono la adquisición de un "lock" optimista. En otras palabras, determina si debería haber un incremento de versióncuando la propiedad esté "sucia"

(8)unique (optativo, por defecto, false): Especifica que existe una constraint de unicidad sobre todas las columnasmapeadas 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 unareferencia 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 lapropiedad 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 deuna clase. La utilidad más importante de este tipo de construcción, es que permite que una combinación de propiedadessea 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).

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

65 de 198 17/02/2009 09:25 a.m.

Page 66: HIBERNATE - Persistencia

(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 nola adquisición de un "lock" optimista. En otras palabras, determina si debería ocurrir un incremento de versióncuando esta propiedad esté "sucia".

(5)unique (optativo, por defecto, false): Especifica que existe un constraint de unicidad sobre todas las columnasmapeadas del componente.

Por ejemplo, si tenemos el siguiente mapeo de <properties>:

<class name="Person">

<id name="personNumber"/> ... <properties name="name" unique="true" update="false"> <property name="firstName"/> <property name="initial"/> <property name="lastName"/> </properties>

</class>

Y entonces podemos tener una asociación de datos al estilo anticuado, que se refiera a esta clave única de la tabla Persontable, en lugar de a la clave primaria.

<many-to-one name="person" class="Person" property-ref="name">

<column name="firstName"/> <column name="initial"/> <column name="lastName"/>

</many-to-one>

Pero no recomendamos esto, excepto en el contexto de un mapeo a datos anticuados/heredados.

5.1.17. subclass

Finalmente, la persistencia polimórfica requiere la declaracíón de cada subclase de la clase persistente raíz. Para laestrategia de mapeo una-tabla-por-clase, se usa la declaración <subclass>.

<subclass name="ClassName" (1)

discriminator-value="discriminator_value" (2) proxy="ProxyInterface" (3) lazy="true|false" (4) dynamic-update="true|false" dynamic-insert="true|false" entity-name="EntityName" node="element-name" extends="SuperclassName">

<property .... />

.....</subclass>

(1)name: El nombre (enteramente calificado) de la subclase.Cada subclase debería declara sus propias propiedades persistentes y subclases. Se asume que las propiedades <version>

e <id> serán heredadas de la clase raíz. Cada subclase en la jerarquía debe definir un valor único de discriminator-

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

66 de 198 17/02/2009 09:25 a.m.

Page 67: HIBERNATE - Persistencia

value. Si no es especificado ninguno, se usa el nomre enteramente calificado de la clase de Java.

Para información sobre el mapeo de herencias, vea Capítulo 9, Mapeo de Herencia.

5.1.18. joined-subclass

Alternativamente, cada subclase puede ser mapeada a su propia tabla (la estrategia de mapeo "una-table-por-subclase"). Elestado heredado se captura haciendo un "join" con la tabla de la superclase. Usamos el elemento <joined-subclass>.

<joined-subclass name="ClassName" (1) table="tablename" (2) proxy="ProxyInterface" (3) lazy="true|false" (4) dynamic-update="true|false" dynamic-insert="true|false" schema="schema" catalog="catalog" extends="SuperclassName" persister="ClassName" subselect="SQL expression" entity-name="EntityName" node="element-name">

<key .... >

<property .... /> .....</joined-subclass>

(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 declararuna columna de tabla que contenga al identificador de objeto, usando el elemento <key>. El mapeo del comienzo delcapítulo sería rescrito así:

<?xml version="1.0"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="eg">

<class name="Cat" table="CATS"> <id name="id" column="uid" type="long"> <generator class="hilo"/> </id> <property name="birthdate" type="date"/>

<property name="color" not-null="true"/> <property name="sex" not-null="true"/> <property name="weight"/> <many-to-one name="mate"/> <set name="kittens"> <key column="MOTHER"/> <one-to-many class="Cat"/> </set> <joined-subclass name="DomesticCat" table="DOMESTIC_CATS"> <key column="CAT"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

67 de 198 17/02/2009 09:25 a.m.

Page 68: HIBERNATE - Persistencia

<property name="name" type="string"/> </joined-subclass>

</class>

<class name="eg.Dog"> <!-- acá podría ir el mapeo de Dog --> </class>

</hibernate-mapping>

Para información sobre los mapeos de herencias, vea Capítulo 9, Mapeo de Herencia.

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. EnHibernate, no es absolutamente necesario mapear dichas jerarquías de herencia. Se puede, simplemente, mapear cadaclase con una declaracíón separada de <class>. De todos modos, si desea usar asociaciones polimórficas (por ejemplo,una asociación a la superclase de su jeraquía), es necesario usar un mapeo con <union-subclass>.

<union-subclass name="ClassName" (1)

table="tablename" (2) proxy="ProxyInterface" (3) lazy="true|false" (4) dynamic-update="true|false" dynamic-insert="true|false" schema="schema" catalog="catalog" extends="SuperclassName" abstract="true|false" persister="ClassName" subselect="SQL expression" entity-name="EntityName" node="element-name">

<property .... />

.....</union-subclass>

(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 (lazyfetching).

Para esta estrategia de mapeo no se requiere una columna discriminadora.

Para información sobre los mapeos de herencias, vea Capítulo 9, Mapeo de Herencia.

5.1.20. join

Usando el elemento <join>, es posible mapear propiedades de una clase a varias tablas, cuando hay una relacionde-uno-a-uno entre dichas tablas.

<join

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

68 de 198 17/02/2009 09:25 a.m.

Page 69: HIBERNATE - Persistencia

table="tablename" (1) schema="owner" (2) catalog="catalog" (3) fetch="join|select" (4) inverse="true|false" (5)

optional="true|false"> (6)

<key ... />

<property ... /> ...</join>

(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 paracapturar una asociación definida por una clase en su superclase, y utilizará un OUTER JOIN para una asociacióndefinida por una subclase. Si se le asigna el valor select, entonces Hibernate usará una asociación secuencialdefinida en una subclase, la cual sólo será emitida cuando ocurra que una fila represente una instancia de lasubclase. 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 propiedadesdefinidas por esta asociación.

(6)optional (optativo, por defecto, false): Si se habilita, Hibernate insertará una nueva fila sólo si las propiedadesdefinidas 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 sepreserva la semántica para todas las propiedades.

<class name="Person" table="PERSON">

<id name="id" column="PERSON_ID">...</id>

<join table="ADDRESS"> <key column="ADDRESS_ID"/> <property name="address"/> <property name="zip"/> <property name="country"/> </join>

...

Esta característica es a menudo sólo útil para modelos de datos anticuados. Recomendamos que haya menos tablas queclases, 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 clasescomo miembro, por ejemplo, una clase Dirección dentro de una clase Persona, en lugar de incluir los campos de ladireccíó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, seconsidera que una representación jerárquica que use las clases Persona y Dirección es más "detallada" que la simpletabla PERSONA subyacente

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

69 de 198 17/02/2009 09:25 a.m.

Page 70: HIBERNATE - Persistencia

De todos modos, "join-table" es útil para alternar entre distintas estrategias de mapeo en una misma jerarquía, como seexplica 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 unaasociació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 elborrado 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 latabla 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 sobreentiendecuando 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 cuandola 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 sesobreentiende 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 definidascon on-delete="cascade", e Hibernate generará una constraint ON CASCADE DELETE a nivel de la base de datos, enlugar de varios comandos DELETE individuales. Tenga presente que esta característica saltea la estrategia usual de lockingoptimista para datos versionados.

Los atributos not-null y update son útiles cuando se mapea una asociación de-uno-a-muchos unidireccional. Si la mapeaa una clave foránea no anulable, hay que declarar la columa clave usando <key not-null="true">.

5.1.22. elementos column y formula

Cualquier elemento de mapeo que acepte un atributo column aceptará, alternativamente, un subelemento <column>. De lamisma manera, el elemento <formula> es una alternativa al atributo formula.

<column name="column_name" length="N" precision="N" scale="N" not-null="true|false"

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

70 de 198 17/02/2009 09:25 a.m.

Page 71: HIBERNATE - Persistencia

unique="true|false" unique-key="multicolumn_unique_key_name" index="index_name" sql-type="sql_type_name" check="SQL expression" default="SQL expression"/>

<formula>SQL expression</formula>

Los atributos column y formula pueden incluso ser combinados dentro de la mismo mapeo de propiedad o asociación,para expresar, por ejemplo, codiciones de asociación exóticas.

<many-to-one name="homeAddress" class="Address" insert="false" update="false"> <column name="person_id" not-null="true" length="10"/> <formula>'MAILING'</formula>

</many-to-one>

5.1.23. import

Supongamos que su aplicación tiene dos clases persistenes con el mismo nombre, y usted no quiere especificar un nombrede 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 interfacesque no estén mapeadas explícitamente.

<import class="java.lang.Object" rename="Universe"/>

<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 demúltiples tablas. Este tipo de mapeo siempre requiere más de una columna. La primera columna contiene el tipo de laentidad asociada. Es imposible especificar una constraint de clave foránea para este tipo de asociaciones. Esto se deberíausar 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 enla base de datos a clases persistentes, las cuales tendrán propiedades indentificadoras del tipo identificado por id-type. Sedebe especificar el mapeo desde los valores valores del meta-tipo a los nombres de las clases.

<any name="being" id-type="long" meta-type="string"> <meta-value value="TBL_ANIMAL" class="Animal"/> <meta-value value="TBL_HUMAN" class="Human"/> <meta-value value="TBL_ALIEN" class="Alien"/> <column name="table_name"/> <column name="id"/></any>

<any name="propertyName" (1)

id-type="idtypename" (2) meta-type="metatypename" (3) cascade="cascade_style" (4) access="field|property|ClassName" (5)

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

71 de 198 17/02/2009 09:25 a.m.

Page 72: HIBERNATE - Persistencia

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 lapropiedad.

(6)optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones de esta propiedad requieren o nola adquisición de un "lock" optimista. En otras palabras, determina si debería ocurrir un incremento de versióncuando esta propiedad esté "sucia".

5.2. Tipos de Hibernate

5.2.1. Entidades y "value types"

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 setransmiten de padre a hijos). Éste es un tipo distinto de manejo de objetos de datos (ODMG, por sus siglas en inglés), queconsiste en la persistencia de objetos basada en la capacidad de acceder a ellos, lo cual corresponde más íntimamente concómo los objetos de una aplicación son usados en sistemas grandes. Las entidades soportan referencias compartidas ycirculares, y pueden ser versionadas.

El estado persistente de una entidad consiste en referencias a otras entidades, y a instancias de lo que en Hibernate sedenomina "value types" (tipos "valor"), Los "value types" son los tipos primitivos, las colecciones (el componentecolección en sí, no lo que está dentro de ellas), y ciertos objetos inmutables. Los "value types" no pueden ser grabados niversionados 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 elusuario, que es persistente. La diferencia es que estos "componentes" tienen la semántica de un "value type", no la de unaentidad. 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. Enun modelo de dominio, ser una entidad en general implica que varios otros objetos compartirán referencias a ella, mientrasque los "value types" simplemente forman parte de una única clase, vía composición o agrupación (en inglés,"composition" y "aggregation").

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

72 de 198 17/02/2009 09:25 a.m.

Page 73: HIBERNATE - Persistencia

Retomaremos estos conceptos todo a lo largo de la documentación.

El desafío es mapear el sistema de tipos de Java (junto con la definición del desarrollador de las entidades y "value types")a un sistema del tipo SQL/BD. El puente entre ambos sistemas es provisto por Hibernate. Para las entidades proveemos<class>, <subclass> y así sucesivamente. Para los "value types" usamos <property>, <component>, etc., normalmentecon 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 suspropias 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.

5.2.2. "Value types" básicos

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 lamarca de la BD): boolean, yes_no y true_false son todas codificaciones alternativas de un boolean o unjava.lang.Boolean de Java.

string

Un mapeo de tipo de java.lang.String a VARCHAR (o el VARCHAR2 de Oracle).

date, time, timestamp

Mapeos de tipo de java.util.Date y sus subclases a tipos SQL DATE, TIME y TIMESTAMP (o equivalentes).

calendar, calendar_date

Mapeos de tipo de java.util.Calendar a tipos SQL TIMESTAMP y DATE (o equivalente).

big_decimal, big_integer

Mapeos de tipo de java.math.BigDecimal y java.math.BigInteger a NUMERIC (e el NUMBER de Oracle).

locale, timezone, currency

Mapeos de tipo de java.util.Locale, java.util.TimeZone y java.util.Currency a VARCHAR (o el VARCHAR2de Oracle). Las instancias de Locale y Currency son mapeadas a sus códigos ISO. Las instancias de TimeZone sonmapeadas a su ID.

class

Un mapeo de tipo java.lang.Class a VARCHAR (o la VARCHAR2 de Oracle). Una Class es mapeada con su nombreenteramente calificado.

binary

Mapea arrays de bytes a un tipo SQL binario apropiado.

text

Mapea cadenas largas de Java los tipos CLOB o TEXT de SQL.

serializable

Mapea tipos serializables de Java a un tipo SQL binario apropiado. También se puede indicar el tipo de Hibernateserializable 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 inconvenientespara algunas aplicaciones, dado que los objetos clob y blob no pueden ser reusados fuera de una transacción. (Más

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

73 de 198 17/02/2009 09:25 a.m.

Page 74: HIBERNATE - Persistencia

aún, el soporte de drivers es esporádico e inconsistente).

imm_date, imm_time, imm_timestamp, imm_calendar, imm_calendar_date, imm_serializable, imm_binary

Mapeos de tipo para lo que normalmente se considera "tipos mutables de Java", en los que Hibernate adopta ciertasoptimizaciones que son sólo apropiadas para tipos inmutables de Java, y la aplicación trata al objeto comoinmutable. 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 queasignarle 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 yclob. (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.

5.2.3. "Value types" hechos a medida

Para los programadores, es relativamente fácil crear sus propios "value types". Por ejemplo, usted podría querer persistirpropiedades del tipo java.lang.BigInteger a columnas VARCHAR. Hibernate no trae un tipo ya incluido para esto. Perolos tipos a medida no solamente sirven para mapear una propiedad de Java (o un elemento colección) a una sola columnade una tabla. Por ejemplo, usted puede tener una propiedad Java con métodos getNombre()/setNombre() de tipojava.lang.String que sea persistida varias columnas, como PRIMER_NOMBRE, INICIAL_DEL_SEGUNDO, APELLIDO.

Para crear un tipo a medida, implemente org.hibernate.UserType o org.hibernate.CompositeUserType y declarepropiedades usando el nombre enteramente calificado del tipo. Revise org.hibernate.test.DoubleStringType paracomprobar el tipo de cosas que es posible hacer.

<property name="twoStrings" type="org.hibernate.test.DoubleStringType">

<column name="first_string"/> <column name="second_string"/>

</property>

Note el uso de los elementos <column> para mapear una sola propiedad a múltiples columnas.

Las interfaces CompositeUserType, EnhancedUserType, UserCollectionType, y UserVersionType poveen soportepara usuarios más especializados.

Incluso se le pueden proveer parámetros a un UserType en el archivo de mapeo. Para lograr esto, su UserType debeimplementar 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 fuepasado.

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ámetrospor defecto si el tipo es parametrizado.

<typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero">

<param name="default">0</param>

</typedef>

<property name="priority" type="default_zero"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

74 de 198 17/02/2009 09:25 a.m.

Page 75: HIBERNATE - Persistencia

También es posible sustituir (override) los parámetros provistos en un typedef, caso por caso, usando parámetros de tipoen el mapeo de propiedades.

Aunque la rica variedad de tipos ya incluidos en Hibernate hace que sólo en contadas ocasiones realmente se necesite usarun tipo a medida, se considera aconsejable crear tipos a medida para clases (no entidades) que ocurran frecuentemente ensu aplicación. Por ejemplo, una clase MonetaryAmount (suma de dinero) sería una buena candidata para unCompositeUserType, incluso si pudiere ser fácilmente mapeada como componente. Uno de los motivos para esto esabstracción. Con un tipo a medida como éste, sus documentos de mapeo serán a prueba de posibles cambios en la formaen que los valores monetarios se representaren en el futuro.

5.3. Mapear una misma clase más de una vez

Es posible proveer más de un mapeo para una clase persistente en particular. En tal caso, se debe proveer un nombre deentidad para desambigüar entre instancias de las dos entidades mapeadas. (Por defecto, el nombre de la entidad es igual alnombre de la clase). Hibernate permite especificar el nombre de entidad al trabajar con objetos persistentes, al escribirconsultas, o al mapear asociaciones a dicha entidad.

<class name="Contract" table="Contracts" entity-name="CurrentContract"> ... <set name="history" inverse="true" order-by="effectiveEndDate desc"> <key column="currentContractId"/> <one-to-many entity-name="HistoricalContract"/> </set></class>

<class name="Contract" table="ContractHistory" entity-name="HistoricalContract"> ... <many-to-one name="currentContract" column="currentContractId" entity-name="CurrentContract"/>

</class>

Note cómo ahora las asociaciones son especificadas usando entity-name en lugar de class.

5.4. Identificadores de SQL entrecomillados

Se puede forzar a Hibernate a encerrar el identificador entre comillas, en el SQL generado, encerrando el nombre de tablao de columna en signos de acento grave (`), en inglés, "backticks". Hibernate usará el entrecomillado adecuado para eldialecto SQL correspondiente (lo cual es usualmente comillas, pero para SQL Server son corchetes, y para MySQL, estos"backticks").

<class name="LineItem" table="`Line Item`">

<id name="id" column="`Item Id`"/><generator class="assigned"/></id> <property name="itemNumber" column="`Item #`"/> ...</class>

5.5. Alternativas de metadatos

XML no es para ualquiera, así que hay algunas otras alternativas para definir los metadatos del mapeo O/R en Hibernate.

5.5.1. Usar marcadores de XDoclet

Muchos usuarios e Hibernate prefieren incrustar la información de mapeo directamente en su código fuente, usando lastags @hibernate.tags de XDoclet. No cubriremos esta estrategia aquí, dado que es estrictamente parte de XDoclet. Detodos modos, incluimos el siguiente ejemplo de la clase Cat con mapeo XDoclet.

package eg;import java.util.Set;import java.util.Date;

/** * @hibernate.class

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

75 de 198 17/02/2009 09:25 a.m.

Page 76: HIBERNATE - Persistencia

* 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 - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

76 de 198 17/02/2009 09:25 a.m.

Page 77: HIBERNATE - Persistencia

// addKitten not needed by Hibernate public void addKitten(Cat kitten) { kittens.add(kitten); }

/** * @hibernate.property * column="SEX" * not-null="true" * update="false" */ public char getSex() { return sex; } void setSex(char sex) { this.sex=sex; }}

Vea el sitio de web de Hibernate para más ejemplos de XDoclet con Hibernate.

5.5.2. Usar anotaciones de JDK 5.0

La JDK 5.0 introdujo anotaciones al estilo XDoclet a nivel del lenguaje. Son de tipo comprobado (type-safe) y se verificanen tiempo de compilación. Este mecanismo es más poderoso que las anotaciones XDoclet, y mejor soportado por lasherramientas gráficas (IDE) como IntelluJ IDEA, muchas de las cuales proveen autocompleción y resaltado de sintaxispara anotaciones de JDK 5.0. La nueva revisión de los EJB, (JSR-220), usa anotaciones como su principal mecanismo demetadatos para los Entity Beans. Hibernate3 implementa el EntityManager JSR-220 (la API de persistencia), hay soportedisponible para el mapeo de metadatos en el paquete de Annotataciones de Hibernate (Hibernate Annotations), como unadescarga separada. Los metadatos que se soportan son tanto Hibernate3 como EJB3 (JSR-220).

El siguiente es un ejemplo de un POJO anotado como Entity Bean EJB.

@Entity(access = AccessType.FIELD)public class Customer implements Serializable {

@Id; Long id;

String firstName; String lastName; Date birthday;

@Transient Integer age;

@Embedded private Address homeAddress;

@OneToMany(cascade=CascadeType.ALL) @JoinColumn(name="CUSTOMER_ID") Set<Order> orders;

// métodos Getter/setter, y de negocio ...}

Note que el soporte para anotaciones JDK 5.0 (y JSR-220) todavía es un trabajo en curso, incompleto. Por favor remítaseal módulo de Anotaciones de Hobernate para más detales.

5.6. Propiedades generadas

Las propiedades generadas son propiedades cuyos valores son generados por la base de datos. En general, las aplicacionesHibernate tienen que refrescar los objetos que contengan cualquier propiedad para la cual la base de datos esté generandovalores. Sin embargo, al marcar propiedades como "generadas" se deja que la aplicación delegue esta responsabilidad enHibernate. Esencialmente, cada vez que Hibernate emita un INSERT o UPDATE para una entidad que tenga propiedadesgeneradas definidas, inmediatamente generará un SELECT para capturar los valores generados.

Por añadidura, las propiedades marcadas como generadas debe ser no insertables y e inmodifocables. Sólo las versiones,las timestamps, y las propiedades simples pueden ser marcadas como generadas.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

77 de 198 17/02/2009 09:25 a.m.

Page 78: HIBERNATE - Persistencia

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 losUPDATEs subsiguientes. Cosas como "fecha de creación" entrarían en esta categoría. Note que, aunque las propiedadesversion 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.

5.7. Objetos auxiliares de base de datos

Permite la ejecución de comandos CREATE y DROP arbitrarios para objetos de base de datos, en conjunción con lasherramientas de evolución de esquema de base de datos con las que cuenta Hibernate, para poveer la capacidad de definirun esquema totalmente dentro de los archivos de mapeo de Hibernate. Aunque fue designada específicamente paraeliminar (DROP) cosas como triggers o procedimientos almacenados, en realidad cualquier comando SQL que pueda serejecutado mediante un java.sql.Statement.execute() es válido aquí: ALTERs, INSERTs, etc). Esencialmente, haydos maneras de definir objetos de base de datos auxiliares:

La primera es listar explícitamente los comandos CREATE y DROP en el archivo de mapeo:

<hibernate-mapping>

... <database-object> <create>CREATE TRIGGER my_trigger ...</create> <drop>DROP TRIGGER my_trigger</drop> </database-object>

</hibernate-mapping>

La segunda es proveer una clase a medida, la cual sepa cómo construir los comandos CREATE y DROP. Esta clase amedida 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 cuandose 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>

Capítulo 6. Mapeo de colecciones

6.1. Colecciones persistentes

Hibernate requiere que los campos con valor de colección sean declarados como el tipo de una interfaz, por ejemplo:

public class Product { private String serialNumber; private Set parts = new HashSet();

public Set getParts() { return parts; } void setParts(Set parts) { this.parts = parts; }

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

78 de 198 17/02/2009 09:25 a.m.

Page 79: HIBERNATE - Persistencia

public String getSerialNumber() { return serialNumber; } void setSerialNumber(String sn) { serialNumber = sn; }}

La interfaz puede ser, efectivamente, un java.util.Set, java.util.Collection, java.util.List, java.util.Map,java.util.SortedSet, java.util.SortedMap o cualquier otra cosa que implemente la interfazorg.hibernate.usertype.UserCollectionType.)

Dese cuenta de cómo inicializamos la variable de instancia con una instancia de HashSet. Ésta es la mejor manera deinicializar propiedades con valor de colección, o instancias recientemente inicializadas (no persistentes). Cuando una clasese vuelve persistente (al llamar persist(), por ejemplo) Hibernate en realidad reemplazará el HashSet con unaimplementación de Set propia de Hibernate mismo. Esté atento a errores como éstos:

Cat cat = new DomesticCat();Cat kitten = new DomesticCat();....Set kittens = new HashSet(); //kittens=gatitoskittens.add(kitten);cat.setKittens(kittens);session.persist(cat);kittens = cat.getKittens(); // Correcto, porque la colección kittens es un Set(HashSet) cat.getKittens(); // ¡Error!

Las colecciones persistentes que son inyectadas por Hibernate se comportan como: HashMap, HashSet, TreeMap, TreeSetor ArrayList, dependiendo del tipo de interfaz.

Las instancias de coleccioones tienen el comportamiento usual de los "value tupes". Son automáticamente persistidascuando son referidas por un objeto persistente, y aumáticamente borradas cuando son "des-referidas". Si una colección sepasa de un objeto persistente a otro, sus elementos pueden ser movidos de una tabla a otra. Dos entidades no puedencompartir una referencia a la misma instancia de una colección. Dado el modelo relacional subyacente, las propiedadescon valor de colección no soportan la semántica de nulo. Hibernate no distingue entre una colección nula y una colecciónvacía.

No vale la pena preocuparse mucho por nada de esto. Use las colecciones persistentes del mismo modo en que usaría lascolecciones comunes de Java. Sólo asegúrese de comprender la semántica de las asociaciones bidireccionales (la cual sediscute más adelante).

6.2. Mapeo de colecciones

Consejo

Hay una gran variedad de mapeos que puede ser generada para colecciones, abarcando muchos modelosrelacionales de uso común. Le sugerimos que experimente con la herramienta de generación de esquemasde 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, unelemento <set> se usa para mapear propiedades del tipo Set.

<class name="Product">

<id name="serialNumber" column="productSerialNumber"/> <set name="parts"> <key column="productSerialNumber" not-null="true"/> <one-to-many class="Part"/> </set>

</class>

Además de <set>, también están los elementos de mapeo <list>, <map>, <bag>, <array> y <primitive-array> . Elelemento <map> es representativo:

<map name="propertyName" (1) table="table_name" (2) schema="schema_name" (3) lazy="true|extra|false" (4)

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

79 de 198 17/02/2009 09:25 a.m.

Page 80: HIBERNATE - Persistencia

inverse="true|false" (5)

cascade="all|none|save-update|delete|all-delete-orphan|delet(6)e-orphan" sort="unsorted|natural|comparatorClass" (7) order-by="column_name asc|desc" (8) where="arbitrary sql where condition" (9) fetch="join|select|subselect" (10)

batch-size="N" (11) access="field|property|ClassName" (12) optimistic-lock="true|false" (13) mutable="true|false" (14) node="element-name|." embed-xml="true|false"

>

<key .... /> <map-key .... /> <element .... /></map>

(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 enasociaciones de-muchos-a-muchos).

(3)schema (optativo) el nombre de un esquema de base de datos que suplanta al esquema declarado en el elementoraíz.

(4)lazy (optativo, por defecto, true) puede usarse para inhabilitar la "captura haragana" (lazy fetching) y especificarque 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 paracolecciones muy grandes).

(5)inverse (optativo, por defecto, false) marca esta colección como el extremo "inverso" de una asociaciónbidireccional.

(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 elorden 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 lacolecció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 subselectsecuencial.

(11)batch-size (optativo, por defecto, 1) especifica el "tamaño de lote" al capturar instancias de esta colección enforma haragana.

6.2.1. Claves foráneas de las colecciones

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

80 de 198 17/02/2009 09:25 a.m.

Page 81: HIBERNATE - Persistencia

Las instancias de colecciones se distinguen en la BD por la clave foránea que posee a la colección. Esta clave foránea sedenomina columna (o columnas) clave de la colección en la tabla de la colección. La columna clave de la colección esmapeada 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 pordefecto, así que a veces es necesario especificar not-null="true".

<key column="productSerialNumber" not-null="true"/>

la constraint de clave foránea puede usar ON DELETE CASCADE.

<key column="productSerialNumber" on-delete="cascade"/>

Vea el capítulo correspondiente para una definición completa del elemento <key>.

6.2.2. Elementos de las colecciones

Las colecciones pueden contener casi cualquier otro tipo de Hibernate, incluyendo todos los tipos básicos, los tipos amedida, componentes, y, por supuesto, referencias a otras entidades. Esta es una distinción importante: un objeto en unacolección puede ser manipulado con la semántica de "valor" (que todo su ciclo de vida dependa del dueño de lacolecció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 deentidades.

6.2.3. Colecciones indexadas

Todos los mapeos de colección, excepto los los que tengan semántica de set o de bag, necesitan una columna índice en latabla 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 mapeadacon <map-key-many-to-many>, o puede ser un tipo compuesto, mapeado con <composite-map-key>. El índice de unarray o lista es siempre del tipo integer y se mapea usando el elemento <list-index> element. La columna mapeadacontiene 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)formula (optativo): Una fórmula SQL usada para evaluar la clave del mapa<map-key-many-to-many

column="column_name" (1)

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

81 de 198 17/02/2009 09:25 a.m.

Page 82: HIBERNATE - Persistencia

formula="any SQL expression" (2)(3) class="ClassName"/>

(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íamapear la propiedad como una <bag> de Hibernate. Una bag (bolsa) no retiene su orden cuando es obtenida de la base dedatos, pero puede ser opcionalmente ordenada.

6.2.4. Colecciones de valores y asociaciones de-muchos-a-muchos

Cualquier colección de valores o asociación de-muchos-a-muchos requiere una tabla de colección dedicada, con unacolumna o columnas de clave foránea, una columna o columnas para el elemento de la colección, y, posiblemente, unacolumna o columnas índice.

Para una colección de valores, usamos la tag <element>.

<element column="column_name" (1) formula="any SQL expression" (2) type="typename" (3)

length="L" precision="P" scale="S" not-null="true|false" unique="true|false" node="element-name"/>

(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

Una asociación de-muchos-a-muchos (many-to-many) se especifica usando el elemento <many-to-many>.

<many-to-many column="column_name" (1) formula="any SQL expression" (2) class="ClassName" (3) fetch="select|join" (4) unique="true|false" (5)

not-found="ignore|exception" (6) entity-name="EntityName" (7) property-ref="propertyNameFromAssociatedClass" (8) node="element-name" embed-xml="true|false" />

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

82 de 198 17/02/2009 09:25 a.m.

Page 83: HIBERNATE - Persistencia

(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 selectsecuencial. Éste es un caso especial: para una captura total y "ansiosa" (usando un solo SELECT) de una entidad ysus relaciones de-muchos-a-muchos con otras entidades, se debería habilitar la captura (fetch) de tipo join nosolamente 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 unicidaden 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 claveforá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. Sino se especifica, se usa la clave primaria de la clase asociada.

A continuación, algunos ejemplos. Primero, un conjunto de strings.

<set name="names" table="person_names"> <key column="person_id"/> <element column="person_name" type="string"/></set>

Una bag conteniendo enteros, con un orden de iteración determinado por el atributo order-by:

<bag name="sizes" table="item_sizes" order-by="size asc"> <key column="item_id"/> <element column="size" type="integer"/></bag>

Un array de entidades (en este caso, una asociación de-uno-a-muchos):

<array name="addresses" table="PersonAddress" cascade="persist"> <key column="personId"/> <list-index column="sortOrder"/> <many-to-many column="addressId" class="Address"/></array>

Un mapa de índices string a fechas:

<map name="holidays" table="holidays" schema="dbo" order-by="hol_name asc"> <key column="id"/> <map-key column="hol_name" type="string"/> <element column="hol_date" type="date"/></map>

Una lista de componentes (se discute más adelante):

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

83 de 198 17/02/2009 09:25 a.m.

Page 84: HIBERNATE - Persistencia

<list name="carComponents" table="CarComponents"> <key column="carId"/> <list-index column="sortOrder"/> <composite-element class="CarComponent"> <property name="price"/> <property name="type"/> <property name="serialNumber" column="serialNum"/> </composite-element></list>

6.2.5. Asociaciones de-uno-a-muchos

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 unacolumna í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é queesté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 nombrede 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 debedeclarar el mapeo de <key> not-null="true" o usar una asociación bidireccional con el mapeo de la colecciónmarcado 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 propiedadpersistente de Part). Note el uso de un índice basado en una fórmula.

<map name="parts" cascade="all"> <key column="productId" not-null="true"/> <map-key formula="partName"/> <one-to-many class="Part"/></map>

6.3. Mapeos de colección avanzados:

6.3.1. Colecciones ordenadas.

Hibernate soporta colecciones que implementen java.util.SortedMap y java.util.SortedSet. Se debe especificarun comparador en el archivo de mapeo.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

84 de 198 17/02/2009 09:25 a.m.

Page 85: HIBERNATE - Persistencia

<set name="aliases" table="person_aliases" sort="natural"> <key column="person"/> <element column="name" type="string"/></set>

<map name="holidays" sort="my.custom.HolidayComparator"> <key column="year_id"/> <map-key column="hol_name" type="string"/> <element column="hol_date" type="date"/></map>

Valores permitidos del atributo sort son unsorted, natural y el nombre de una clase que implementejava.util.Comparator.

Las colecciones ordenadas en realidad se comportan como un java.util.TreeSet o java.util.TreeMap.

Si quiere que la base de datos misma ordene los elementos de la colección, use el atributo order-by del mapeo de losmapeos de set, bag o map. Esta solución sólo se encuentra disponible bajo la JDK 1.4 o superior (se implementa usandoLinkedHashSet o LinkedHashMap). Esto efectúa el ordenamiento en la consulta SQL, no en memoria.

<set name="aliases" table="person_aliases" order-by="lower(name) asc"> <key column="person"/> <element column="name" type="string"/></set>

<map name="holidays" order-by="hol_date, hol_name"> <key column="year_id"/> <map-key column="hol_name" type="string"/> <element column="hol_date type="date"/></map>

¡Note que el valor del atributo order-by es un ordenamiento SQL, no HQL!

Las asociaciones pueden incluso ser ordenadas por algún criterio arbitrario en tiempo de ejecución, usandp un filtro(filter()) de colección.

sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();

6.3.2. Asociaciones bidireccionales

Una asociación bidireccional (bidirectional association) permite navegar desde ambos "extremos" de la asociación. Sesoportan dos tipos de asociación bidireccional:

one-to-many

(de-uno-a-muchos) un set o una bag en un extremo, un valor simple en en otro extremo.

many-to-many

(de-muchos-a-muchos) un set o una bag con valores en ambos extremos

Se puede especificar una asociación bidireccional de-muchos-a-muchos simplemente mapeando dos asociaciones a lamisma tabla de la base de datos, y declarando uno de los extremos como inverse (cuál, es a elección, pero no puede seruna colección indexada).

He aquí un ejemplo de una asociación bidireccional de-muchos-a-muchos; cada categoría puede tener muchos items ycada ítem puede tener muchas categorías:

<class name="Category"> <id name="id" column="CATEGORY_ID"/> ...

<bag name="items" table="CATEGORY_ITEM"> <key column="CATEGORY_ID"/> <many-to-many class="Item" column="ITEM_ID"/> </bag></class>

<class name="Item">

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

85 de 198 17/02/2009 09:25 a.m.

Page 86: HIBERNATE - Persistencia

<id name="id" column="ITEM_ID"/> ...

<!-- lado inverso --> <bag name="categories" table="CATEGORY_ITEM" inverse="true"> <key column="ITEM_ID"/> <many-to-many class="Category" column="CATEGORY_ID"/> </bag></class>

Los cambios que se le hacen sólo al extremo inverso de la asociación, no son persistidos. Esto significa que Hibernatetiene 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-muchosen Java.

category.getItems().add(item); // La categoría ahora "sabe" acerca de la relaciónitem.getCategories().add(category); // El ítem ahora "sabe" acerca de la relación

session.persist(item); // ¡La relación no será grabada!session.persist(category); // La relación sí será grabada

El lado no-inverso se usa para grabar en la base de datos la representación en memoria.

Se puede definir una asociación bidireccional de-muchos-a-muchos mapeando un a asocíación de-uno-a-muchos a la(s)misma(s) columna(s) de la tabla que una asociación de-muchos-a-uno, y poniendo inverse="true" en el el extremo'muchos'.

<class name="Parent">

<id name="id" column="parent_id"/> .... <set name="children" inverse="true"> <key column="parent_id"/> <one-to-many class="Child"/> </set></class>

<class name="Child"> <id name="id" column="child_id"/> .... <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/></class>

Mapear uno de los extremos de la asociación con inverse="true" no afecta las propagaciones en cascada. ¡Sonconceptos ortogonales!

6.3.3. Asociaciones bidireccionales con colecciones indexadas.

Una asociación bidireccional en donde un extremo esté representado como una <list> o un <map>necesita algunasconsideraciones especiales. Si existe una propiedad de la clase hija que mapee a la columna de índice, no hay problema,podemos continuar usando inverse="true" en el mapeo de la colección.

<class name="Parent">

<id name="id" column="parent_id"/> .... <map name="children" inverse="true"> <key column="parent_id"/> <map-key column="name" type="string"/> <one-to-many class="Child"/> </map>

</class>

<class name="Child"> <id name="id" column="child_id"/> .... <property name="name" not-null="true"/> <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/></class>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

86 de 198 17/02/2009 09:25 a.m.

Page 87: HIBERNATE - Persistencia

Pero si tal propiedad no existe en la clase hoja, no podemos concebir la asociación como verdaderamete bidireccional (hayinformación disponible en un extremo que no lo está en el otro extremo). En este caso, no podemos mapear la coleccióncon 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. AHACER: ¿esto resulta realmente en la ejecución de UPDATES innecesarios?

6.3.4. Asociaciones ternarias

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 usamás comúnmente.

Una última alternativa, es usar elementos compuestos, lo cual se discute más adelante.

6.3.5. Usar una <idbag>

Si usted está totalmente convencido de que las claves compuestas son una cosa mala, y de que las entidades debe teneridentidicadores "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! Estepunto es debatible. Una tabla que sea puramente "de asociación" no se beneficia mucho con la existencia de una clavesustituta (aunque una colección de valores compuestos sí podría). De todos modos, Hibernate provee un mecanismo quepermite mapear asociaciones de-muchos-a-muchos y colecciones de valores a una tabla con clave sustituta.

El elemento <idbag> le permite mapear una List (o Collection) con semántica de bag.

<idbag name="lovers" table="LOVERS">

<collection-id column="ID" type="long"> <generator class="sequence"/> </collection-id>

<key column="PERSON1"/> <many-to-many column="PERSON2" class="Person" fetch="join"/>

</idbag>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

87 de 198 17/02/2009 09:25 a.m.

Page 88: HIBERNATE - Persistencia

Como se puede ver, una <idbag> tiene un generador de id "sintético", ¡igual que una clase de entidad! A cada fila de lacolección se le asigna una clave sustituta diferente. Si embargo, Hibernate no provee ningún mecanismo para descubrir elvalor 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 puedelocalizar filas individuales eficientemente, y actualizarlas o borraras individualmente, como si fuera una list, un map o unset.

En la implementación actual, el identificador native como estrategia de generación de identificadores no se soporta paralas <idbag>s.

6.4. Ejemplos de colecciones

Las secciones precedentes son bastante confusas. Así que veamos un ejemplo. Esta clase:

package eg;import java.util.Set;

public class Parent { private long id; private Set children;

public long getId() { return id; } private void setId(long id) { this.id=id; }

private Set getChildren() { return children; } private void setChildren(Set children) { this.children=children; }

.... ....}

Tiene una colección de instancias Child. Si cada child (hijo) tiene como mucho un padre, el mapeo más natural es unaasociación de-uno-a-muchos:

<hibernate-mapping>

<class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children"> <key column="parent_id"/> <one-to-many class="Child"/> </set> </class>

<class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> </class>

</hibernate-mapping>

Esto se mapea a las siguientes definiciones de tabla:

create table parent ( id bigint not null primary key )create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )alter table child add constraint childfk0 (parent_id) references parent

If the parent is required, use a bidirectional one-to-many association:

<hibernate-mapping>

<class name="Parent"> <id name="id"> <generator class="sequence"/> </id>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

88 de 198 17/02/2009 09:25 a.m.

Page 89: HIBERNATE - Persistencia

<set name="children" inverse="true"> <key column="parent_id"/> <one-to-many class="Child"/> </set> </class>

<class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/> </class>

</hibernate-mapping>

Note la constraint NOT NULL:

create table parent ( id bigint not null primary key )create table child ( id bigint not null primary key, name varchar(255), parent_id bigint not null )alter table child add constraint childfk0 (parent_id) references parent

Alternativamente, si usted insiste absolutamente en que la asociación debe ser unidireccional, debe declarar la constraintNOT NULL en el mapeo de la <key>.

<hibernate-mapping>

<class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children"> <key column="parent_id" not-null="true"/> <one-to-many class="Child"/> </set> </class>

<class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> </class>

</hibernate-mapping>

Por otra parte, si un hijo tiene múltiles padres, lo apropiado es una relación de-muchos-a-muchos.

<hibernate-mapping>

<class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children" table="childset"> <key column="parent_id"/> <many-to-many class="Child" column="child_id"/> </set> </class>

<class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> </class>

</hibernate-mapping>

Definiciones de tablas:

create table parent ( id bigint not null primary key )

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

89 de 198 17/02/2009 09:25 a.m.

Page 90: HIBERNATE - Persistencia

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_ialter table childset add constraint childsetfk0 (parent_id) references parentalter 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.

Capítulo 7. Mapeos de asociaciones

7.1. Introducción

Los mapeos de asociaciones son lo más difícil de comprender correctamente. En esta sección revisaremos los casoscanónicos uno por uno, empezando con los mapeos unidireccionales, y siguiendo con los bidireccionales. UsaremosPerson 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 seeliminara la constraint de nulabilidad.

7.2. Asociaciones unidireccionales.

7.2.1. de-muchos-a-uno

Una asociación unidireccional de-muchos-a-uno es el tipo más común de asociación unidireccional.

<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <many-to-one name="address" column="addressId" not-null="true"/></class>

<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id></class>

create table Person ( personId bigint not null primary key, addressId bigint not null )create table Address ( addressId bigint not null primary key )

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 laconstraint 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">

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

90 de 198 17/02/2009 09:25 a.m.

Page 91: HIBERNATE - Persistencia

<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 quehemos revertido la dirección de la asociación en este ejemplo).

<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id></class>

<class name="Address"> <id name="id" column="personId"> <generator class="foreign"> <param name="property">person</param> </generator> </id> <one-to-one name="person" constrained="true"/></class>

create table Person ( personId bigint not null primary key )create table Address ( personId bigint not null primary key )

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>

create table Person ( personId bigint not null primary key )create table Address ( addressId bigint not null primary key, personId bigint not null )

Pensamos que para este caso es mejor usar una tabla de asociación.

7.3. Asociaciones unidireccionales con tablas 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 alespecificar unique="true", hemos cambiado la multiplicidad de "de-muchos-a-muchos" a "de-uno-a-muchos".

<class name="Person">

<id name="id" column="personId"> <generator class="native"/> </id>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

91 de 198 17/02/2009 09:25 a.m.

Page 92: HIBERNATE - Persistencia

<set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" unique="true" class="Address"/> </set></class>

<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id></class>

create table Person ( personId bigint not null primary key )create table PersonAddress ( personId not null, addressId bigint not null primary key )create table Address ( addressId bigint not null primary key )

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 esoptativa

<class name="Person"> <id name="id" column="personId"> <generator class="native"/>

</id> <join table="PersonAddress" optional="true"> <key column="personId" unique="true"/> <many-to-one name="address" column="addressId" not-null="true"/> </join></class>

<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id></class>

create table Person ( personId bigint not null primary key )create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )create table Address ( addressId bigint not null primary key )

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>

create table Person ( personId bigint not null primary key )create table PersonAddress ( personId bigint not null primary key, addressId bigint not null uniquecreate table Address ( addressId bigint not null primary key )

7.3.4. de-muchos-a-muchos

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

92 de 198 17/02/2009 09:25 a.m.

Page 93: HIBERNATE - Persistencia

Finalmente, tenemos una asociación unidireccional de-muchos-a-muchos.

<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" class="Address"/> </set></class>

<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id></class>

create table Person ( personId bigint not null primary key )create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (perscreate table Address ( addressId bigint not null primary key )

7.4. Asociaciones bidireccionales

7.4.1. de-uno-a-muchos / de-muchos-a-uno

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, ydejar 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>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

93 de 198 17/02/2009 09:25 a.m.

Page 94: HIBERNATE - Persistencia

Es importante que se defina not-null="true" en el elemento <key> del mapeo de la colección, si la columna de claveforá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

Un asociación bidireccional de-uno-a-uno por clave foránea es bastante común

<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <many-to-one name="address" column="addressId" unique="true" not-null="true"/></class>

<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <one-to-one name="person" property-ref="address"/></class>

create table Person ( personId bigint not null primary key, addressId bigint not null unique )create table Address ( addressId bigint not null primary key )

Un asociación bidireccional de-uno-a-uno por clave primaria usa el generador de id especial.

<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <one-to-one name="address"/></class>

<class name="Address"> <id name="id" column="personId"> <generator class="foreign"> <param name="property">person</param> </generator> </id> <one-to-one name="person" constrained="true"/></class>

create table Person ( personId bigint not null primary key )create table Address ( personId bigint not null primary key )

7.5. Asociaciones bidireccionales por tablas de asociación

7.5.1. de-uno-a-muchos / de-muchos-a-uno

Un asociación bidireccional de-uno-a-muchos por tabla de asociación. Note que el inverse="true" puede ir ancualquier extremo de la asociación, en la colección, o en el join.

<class name="Person">

<id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" unique="true" class="Address"/> </set></class>

<class name="Address"> <id name="id" column="addressId"> <generator class="native"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

94 de 198 17/02/2009 09:25 a.m.

Page 95: HIBERNATE - Persistencia

</id> <join table="PersonAddress" inverse="true" optional="true"> <key column="addressId"/> <many-to-one name="person" column="personId" not-null="true"/> </join></class>

create table Person ( personId bigint not null primary key )create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )create table Address ( addressId bigint not null primary key )

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>

create table Person ( personId bigint not null primary key )create table PersonAddress ( personId bigint not null primary key, addressId bigint not null uniquecreate table Address ( addressId bigint not null primary key )

7.5.3. de-muchos-a-muchos

Finalmente, tenemos una asociación bidireccional de-muchos-a-muchos.

<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" class="Address"/> </set></class>

<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <set name="people" inverse="true" table="PersonAddress"> <key column="addressId"/> <many-to-many column="personId" class="Person"/> </set></class>

create table Person ( personId bigint not null primary key )create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (perscreate table Address ( addressId bigint not null primary key )

7.6. Mapeos de asociaciones más complejas

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

95 de 198 17/02/2009 09:25 a.m.

Page 96: HIBERNATE - Persistencia

Asociaciones más complejas son extremadamente raras. Hibernate posibilita manejar asociaciones más complejas usandofragmentos SQL incrustados en el documento de mapeo. Por ejemplo, si una tabla con información contable históricadefiniere las columnas accountNumber, effectiveEndDate and effectiveStartDate, mapeadas de esta manera:

<properties name="currentAccountKey"> <property name="accountNumber" type="string" not-null="true"/> <property name="currentAccount" type="boolean"> <formula>case when effectiveEndDate is null then 1 else 0 end</formula> </property></properties>

<property name="effectiveEndDate" type="date"/>

<property name="effectiveStateDate" type="date" not-null="true"/>

entonces podemos mapear una asociación a la instancia actual (la que tiene effectiveEndDate nula) usando:

<many-to-one name="currentAccountInfo" property-ref="currentAccountKey" class="AccountInfo"> <column name="accountNumber"/> <formula>'1'</formula></many-to-one>

En un ejemplo más complejo, imagine que la asociación entre Employee y Organization estuviere mantenida por unatabla Employment, llena de datos históricos de empleo. Entonces, una asociación al empleador más reciente de unempleado (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 casosunando HQL o consultas Criteria.

Capítulo 8. Mapeos de componentes

La noción de componente (en inglés, component) se reusa a lo largo y a lo ancho de Hibernate en distintos contextos, paradistintos propósitos.

8.1. Objetos dependientes

Un componente es un objeto contenido, que es persistido como "value type", no como referencia a una entidad. El término"componente" se refiere a la noción orientada a objetos de "composición" (no a "componentes" a nivel de arquitectura).Por ejemplo, se puede modelar a una persona así:

public class Person { private java.util.Date birthday; private Name name; private String key; public String getKey() { return key; } private void setKey(String key) { this.key=key; } public java.util.Date getBirthday() { return birthday; } public void setBirthday(java.util.Date birthday) { this.birthday = birthday; } public Name getName() {

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

96 de 198 17/02/2009 09:25 a.m.

Page 97: HIBERNATE - Persistencia

return name; } public void setName(Name name) { this.name = name; } ...... ......}

public class Name { char initial; String first; String last; public String getFirst() { return first; } void setFirst(String first) { this.first = first; } public String getLast() { return last; } void setLast(String last) { this.last = last; } public char getInitial() { return initial; } void setInitial(char initial) { this.initial = initial; }}

Ahora, Name puede ser persistida como un componente de Person (en inglés, "nombre" y "persona", respectivamente).Note que Name define métodos getter y setter para sus propiedades persistentes, pero no necesita declarar ninguna interfazni propiedades identiicadoras.

Nuestro mapeo Hibernate se vería así:

<class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name"> <!-- el atributo 'class" es optativo --> <property name="initial"/> <property name="first"/> <property name="last"/> </component></class>

La tabla PERSON tendría las columnas pid, birthday, initial, first y last.

Como todos los "value types", los componentes no soportan referencias compartidas. Dos personas pueden tener el mismonombre, pero los dos objetos Person correspondientes contendrán dos objetos nombre independientes, sólo que con "elmismo" valor. La semántica de nulo de un componente es ad hoc. Cuando se recargue el objeto contenedor, Hibernateassumirá 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 Hibernatesoporte 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 componentecomo referencia de vuelta a la entidad contenedora.

<class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name" unique="true"> <parent name="namedPerson"/> <!-- referencia de vuelta a Person --> <property name="initial"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

97 de 198 17/02/2009 09:25 a.m.

Page 98: HIBERNATE - Persistencia

<property name="first"/> <property name="last"/> </component></class>

8.2. Colecciones de objetos dependientes

Hay soporte para colecciones de componentes (por ejemplo, un array de tipos Name). Declare su colección decomponentes reemplazando la tag del elemento <element> con una tag <composite-element>.

<set name="someNames" table="some_names" lazy="true"> <key column="id"/> <composite-element class="eg.Name"> <!-- el atributo class es obligatorio --> <property name="initial"/> <property name="first"/> <property name="last"/> </composite-element></set>

Nota: si se define un Set de elementos compuestos, es importante implementar equals() y hashCode() correctamente.

Los elementos compuestos pueden contener otros componentes, pero no colecciones. Si su componente compuesto a suvez contiene componentes, use la tag <nested-composite-element>. Éste es un caso batante exótico: una colección decomponentes que a su vez tengan componentes. A este punto usted debería preguntarse si no sería más apropiada unaasociació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 claveprimaria separada en la tabla de elementos compuestos), lo cual no es posible con valores nulos. Se deberá o bien usar sólovalores 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. Unmapeo como éste permite mapear columnas extra de una tabla de asociación de-muchos-a-muchos a la clase del elementocompuesto. La siguiente es una asociación de-muchos-a-muchos de from Order a Item (de orden a ítem) en donde lafecha de compra ( purchaseDate), el precio (price) y la cantidad (quantity) son propiedades de la asociación.

<class name="eg.Order" .... > ....

<set name="purchasedItems" table="purchase_items" lazy="true"> <key column="order_id"> <composite-element class="eg.Purchase"> <property name="purchaseDate"/> <property name="price"/> <property name="quantity"/>

<many-to-one name="item" class="eg.Item"/> <!-- el atributo "clase" es optativo -->

</composite-element> </set></class>

Por supuesto, no puede haber una referencia a la compra del otro lado, para proveer navegación bidireccional de laasociación. Recuerde que los componentes son "value types", y no aceptan referencias compartidas. Una simple compra(Purchase) puede estar en el set de una Order, pero no puede ser referida por el Item al mismo tiempo.

Incluso son posibles asociaciones ternarias o cuaternarias:

<class name="eg.Order" .... > ....

<set name="purchasedItems" table="purchase_items" lazy="true"> <key column="order_id"> <composite-element class="eg.OrderLine"> <many-to-one name="purchaseDetails class="eg.Purchase"/> <many-to-one name="item" class="eg.Item"/> </composite-element> </set>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

98 de 198 17/02/2009 09:25 a.m.

Page 99: HIBERNATE - Persistencia

</class>

Los elementos compuestos pueden aparecer en consultas, usando la misma sintaxis que las entidades.

8.3. Componentes como índices de un Map

El elemento <composite-map-key> (clave compuesta de mapa) permite mapear una clase componente como la clave deun Map. Asegúrese de reemplazar correctamente los métodos hashCode() y equals() en la clase componente.

8.4. Componentes usados como identificadores compuestos

Se puede usar un componente como identificador de una clase entidad. Dicho componente debe satisfacer ciertosrequisitos:

Debe implementar java.io.Serializable.

Debe reimplementar equals() y hashCode(), de una manera consistente con la noción de igualdad para la clavecompuesta 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 asignarsus propios identificadores.

Use la tag <composite-id> (con elementos <key-property> anidados) en lugar de la declaración de <id> usual. Porejemplo, la clase OrderLine (línea de una orden) tiene una clave primaria que depende de la clave (compuesta) de Order.

<class name="OrderLine">

<composite-id name="id" class="OrderLineId"> <key-property name="lineId"/> <key-property name="orderId"/> <key-property name="customerId"/> </composite-id>

<property name="name"/>

<many-to-one name="order" class="Order" insert="false" update="false"> <column name="orderId"/> <column name="customerId"/> </many-to-one> ....

</class>

Ahora, cualquier clave foránea que se refiera a la tabla ORDERLINE, también será compuesta. Esto se debe declarar enlos mapeos para otras clases. La asociación con OrderLine sería mapeada así:

<many-to-one name="orderLine" class="OrderLine"> <!-- el atributo "class" es optativo, como de costumbre -->

<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.)

Una asociación de-muchos-a-muchos a OrderLine también usa la clave foránea compuesta.

<set name="undeliveredOrderLines"> <key column name="warehouseId"/>

<many-to-many class="OrderLine"> <column name="lineId"/> <column name="orderId"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

99 de 198 17/02/2009 09:25 a.m.

Page 100: HIBERNATE - Persistencia

<column name="customerId"/> </many-to-many>

</set>

La colección de OrderLines en Order usaría:

<set name="orderLines" inverse="true">

<key> <column name="orderId"/> <column name="customerId"/> </key> <one-to-many class="OrderLine"/></set>

(El elemento <one-to-many>, como es usual, no declara ninguna columna).

Si la OrderLine misma posee una colección, también tiene una clave foránea compuesta.

<class name="OrderLine"> .... .... <list name="deliveryAttempts">

<key> <!-- una colección hereda el tipo de clave compuesta --> <column name="lineId"/> <column name="orderId"/> <column name="customerId"/> </key>

<list-index column="attemptId" base="1"/>

<composite-element class="DeliveryAttempt"> ... </composite-element> </set></class>

8.5. Componentes dinámicos

Se puede incluso mapear una propiedad de tipo Map:

<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>

La semántica de un mapeo <dynamic-component> es idéntica a la de <component>. La ventaja de este tipo de mapeo esla capacidad de determinar las verdaderas propiedades del bean en tiempo de despliegue (deployment), simplementeeditando el archivo de mapeo. También es posible la manipulación en tiempo de ejecución del documento de mapeo,usando un parser DOM. Mejor aún, se puede acceder al meta-modelo de tiempo de configuración de Hibernate (ycambiarlo) usando el objeto Configuration .

Capítulo 9. Mapeo de herencia

9.1. Las tres estrategias

Hibernate soporta tres estrategias básicas de mapeo de herencia:

una tabla por jerarquía de clases

una tabla por subclase

una tabla por clase concreta

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

100 de 198 17/02/2009 09:25 a.m.

Page 101: HIBERNATE - Persistencia

Adicionalmente, Hibernate soporta una cuarta clase de polimorfismo, ligeramente diferente:

polimorfismo implícito

Es posible usar estrategias de mapeo diferentes para distintas ramas de la misma jerarquía de herencias, y después haceruso del polimorfismo implícito para lograr polimorfismo todo a lo largo de dicha jerarquía. Sin embargo, Hibernate nosoporta 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, justodebajo de hibernate-mapping. Esto permite extender la jerarquía de clases, simplemente agregando un nuevo archivo demapeo. Se debe especificar el atributo extends en el mapeo de la subclase, nombrando una superclase previamentemapeada. Nota: en el pasado, esta característica hacía que el orden de los documentos de mapeo fuese relevante. DesdeHibernate 3, el orden no importa cuando se usa la palabra "extends". El orden dentro de cada documento individual, aúntiene que tener las superclases antes de las subclases.

<hibernate-mapping>

<subclass name="DomesticCat" extends="Cat" discriminator-value="D"> <property name="name" type="string"/> </subclass>

</hibernate-mapping>

9.1.1. Una tabla por jerarquía de clases

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" severía así:

<class name="Payment" table="PAYMENT">

<id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ...

<subclass name="CreditCardPayment" discriminator-value="CREDIT"> <property name="creditCardType" column="CCTYPE"/> ... </subclass>

<subclass name="CashPayment" discriminator-value="CASH"> ... </subclass>

<subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass>

</class>

Se requiere exactamente una tabla. Con esta estrategia de mapeo, hay una gran limitación: las columnas declaradas por lassubclases, como CCTYPE, no pueden tener constraints NOT NULL.

9.1.2. Una tabla por subclase

El mapeo de una tabla por sublcase se vería así:

<class name="Payment" table="PAYMENT">

<id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="AMOUNT"/> ...

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

101 de 198 17/02/2009 09:25 a.m.

Page 102: HIBERNATE - Persistencia

<joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </joined-subclass>

<joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass>

<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass>

</class>

Se requieren 4 tablas. Las tres tablas de subclases tienen asociaciones por clave primaria a la tabla de la superclase (demodo que el modelo relacional es en realidad una asociación de-uno-a-uno).

9.1.3. Una tabla por subclase, usando un discriminador

Note que la implementación de Hibernate para "una tabla por subclase" no requiere una columna discriminadora. Otrosmapeadores objeto/relacionales usan una implementación diferente de "una tabla por subclase", que sí requiere unacolumna discriminadora en la tabla de la superclase. El enfoque adoptado por Hibernate es mucho más difícil deimplementar, pero probablemente más correcto desde el punto de vista relacional. Si usted desea usar una columnadiscriminadora con la estrategia de "una tabla por subclase", puede combinar el uso de <subclass> y <join>, como sigue:

<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/>

</id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ...

<subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass>

<subclass name="CashPayment" discriminator-value="CASH"> <join table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </join> </subclass>

<subclass name="ChequePayment" discriminator-value="CHEQUE"> <join table="CHEQUE_PAYMENT" fetch="select"> <key column="PAYMENT_ID"/> ... </join> </subclass></class>

La declaración optativa fetch="select" le dice a Hibernate que no capture los datos de la subclase ChequePaymentusando 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 esteabordaje:

<class name="Payment" table="PAYMENT">

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

102 de 198 17/02/2009 09:25 a.m.

Page 103: HIBERNATE - Persistencia

<id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/>

... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass>

<subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass></class>

Para cualquiera de estas estrategias de mapeo, una asociación polimórfica a la clase raíz Payment se mapea usando<many-to-one>.

<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>

9.1.5. Una tabla por clase concreta

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>

<property name="amount" column="AMOUNT"/> ... <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </union-subclass> <union-subclass name="CashPayment" table="CASH_PAYMENT">

... </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 elmismo en todas las tablas de subclases. (Podemos relajar este requisito en versiones futuras de Hibernate). La estrategia degenerador de identidad no está permitida para una herencia que esté usando union-subclass. Lógico, dado que la claveprimaria 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 tablaadicional (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

Un enfoque alternativo es usar polimorfismo implícito:

<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

103 de 198 17/02/2009 09:25 a.m.

Page 104: HIBERNATE - Persistencia

</id> <property name="amount" column="CREDIT_AMOUNT"/> ...

</class>

<class name="CashPayment" table="CASH_PAYMENT"> <id name="id" type="long" column="CASH_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CASH_AMOUNT"/>

...</class>

<class name="ChequePayment" table="CHEQUE_PAYMENT"> <id name="id" type="long" column="CHEQUE_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CHEQUE_AMOUNT"/>

...</class>

Nótese que no mencionamos explícitamente en ningún lado la interfaz Payment. También nótese que las propiedades dePayment 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>.

<any name="payment" meta-type="string" id-type="long"> <meta-value value="CREDIT" class="CreditCardPayment"/> <meta-value value="CASH" class="CashPayment"/> <meta-value value="CHEQUE" class="ChequePayment"/>

<column name="PAYMENT_CLASS"/> <column name="PAYMENT_ID"/></any>

9.1.7. Mezclar polimorfismo implícito con otras estrategias de mapeo de herencia

Hay una cosa más que resaltar, acerca de este mapeo. Como cada una de las subclases está mapeada en su propioelemento <class> (y dado que Payment sólo es una interfaz), ¡cada una de las subclases podría fácilmente ser parte deotra jerarquía de clases! (y aún se podrían usar consultas polimórficas contra la interfaz Payment).

<class name="CreditCardPayment" table="CREDIT_PAYMENT">

<id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="CREDIT_CARD" type="string"/> <property name="amount" column="CREDIT_AMOUNT"/> ... <subclass name="MasterCardPayment" discriminator-value="MDC"/>

<subclass name="VisaPayment" discriminator-value="VISA"/></class>

<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN"> <id name="id" type="long" column="TXN_ID"> <generator class="native"/> </id>

... <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CASH_AMOUNT"/> ... </joined-subclass>

<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

104 de 198 17/02/2009 09:25 a.m.

Page 105: HIBERNATE - Persistencia

<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 instanciasde, por ejemplo, CreditCardPayment y sus subclases, que también implementan Payment, pero no instancias deNonelectronicTransaction.

9.2. Limitaciones

Hay ciertas limitaciones del abordaje a la estrategia "una tabla por clase concreta" usando polimorfismo implícito. Y haylimitaciones un tanto menos restrictivas para los mapeos que usan <union-subclass>.

Una comparación de las limitaciones de ambas estrategias se muestra en la tabla siguiente:

Tabla 9.1. Características de los mapeos de herencia

Estrategia deherencia

polimórficade-muchos-a-uno

polimórficade-uno-a-uno

polimórficade-uno-a-muchos

polimórficade-muchos-a-muchos

load()/get() polimórficos

una tabla porjerarquía declase

<de-muchos-a-uno>

<de-uno-a-uno>

<de-uno-a-muchos>

<de-muchos-a-muchos> s.get(Payment.class, id)

una tabla porsubclase

<de-muchos-a-uno>

<de-uno-a-uno>

<de-uno-a-muchos>

<de-muchos-a-muchos> s.get(Payment.class, id)

una tabla porclaseconcreta(union-subclass)

<de-muchos-a-uno>

<de-uno-a-uno>

<de-uno-a-muchos> (forinverse="true"only)

<de-muchos-a-muchos> s.get(Payment.class, id)

una tabla porclaseconcreta(polimorfismoimplícito)

<any>notsupported not supported <many-

to-any>

s.createCriteria(Payment.class).Restrictions.idEq(id)).uniqueResult()

Capítulo 10. Trabajar con objetos

Hibernate es un dispositivo completo de mapeo objeto/relacional, que no sólo escuda al programador contra los detalles degestión del sistema de base de datos subyacente, sino que ofrece manejo del estado de sus objetos. Y ésta es una formamuy natural de manejar la persistencia en una aplicación de Java, al contrario de los comandos SQL en las capas usualesde persistencia JDBC/SQL.

En otras palabras, los programadores en Hibernate siempre deberían pensar en términos del estado de sus objetos, y nonecesariamente en términos de qué comandos SQL están siendo ejecutados. De esto se encarga Hibernate, y sólo esrelevante para el programador cuando se trate de ajustar la perfomance del sistema.

10.1. Estados de un objeto de Hibernate

Hibernate define y soporta los siguientes estados de un objeto:

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

105 de 198 17/02/2009 09:25 a.m.

Page 106: HIBERNATE - Persistencia

Transitorio (en inglés, transient): un objeto es transitorio cuando ha sido instanciado usando el operador new , y noestá asociado con una sesión de Hibernate. No tiene representación persistente en la base de datos y no se le haasignado ningún identificador. Las instancias transitorias serán destruidas por el recolector de basura (garbagecollector), si la aplicación ya no tiene ninguna referencia a ellas. Use la sesión de Hibernate para volver persistenteun 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 unvalor de identificador. Aunque recién haya sido cargada o grabada, por definición ya está inscripta en el alcance dela 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 tranformadoen 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 sidocerrada. La referencia al objeto aún es válida, y la instancia desprendida puede incluso ser modificada en esteestado. Una instancia desprendida puede ser reasociada a una nueva sesión en el futuro, volviéndola persistente denuevo (junto con todas las modificaciones que haya sufrido). Esta característica es ideal para programar unidades detrabajo largas, que necesiten darle a la aplicación tiempo para "pensar". Las llamamos transacciones "de laaplicació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 estastransiciones) con más detalle.

10.2. Volver persistente un objeto

Las instancias recientemente creadas de una clase son consideradas transitorias por Hibernate. Podemos convertir unainstancia transitoria en persistente asociándola con una sesión:

DomesticCat fritz = new DomesticCat();fritz.setColor(Color.GINGER);fritz.setSex('M');fritz.setName("Fritz");Long generatedId = (Long) sess.save(fritz);

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 acat antes de invocar save(). Se puede usar persist() em lugar de save(), con la semántica definida en el borradortemprano de EJB3.

persist() convierte una instancia transitoria en persistente. Pero no garantiza que el identificador le vaya a serasignado 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. Estoes ú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 elgenerador es "identity" y no "sequence") el INSERT ocurre inmediatamente, sin importar si se está dentro o fuera deuna 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().

DomesticCat pk = new DomesticCat();pk.setColor(Color.TABBY);pk.setSex('F');pk.setName("PK");pk.setKittens( new HashSet() );pk.addKitten(fritz);sess.save( pk, new Long(1234) );

Si el objeto que se hace persistente tiene objetos asociados, (por ejemplo, la colección "kittens", gatitos, en el ejemploanterior), dichos objetos pueden ser hechos persistentes en cualquier orden, a menos que exista una constraint NOT NULLen una columna de clave foránea. El riesgo de violar una constraint de clave foránea no existe, pero sí se puede llegar aviolar 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 lacaracterística de Hibernate llamada persistencia transitiva para grabar los objetos asociados automáticamente. Con ella, nisiquiera las violaciones de NOT NULL ocurren, Hibernate se hace cargo de todo. La persistencia transitiva se discute más

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

106 de 198 17/02/2009 09:25 a.m.

Page 107: HIBERNATE - Persistencia

adelante en este capítulo.

10.3. Cargar un objeto

El método load() de Session provee una manera de capturar una instancia persistente, si ya se conoce su identificador.load() acepta un objeto Class, y cargará el estado en una nueva instancia de esa clase, inicializada en el momento, enestado persistente.

Cat fritz = (Cat) sess.load(Cat.class, generatedId);

// se necesita "envolver" los identificadores de tipos primitivoslong id = 1234;DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );

Alternativamente, se puede cargar estado en una instancia ya existente:

Cat cat = new DomesticCat();// cargar el estado identificado por la clave primaria en Catsess.load( cat, new Long(pkId) );Set kittens = cat.getKittens();

Dese cuenta de que load() lanzará una excepción irrecuperable si no existe la fila de base de datos correspondiente. Si laclase es mapeada con un proxy, load() simplemente devuelve un proxy no inicializado, y no hay contacto con la base dedatos hasta que realmente se invoque un método del proxy. Este comportamiento es muy útil si se desea crear unaasociación con un objeto, sin realmente cargarlo desde la base de datos. También permite cargar múltiples instancias enlotes, 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 basede datos inmediatamente y devuelve null si no existe una fila correspondiente.

Cat cat = (Cat) sess.get(Cat.class, id);if (cat==null) { cat = new Cat(); sess.save(cat, id);}return cat;

Incluso se puede cargar un objeto usando un SQL SELECT ... FOR UPDATE, usando LockMode. Vea la documentación dela API para más información.

Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);

Note que ni las instancias asociadas ni las colecciones contenidas son seleccionadas FOR UPDATE (con el propósito de sermodificadas), 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 útilcuando se están usando triggers de DB para inicializar algunas de las propiedades del objeto.

sess.save(cat);sess.flush(); //fuerza el SQL INSERTsess.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 SQLSELECTs 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). Hibernatesoporta 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 vecesreferidos como QBC y QBE, por "query by criteria" y "query by example", respectivamente). También se puede expresarla consulta en el lenguaje nativo de su base de datos, con soporte opcional de conversión de su resultado en objetos.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

107 de 198 17/02/2009 09:25 a.m.

Page 108: HIBERNATE - Persistencia

10.4.1. Ejecutar consultas

Las consultas en HQL y en SQL nativo son representadas por una instancia de org.hibernate.Query. Esta interfazofrece métodos para vincular parámetros, manejo de resultsets, y la ejecución de la consulta misma. Siempre se obtiene elQuery a partir la sesión actual.

List cats = session.createQuery( "from Cat as cat where cat.birthdate < ?") .setDate(0, date) .list();

List mothers = session.createQuery( "select mother from Cat as cat join cat.mother as mother where cat.name = ?") .setString(0, name) .list();

List kittens = session.createQuery( "from Cat as cat where cat.mother = ?") .setEntity(0, pk) .list();

Cat mother = (Cat) session.createQuery( "select cat.mother from Cat as cat where cat = ?") .setEntity(0, izi) .uniqueResult();]]

Query mothersWithKittens = (Cat) session.createQuery( "select mother from Cat as mother left join fetch mother.kittens");Set uniqueMothers = new HashSet(mothersWithKittens.list());

Una consulta normalmente se ejecuta invocando list(), el resultado de la consulta será cargado completamente en unacolección en memoria. Las entidades de instancia que son devueltas están en estado persistente. El métodouniqueResult() ofrece un atajo si usted ya sabe que la consulta devolverá un solo objeto. Note que las consultas quehacen uso de "captura ansiosa" (eager fetching), normalmente devuelven duplicados del objeto raíz, con sus coleccionesinicializadas. Estos duplicados se pueden eliminar, simplemente usando un Set.

10.4.1.1. Iterar resultados

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 enel caché de 2do nivel. Si no están en el caché, iterate() será más lento que list(), y es posible que requiera variosviajes a la base de datos para una simple consulta: normalmente 1 SELECT inicial que sólo devuelve los identificadores, yN SELECTs adicionales que inicializan las instancias propiamente dichas.

// captura de idsIterator 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; }}

10.4.1.2. Consultas que devuelven T-uplas

Las consultas de Hibernate a veces devuelven t-uplas de objetos, en cuyo caso cada t-upla se devuelve como un array.

Iterator kittensAndMothers = sess.createQuery( "select kitten, mother from Cat kitten join kitten.mother mother") .list() .iterator();

while ( kittensAndMothers.hasNext() ) { Object[] tuple = (Object[]) kittensAndMothers.next();

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

108 de 198 17/02/2009 09:25 a.m.

Page 109: HIBERNATE - Persistencia

Cat kitten = (Cat) tuple[0]; Cat mother = (Cat) tuple[1]; ....}

10.4.1.3. Resultados escalares

Una consulta pueden especificar una propiedad de la clase en la cláusula SELECT. Las funciones agregadas sonconsideradas resultados "escalares", y no entidades en estado persistente.

Iterator results = sess.createQuery( "select cat.color, min(cat.birthdate), count(cat) from Cat cat " + "group by cat.color") .list() .iterator();

while ( results.hasNext() ) { Object[] row = (Object[]) results.next(); Color type = (Color) row[0]; Date oldest = (Date) row[1]; Integer count = (Integer) row[2]; .....}

10.4.1.4. Parámetros vinculados

En Query se proveen métodos para vincular valores con parámetros nombrados, o parámetros ? al estilo JDBC. Alcontrario que con JDBC, Hibernate numera los parámetros empezando de 0. Los parámetros nombrados sonidentificadores 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

pueden ocurrir varias veces en la misma consulta

son autodocumentados

//parámetro nombrado (la forma preferida)Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");q.setString("name", "Fritz");Iterator cats = q.iterate();

//parámetro posicionalQuery q = sess.createQuery("from DomesticCat cat where cat.name = ?");q.setString(0, "Izi");Iterator cats = q.iterate();

//lista de parámetros nombradaList names = new ArrayList();names.add("Izi");names.add("Fritz");Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");q.setParameterList("namesList", names);List cats = q.list();

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 deseaobtener datos), deberían usarse los siguientes métodos de la interfaz Query:

Query q = sess.createQuery("from DomesticCat cat");q.setFirstResult(20);q.setMaxResults(10);List cats = q.list();

Hibernate sabe cómo traducir este límite al el SQL nativo de su base de datos.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

109 de 198 17/02/2009 09:25 a.m.

Page 110: HIBERNATE - Persistencia

10.4.1.6. Iteración navegable

Si su driver de JDBC soporta ResultSets navegables, la interfaz Query puede ser usada para obtener un objetoScrollableResults, el cual permite una navegación flexible del los resultados de la consulta.

Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " + "order by cat.name");ScrollableResults cats = q.scroll();if ( cats.first() ) {

// 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) );

// ahora obtiene la primera página de gatos pageOfCats = new ArrayList(); cats.beforeFirst(); int i=0; while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );

}cats.close()

Nótese que para esta funcionalidad se requiere una conexión abierta a la base de datos. y un cursor. UsesetMaxResult()/setFirstResult() si necesita funcionalidad de paginación desconectada.

10.4.1.7. Externalizar consultas nombradas

Se puede definir consultas nombradas (named queries) en el documento de mapeo. Recuerde usar una sección CDATA si suconsulta 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>

La vinculación de parámetros y la ejecución se hacen programáticamente.

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 puedendefinir 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 únicopara la consulta, mientras que la declaración de una consulta dentro de un elemento <class> se vuelve únicaautomáticamente al afijarle el nombre enteramente calificado de la clase, por ejemplo, eg.Cat.PorNombreYPesoMaximo.

10.4.2. Filtrar colecciones

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.

Collection blackKittens = session.createFilter( pk.getKittens(), "where this.color = ?") .setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) ) .list());

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

110 de 198 17/02/2009 09:25 a.m.

Page 111: HIBERNATE - Persistencia

La colección devuelta se considera una bag, y es una copia de a colección dada. La colección original no es modificada (locual 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ánlimitados a devolver los elementos de colecciones mismos.

Collection blackKittenMates = session.createFilter( pk.getKittens(), "select this.mate where this.color = eg.Color.BLACK.intValue") .list();

Incluso un filtro con su consulta vacía es útil, por ejemplo para cargar un subconjunto de elementos en una colecciónenorme:

Collection tenKittens = session.createFilter( mother.getKittens(), "") .setFirstResult(0).setMaxResults(10) .list();

10.4.3. Consultas "Criteria"

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, paraestos casos:

Criteria crit = session.createCriteria(Cat.class);crit.add( Restrictions.eq( "color", eg.Color.BLACK ) );crit.setMaxResults(10);List cats = crit.list();

Las APIs de Criteria y su pariente Example se discuten con más detalle en Capítulo 15, Consultas Criteria.

10.4.4. Consultas en SQL nativo

Se puede expresar una consulta en SQL, usando createSQLQuery() y dejar que Hibernate se haga cargo del mapeo delresultset a objetos. Note que en cualquier momento se puede llamar session.connection() y usar la Connection deJDBC directamente. Si decide usar la API de Hibernate, debe incluir los alias de SQL entre llaves:

List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10") .addEntity("cat", Cat.class).list();

List cats = session.createSQLQuery( "SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " + "{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " + "FROM CAT {cat} WHERE ROWNUM<10") .addEntity("cat", Cat.class).list()

Las consultas SQL pueden contener parámetros nombrados o posicionales, al igual que las consultas de Hibernate. Sepuede encontrar más información sobre las consultas SQL nativas en Capítulo 16, SQL Nativo.

10.5. Modificar objetos persistentes

Las instancias persistentes transaccionales (esto es, los objetos cargados, grabados, creados o consultados por las sesión)pueden ser manipulados por la aplicación, y cualquier cambio a su estado de persistencia será persistido cuando se leaplique "flush" a la sesión (como se discute más adelante en este capítulo). No hay necesidad de invocar ningún método enparticular (como por ejemplo update(), que sirve para otra cosa) para que las modificaciones se vuelvan persistentes. Asíque la manera más sencilla y directa de actualizar el estado de un objeto es cargarlo con load(), y luego manipularlodirectamente, mientras la sesión esté abierta.

DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );cat.setName("PK");sess.flush(); // los cambios se detectan y son persistidos automáticamente

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

111 de 198 17/02/2009 09:25 a.m.

Page 112: HIBERNATE - Persistencia

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 abordajealternativo, usando instancias desprendidas:

Note que Hibernate no expone una API propia para ejecutar comandos UPDATE o DELETE directamente. Hibernate es unservicio de manejo de persistencia, y no se lo debe concebir en términos de qué comandos ejecuta. JDBC es una APIperfectamente adecuada para ejecutar comandos SQL, se puede obtener una conexión de JDBC en cualquier momentoinvocando session.connection(). Más aún, la noción de "operaciones en masa" entra en conflicto con el mapeoobjeto/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.

10.6. Modificar objetos desprendidos

Muchas aplicaciones necesitan capturar un objeto en una transacción, mandarlo a la capa de interfaz de usuario para sumanipulación, y grabar sus cambios en una nueva transacción. Las aplicaciones que usan este tipo de estrategia enentornos de alta concurrencia, normalmente usan datos versionados para garantizar aislamiento durante la "larga" unidadde trabajo.

Hibernate soporta este modelo, proveyendo "reasociación" de entidades desprendidas usando los métodosSession.update() o Session.merge():

// en la primera sesiónCat cat = (Cat) firstSession.load(Cat.class, catId);Cat potentialMate = new Cat();firstSession.save(potentialMate);

// en una capa más alta de la aplicacióncat.setMate(potentialMate); //mate=pareja

// luego, en una nueva sesiónsecondSession.update(cat); // actualiza catsecondSession.update(mate); // actualiza mate

Si el Cat con identificador catId ya hubiera sido cargado por la segunda sesión (secondSession) cuando la sesión tratabade 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 esainstancia 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 encuestión, si y sólo si desea que su estado también sea actualizado. Esto puede ser automatizado, por supuesto, usandopersistencia 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 instanciadesprendida no debe haber sido modificada!

//simplemente reasociasess.lock(fritz, LockMode.NONE);//primero verifique la versión, luego reasociesess.lock(izi, LockMode.READ);//primero verifique la versión usando SELECT ... FOR UPDATE, luego reasociesess.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 detransacciones 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”.

10.7. Detección de estado automática

Los usuarios de Hibernate han solicitado un método de uso general, que o bien grabe una instancia transitoria (generando

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

112 de 198 17/02/2009 09:25 a.m.

Page 113: HIBERNATE - Persistencia

un identificador nuevo), o bien actualice/reasocie las instancias desprendidas asociadas con el identificador actual. Elmétodo saveOrUpdate() implementa tal funcionalidad:

// en la primera sesiónCat cat = (Cat) firstSession.load(Cat.class, catID);

// en una capa más alta de la aplicaciónCat mate = new Cat();cat.setMate(mate);

// luego, en una nueva sesiónsecondSession.saveOrUpdate(cat); // actualice el estado existente (cat tiene un id no nulo)secondSession.saveOrUpdate(mate); // grabe la instancia nueva (mate tiene un id nulo)

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(), nimerge(). Hay aplicaciones enteras que jamás utilizarán ninguno de estos métodos.

Usualmente, update() o saveOrUpdate() se usan en el siguiente escenario:

la aplicación carga un objeto en la primera sesión

el objeto es pasado a la capa de interfaz de usuario

se le hacen algunas modificaciones al objeto

el objeto baja de nuevo a la capa de lógica de negocios

la aplicación persiste estas modificaciones invocando update() en una segunda sesión

saveOrUpdate() hace lo siguiente

si el objeto ya es persistente en esta sesíón, no hace nada

si ya hay otro objeto asociado con la sesión con el mismo identificador, se produce una excepción

si el objeto no tiene una valor de identificador, se invoca save()

si el identificador del objeto es asignable, y le ha sido recientemente asignado a una instancia recién creadam seinvoca save().

si el objeto es versionado (por una <version> o <timestamp>), y la propiedad versión es el mismo valor asignado aun objeto recién instanciado, se invoca save()

en cualquier otro caso, se invoca update()

y merge() es muy distinto

si ya hay una instancia persistente con el mismo identificador asociada con la sesión, copia el estado del objeto dadoen la instancia persistente.

si no hay ninguna instancia asociada con la sesión, intenta cargarla de la base de datos, o crear una nueva instanciapersistente.

se devuelve la instancia persistente

la instancia dada (como parámetro) no se vuelve asociada con la sesión, permanece desprendida.

10.8. Borrar objetos persistentes

Session.delete() quitará el estado de un objeto de la base de datos. Por supuesto, su aplicación podría aún conteneruna referencia al objeto quitado. Es mejor pensar que delete() está convirtiendo una instancia persistente en transitoria.

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

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

113 de 198 17/02/2009 09:25 a.m.

Page 114: HIBERNATE - Persistencia

posible violar constraints NOT NULL aplicadas a la columna de clave foránea, por ejemplo si se borra al padre antes que alhijo.

10.9. Replicar un objeto entre dos repositorios de datos distintos

Ocasionalmente, es útil poder tomar un árbol de instancias persistentes y hacerlas persistentes en otro repositorio de datosdistinto, sin tener que regenerar los valores de los identificadores.

//obtener un cat de la base de datosSession session1 = factory1.openSession();Transaction tx1 = session1.beginTransaction();Cat cat = session1.get(Cat.class, catId);tx1.commit();session1.close();

//reconciliar con la segunda base de datosSession session2 = factory2.openSession();Transaction tx2 = session2.beginTransaction();session2.replicate(cat, ReplicationMode.LATEST_VERSION);tx2.commit();session2.close();

El ReplicationMode determina cómo el método replicate() lidiará con conflictos que puedan existir con filasexistentes 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 mismoidentificador.

ReplicationMode.EXCEPTION: lanzar una excepción si ya existe una fila en la base de datos que tenga el mismoidentificador.

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 dedatos, 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.

10.10. "Flush" de la sesión

De tanto en tanto la sesión ejecutará los comandos SQL que sean necesarios para sincronizar el estado de la conexiónJDBC con el estado de los objetos mantenidos en la memoria. Este proceso llamado el "nivelado" o "desagote" de la sesión(en inglés, flush), ocurre por defecto en los siguientes puntos:

antes de algunas ejecuciones de consultas

en org.hibernate.Transaction.commit()

en Session.flush()

Los comandos SQL son ejecutados en el siguiente orden

todas las inserciones de entidades, en el mismo orden en que los objetos hayan sido grabados con Session.save()

todas las actualizaciones a entidades

todos los borrados de colecciones

todos los borrados, modificaciones e inserciones de elementos en las colecciones

todas las inserciones de colecciones

todos los borrados de entidades, en el mismo orden en que los objetos hayan sido borrados usando

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

114 de 198 17/02/2009 09:25 a.m.

Page 115: HIBERNATE - Persistencia

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 losllamados JDBC serán ejecutados por la sesión, sólo acerca del orden en que serán ejecutados. Sin embargo, Hibernategarantiza que Query.list(..) nunca devolverá datos rancios.

Es posible cambiar el comportamiento por defecto para que el "flush" ocurra menos frecuentemente. La clase FlushModedefine tres modos diferentes: sólo provocar el "flush" al momento de llamar "commit" (y esto sólo en una transacción de laAPI de Hibernate), provocar el "flush" automáticamente usando la rutina explicada, o nunca provocar el "flush" menosque flush() sea invocado explícitamente. Este último modo es útil para unidades de trabajo largas, en donde la sesión semantiene 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

Cat izi = (Cat) sess.load(Cat.class, id);izi.setName(iznizi);

// podría devolver datos ranciossess.find("from Cat as cat left outer join cat.kittens kitten");

// ¡el cambio a izi no ha tenido "flushing"!...tx.commit(); // flush occurssess.close();

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, lodiscutiremos en el Capítulo 11, Transacciones y concurrencia.

10.11. Persistencia transitiva

Es bastante engorroso grabar, borrar o reasociar objetos individuales, especialmente si se está lidiando con un árbol deobjetos asociados. Un caso común es el de la relación padre/hijo. Considere el siguiente ejemplo:

Si los hijos en una relación padre/hijo fuesen de la índole "value type" (por ejemplo una colección de direcciones, o decadenas), su ciclo de vida dependería del padre, y no haría falta hacer nada para que la propagación en cascada seefectuare de manera adecuada ante cambios de estado. Cuando el padre es grabado, los hijos "value type" son grabadostambién; cuando se borra al padre, a los hijos también, etc. Esto inclusive funciona para operaciones como el borrado deun hijo de una colección: Si un "value type" hijo es borrado de una colección, como los "value types" no pueden tenerreferencias 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í quequitar a una referncia de una colección no significa que pueda ser borrada), y por defecto no hay propagación en cascadade 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 quiereque una operación sea propagada en cascada en la dirección de una asociación, eso se debe indicar en el documento demapeo. Por ejemplo:

<one-to-one name="person" cascade="persist"/>

Los estilos de propagación en cascada pueden ser combinados.

<one-to-one name="person" cascade="persist,delete,lock"/>

Incluso se puede usar cascade="all" para indicar que todas las operaciones deben ser propagadas en dirección de esaasociación. El valor por defecto, cascade="none", especifica que ninguna operación debe ser propagada.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

115 de 198 17/02/2009 09:25 a.m.

Page 116: HIBERNATE - Persistencia

Un tipo especial de propagación en cascada, delete-orphan, se aplica sólo a las asociaciones de-uno-a-muchos, e indicaque 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 especificandocascade="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 usarcascade="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 "delestilo padre/hijo", en donde una grabación/actualización/borrado del padre resulta en una grabación/actualización/borradode 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. Loinverso no se da, sin embargo. Un hijo a quien el padre deje de hacer referencia no es automáticamente borrado, exceptoen el caso de una asociación de-uno-a-muchos mapeada con cascade="delete-orphan". La semántica exacta de larelació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 asaveOrUpdate().

Si un hijo transitorio o desprendido empieza a ser referido por un padre persistente, se le pasa a saveOrUpdate()

si un padre es borrado, todos sus hijos le son pasados a delete()

Si un hijo deja de ser referido por un padre persistente, no pasa nada en especial: la aplicación debería borrar el hijoexplícitamente si hace falta (a menos que haya cascade="delete-orphan", en cuyo caso la clase el hijo que se havuelto 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 oal momento de efectuar "flush". Todas las operaciones, si están habilitadas, se propagan en cascada hacia todas lasentidades que estén a su alcance cuando la operación es ejecutada. Sin embargo, save-upate y delete-orphan sontransitivas hacia las entidades asociadas cuando ocurre el "flush" de la sesión.

10.12. Usar metadatos

Hibernate requiere un modelo muy frondoso, a un meta-nivel, de todas sus entidades y "value-types". De tanto en tanto,este modelo le es muy útil a la aplicación misma. Por ejemplo, la aplicación podría usar los metadatos de Hibernate paraimplementar un algoritmo inteligente de "copia profunda" (deep copy) que entienda cuáles objetos deben ser copiados (porejemplo, "value types" mutables) y cuáles no (por ejemplo, "value types" inmutables y, posiblemente, entidadesasociadas).

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.

Cat fritz = ......;ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);

Object[] propertyValues = catMeta.getPropertyValues(fritz);String[] propertyNames = catMeta.getPropertyNames();Type[] propertyTypes = catMeta.getPropertyTypes();

// obtiene un Map de todas las propiedades que no sean colecciones o asociacionesMap namedValues = new HashMap();for ( int i=0; i<propertyNames.length; i++ ) { if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

116 de 198 17/02/2009 09:25 a.m.

Page 117: HIBERNATE - Persistencia

namedValues.put( propertyNames[i], propertyValues[i] ); }}

Capítulo 11. Transacciones y concurrencia

El punto más importante acerca de Hibernate y el control de concurrencia, es que es muy fácil de entender. Hibernate usadirectamente las conecciones JDBC y los recursos de JTA sin agregar ningún mecanismo adicional de "locking". Lerecomendamos que pase un buen tiempo revisando la documentación sobre JDBC, ANSI, y cómo funciona el aislamientode transacciones en su base de datos en particular.

Hibernate no efectúa "lock" de objetos en memoria. Si aplicación puede esperar el comportamiento que esté definido porel nivel de aislamiento de sus transacciones de base de datos. Note que, gracias a la sesión, que es también un caché conalcance 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) paraefectuar un "lock" de filas pesimista, usando la sintaxis SELECT FOR UPDATE. El control de concurrencia optimista, y estaAPI, 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 lasconversaciones largas.

11.1. La sesión y el alcance (scope) de las transacciones

La fábrica de sesiones (SessionFactory) es un objeto seguro en cuanto al acceso por threads múltiples (thread-safe), y escostosa de crear. Se la concibió para ser compartida por todos los threads de la aplicación. Se la crea una sola vez,normalmente durante el arranque de la aplicación, a partir de una instancia de Configuration.

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, unasola "conversación" o "unidad de trabajo"), y luego sea descartada. Una sesión no intentará obtener una conexión deJDBC, ni una fuente de datos (Connection, Datasource) a menos que sea necesario. Por lo tanto, no consume recursosmientras no es usada.

Para completar este panorama se debe pensar también en las transacciones de base de datos. Una transacción de base dedatos debe ser lo más corta posible, para reducir las posibilidades de conflictos de "lock" en la base de datos. Lastransacciones largas impiden que la aplicación sea "escalable" (es decir, que se adapte con facilidad a una mayor demanday tamaño), por no poder soportar una mayor carga de demandas concurrentes. Por tal motivo, mantener una transacciónabierta 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 basede 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 sedemarcan los límites de una transacción de base de datos?

11.1.1. Unidad de trabajo

¡Antes que nada, no emplee la práctica desaconsejada (antipattern) de una-sesión-por-operación, es decir, no abra ycierre una sesión para cada llamado a la base de datos en un mismo thread! En una aplicación, los llamdos a la base dedatos son hechos en un orden planificado, y son agrupados en unidades de trabajo atómicas. (Nótese que esto tambiénsignifica que tener la conexión en auto-commit después de cada comando SQL es inútil en una aplicación, esta modalidadde trabajo es más acorde con trabajo ad-hoc en una consola SQL. Hibernate inhabilita el auto-commit inmediatamente, oespera que el servidor de aplicaciones lo haga). Las transacciones de base de datos nunca son opcionales, todacomunicación con la base de datos debe ocurrir dentro de una transacción, ya sea para escribir o para leer datos. Como sedijo anteriormente, el comportamiento "auto-commit" debe evitarse, dado que es improbable que un conjunto de muchaspequeñas transacciones tenga mejor performance que una unidad de trabajo claramente definida. Esta última es tambiénmá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

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

117 de 198 17/02/2009 09:25 a.m.

Page 118: HIBERNATE - Persistencia

capa de persistencia Hibernate), se abre una nueva sesión, y todas las operaciones de base de datos son ejecutadas e estaunidad de trabajo. Una vez que el trabajo se haya completado (y la respuesta para el cliente se haya preparado), la sesiónsufre 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 ytransacció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 deeste patrón, y manejar lo que se considera la "sesión actual". Todo lo que el programador debe hacer, es comenzar unatransacción cuando haya que procesar una solicitud (request) al servidor, y finalizar la transacción antes de que larespuesta 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 contenedorde proxies/intercepciones, etc. Una manera estándar de implementar aspectos que conciernen de una misma forma a variosniveles y áreas de la aplicación (en inglés, "cross-cutting aspects"), es usar un contenedor de EJB, el cual puedeimplementar transacciones de una manera declarativa y manejada por el contenedor mismo (CMT). Si se decide usar lademarcació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 invocandosessionFactory.getCurrentSession() en cualquier lugar, y tan a menudo como haga falta. Siempre se obtendrá unasesión que estará dentro del alcance o "scope" de la transacción de base de datos actual. Esto tiene que ser configurado, yasea 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 unafase separada de presentacíón posterior al procesamiento de la solicitud o "request". Extender la transacción de base dedatos hasta que la "view" sea presentada es fácil si se implementa un interceptor propio. Sin embargo, no es fácil de hacersi uno se apoya en un EJBs con transacciones CMT, dado que la transacción se completará tras el return de los métodos delos EJBs, antes de que la presentación de ninguna view hubiere comenzado. Visite el sitio de web de Hibernate paraconsejos y ejemplos acerca de este patrón Open Session in View.

11.1.2. Conversaciones largas

El patrón "una-sesión-por-solicitud" (session-per-request) no es el único concepto útil que puede usarse para diseñarunidades de trabajo. Muchos procesos de negocios requieren toda una serie de interacciones con el usuario, entretejidascon accesos a la base de datos. En las aplicaciones de web y corporativas, no es aceptable que una transacción de base dedatos 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 debase 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énespera haber sido la única persona que haya editado esa información, y que no puedan haber ocurrido otrasmodificaciones conflictivas.

A esto le llamamos una "unidad de trabajo"; desde el punto de vista del usuario, una conversación (o transacción de laaplicació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 tiempoque el usuario se tome para pensar, lo cual ejerce un "lock" sobre la base de datos para impedir modificacionesconcurrentes, 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 usuariosconcurrentes.

Claramente, debemos usar varias transacciones de BD para implementar la conversación. En este caso, mantener unaislamiento entre los procesos de negocio se vuelve en parte responsabilidad de la capa de la aplicación. Una simpleconversació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 ventanade diálogo tipo "paso a paso" o "wizard", que abarque varios ciclos solicitud/respuesta). Esto es más fácil de implementarde 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 concurrenciaoptimista, 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.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

118 de 198 17/02/2009 09:25 a.m.

Page 119: HIBERNATE - Persistencia

Objetos desprendidos: si se decide usar el patrón una sesión por solicitud (session-per-request) ya discutido, todaslas instancias cargadas se convertirán en "desprendidas" (detached) durante el tiempo que el usario se tome parapensar. 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 deque la transacción haya ejecutado su commit, y reconectada cuando ocurra una nueva solicitud del cliente. Estepatrón se conoce como una-sesión-por-conversación y hace que la reasociación de objetos sea innecesaria. Paraaislar modificaciones concurrentes se usa versionado automático, y a la sesión no se le permite hacer "flush"automático, sino explícito.

Ambos patrones, una-sesión-por-spolicitud y una-sesión-por-conversación tienen ventajas y desventajas. Lasdiscutiremos más adelante, en el contexto del control de concurrencia optimista.

11.1.3. Considerar la identidad de los objetos

Una aplicación puede aceder en forma concurrente al mismo estado persistente en dos sesiones diferentes. Pero unainstancia de una clase persistente nunca se comparte entre dos instancias de Session. Por lo tanto, hay dos nocionesdiferentes de identidad:

Identidad de base de datos

foo.getId().equals( bar.getId() )

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 dosnociones son equivalentes, e Hibernate garantiza que la identidad JVM equivale a la identidad de BD. Sin embargo, si laaplicación accede en forma concurrente al mismo dato, puede ocurrir que la misma identidad persistente esté contenida endos instancias de objeto en dos sesiones distintas. En este caso la identidad persistente o de base de datos existe, pero laidentidad 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 quegarantizar la identidad sólo a nivel de unidades de trabajo en un thread simple no requiere un costoso "lock" ni otrosmedios 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 ocurririncluso en lugares insólitos, por ejemplo, si se colocan dos instancias desprendidas en el mismo Set, existe la posibilidad deque ambas tengan la misma identidad de base de datos (es decir, que representen la misma fila) pero no la misma identidadJVM. El programador debe sustituir los métodos equals() y hashCode() en las clases persistentes, e implementar supropia noción de igualdad entre objetos. Sólo una advertencia: nunca use el identificador de base de datos paraimplementar igualdad; use una "clave de negocios", una combinación única y normalmente inmutable de atributos. Si lainstancia transitoria es almacenada en un Set, cambiar el hashcode rompe el contrato del Set. Los atributos de las "clavesde 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 unadiscusión más detallada sobre este tema. También note que éste no es un problema de Hibernate, sino la manera en que losobjetos de Java implementan identidad e igualdad.

11.1.4. Problemas comunes

Nunca use los anti-patrones una-sesión-por-cada-interacción-con-el-usuario ni una-sesión-para-toda-la-aplicación (porsupuesto, puede haber raras excepciones a esta regla). Note que los problemas que listamos a continuación puedenaparecer incluso si se están usando los patrones que sí recomendamos. Asegúrese de que entiende las implicancias de ladecisión de diseño que tome.

Una sesión (Session) no es segura en cuanto a acceso concurrente por múltiples threads (no es "thread-safe"). Lascosas que se supone funcionen en forma concurrente, como las HTTP requests, los session beans, o los workers deSwing workers, causarían condiciones de conflicto por recursos conocidas como "race conditions" si la instancia deuna sesión de Hibernate fuese compartida. Si la sesión de Hibernate está contenida en una sesión de HTTP

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

119 de 198 17/02/2009 09:25 a.m.

Page 120: HIBERNATE - Persistencia

(HttpSession, la cual se discute más adelante), se debería considerar el sincronizar el acceso a la sesión HTTP. Deotra manera, cualquier usuario que cliquee "reload" lo suficientemente rápido, es probable que termine usando lamisma 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, ycerrar 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 enel que estaban antes de que la transacción ocurriese. Esto significa que el estado de la base de datos y el de losobjetos de negocio sí quedan fuera de fase. Normalmente esto no es problema, porque las excepciones no sonrecuperables, 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 comprobadoHibernate 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. Unasolución para esto, es invocar clear() y evict() para manejar el caché de la sesión, pero lo que másprobablemente se debería considerar es un procedimiento de base de datos almacenado (stored procedure) si senecesitan 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 losdatos se vuelvan "rancios".

11.2. Demarcación de transacciones de base de datos

La demarcación de las transacciones de base de datos (o de sistema) siempre es necesaria. No puede ocurrir ningunacomunicación con la base de datos si no es dentro de una transacción. (Esto parece confundir a algunos programadoresacostumbrados a trabajar siempre en modo "auto-commit"). Siempre deben emplearse límites de transacción claros,incluso para las operaciones de lectura. Dependiendo del niverl de aislamiento y de las posibilidades de la base de datos,esto puede no ser obligatorio, pero no hay ninguna desventaja ni se puede dar ninguna razón en contra de demarcarexplícitamente, siempre, los límites de una transacción. Una única transacción de base de datos, ciertamente tendrá muchamejor performance que múltiples pequeñas transacciones, incluso al leer datos.

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" (managedenvironments). 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étodosbegin, commit y rollback de las transacciones él mismo). Un entorno "administrado", normalmente provee transaccionesadministradas por el contenedor ("container-managed transactions o CMT por sus siglas en inglés), con la disposición delas tranascciones definida declarativamente en los archivos de descripción de despliegue o "deployment descriptors" de lossession 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 noadministrados. Utilizar la demarcación programática se puede usar.en ambos casos. Hibernate ofrece una API llamadaTransaction que se traduce en el sistema nativo de transacciones del entorno en el cual el programa haya sidodesplegado. 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.

Usualmente, finalizar una sesión involucra las siguientes fases:

invocar el "flush" de la sesión

invocar "commit" en la transacción de base de datos

cerrar la sesión

manejar las excepciones que hayan podido ocurrir

Efectuar el "flush" de la sesión ha sido discutido antes; ahora le daremos una mirada más detallada a la demarcación detransacciones y al manejo de excepciones, tanto en un entorno administrado como no administrado.

11.2.1. Entornos no administrados

Si la capa de persistencia e Hibernate se ejecuta en un entorno no-administrado, las coneiones a la base de datosusualmente son manejadas por un "pool" de conexiones simple (es decir, no una DataSource) del cual Hibernta obtiene las

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

120 de 198 17/02/2009 09:25 a.m.

Page 121: HIBERNATE - Persistencia

conexiones que necesite. El estilo de manejo de sesión/tranascción se ve así:

// Non-managed environment idiomSession sess = factory.openSession();Transaction tx = null;try { tx = sess.beginTransaction();

// efectuar algo de trabajo ...

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 lasincronización (dependiendo del Modo de "flush" para la sesión. Un llamado a close() marca el fi de la sesión. Laimplicancia 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.

// Estilo para un entorno no-administrado con getCurrentSession()try { factory.getCurrentSession().beginTransaction();

// efectuar algo de trabajo ...

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 depersistencia, y el código que maneja las RuntimeExceptions (y normalmente hace las tareas de limpeza y salida) están encapas 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 estecapítulo.

Note que debería seleccionarse org.hibernate.transaction.JDBCTransactionFactory (el valor pord efecto), y, parael segundo ejemplo, el valor "thread" para hibernate.current_session_context_class.

11.2.2. Usar JTA

Si la capa de persistencia se ejecuta en un servidor de aplicaciones (por ejemplo, detrás de EJB session beans), cadaconexión de base de datos obtenida por Hibernate será automáticamente parte de la transacción global JTA. También sepuede instalar una implementación JTA autónoma, y usarla sin EJB. Hibernate ofrece dos estrategias para integración conJTA:

Si se usan tranascciones manejadas por beans (bean-managed transactions o BMT por sus siglas en inglés), Hibernate ledirá al servidor de aplicaciones que empiece y finalice una transaccción BMT si se usa la API de Transaction API. Deeste modo, le código e manejo de transacciones para un entorno no administrado es idéntico al de un entornoadministrado.

// estilo BMTSession sess = factory.openSession();Transaction tx = null;try {

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

121 de 198 17/02/2009 09:25 a.m.

Page 122: HIBERNATE - Persistencia

tx = sess.beginTransaction();

// efectuar algo de trabajo ...

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ácilpropagación del contexto, se debe usar la API de JTA UserTransaction directamente:

// estilo BMT con getCurrentSession()try { UserTransaction tx = (UserTransaction)new InitialContext() .lookup("java:comp/UserTransaction");

tx.begin();

// efectuar algo de trabajo con la sesión asociada a la transacción factory.getCurrentSession().load(...); factory.getCurrentSession().persist(...);

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 sessionbean, no se hace programáticamente. Así que el código queda reducido a:

// estilo CMT Session sess = factory.getCurrentSession();

// efectuar algo de trabajo ...

En un entorno CMT/EJB incluso el rollback ocurre automáticamente, puesto que una RuntimeException no capturadaemitida por el método de un session bean le dice al contenedor que efectúe rollback en la transacción global. Estosignifica que no hace falta usar la API de Transaction API de Hibernate en absoluto con BMT or CMT, e igualmentese obtiene propagación automática de la sesión "actual" asociada a la transacción.

Cuando se elige la fábrica (factory) de transacciones, note que debería elegirseorg.hibernate.transaction.JTATransactionFactory si se usa JTA directamente (BMT), y debería usarseorg.hibernate.transaction.CMTTransactionFactory en un session bean CMT. Más aún, asegúrese de que suhibernate.current_session_context_class ha sido o bien eliminado (por compatibilidad hacia a trás o "backwardscompatibility"), o bien puesto a "jta".

La operación getCurrentSession() tiene una desventaja en un entorno JTA:, Hay una advertencia sobre el uso delmodo de liberación de conexiones after_statement, el cual es el valor por defecto. Debido a una tonta limitación de laespecificación JTA, para Hibernate no es posible limpiar automáticamente instancias no cerrada de ScrollableResults oIterator que hayan sido devueltas por scroll() o iterate(). Usted debe liberar el cursor de base de datos subyacenteinvocando explícitamente cursor by calling ScrollableResults.close() o Hibernate.close(Iterator) en el bloquefinally. (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).

11.2.3. Manejo de excepciones

Si la sesión provoca una excepción (incluida cualquier SQLException), se debería efectuar un rollback de la transacciónde base de datos inmediatamente, invocar Session.close() y descartar la instancia de la sesión. Algunos métodos deSession no dejarán a la sesión en un estado consistente. Ninguna excepción generada por Hibernate puede ser tratada

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

122 de 198 17/02/2009 09:25 a.m.

Page 123: HIBERNATE - Persistencia

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, esuna excepción del tipo "unchecked" (esto es, que no requiere captura obligatoriamente en tiempo de compilación). No loera en versiones anteriores de Hibernate. En nuestra opinión, no se debería forzar al programador a capturar excepcionesde bajo nivel en la capa de persistencia. En la mayoría de los sistemas, las excepciones "unchecked" son manejadas en unode 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 lepresenta un mensaje de error al usuario de la aplicación, o se adopta algún otro curso de acción apropiado. Note queHibernate puede también emitir otras exceptiones, además de HibernateException. Éstas son, de nuevo, norecuperables, 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 unaJDBCException. De hecho, Hibernate intentará convertir la excepción en una subclase de JDBCException que tenga mássentido. La SQLException subyacente está siempre disponible via JDBCException.getCause(). Hibernate convierte lasSQLException 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 SQLelegido. Pero es posible enchufar una implementación a medida (ver los javadocs para la claseSQLExceptionConverterFactory por detalles). Los subtipos estándar de JDBCException son:

JDBCConnectionException: indica un error en la comunicación con la JDBC subyacente

SQLGrammarException: indica un error sintáctico o gramatical con el SQL emitido

ConstraintViolationException: indica alguna forma de violación de una constraint de integridad

LockAcquisitionException: indica un error al adquirir el nivel de "lock" necesario para efectuar la operaciónsolicitada.

GenericJDBCException: una excepción genérica que no cae en ninguna de las otras categorías.

11.2.4. Expiración de transacciones

Una característica extremadamente importante provista por un entorno aministrado, como EJB, la cual nunca es provistapor un entorno no administrado, es la expiración de las transacciones, o "transaction timeout". Los "timeouts" de lastransacciones que ninguna trnasacción "rebelde" ocupe indefinidamente los recursos del sistema sin devolverle respuestaalguna al usuario. Fuera de un entorno administrado (JTA), Hibernate no puede proveer esta funcionalidad en formacompleta. Sin embargo, puede al menos controlar las operaciones de acceso a datos, asegurándose de que los "puntosmuertos" (deadlocks) y las consultas con resultados enormes estén limitadas a un tiempo definido. En un entornoadministrado, Hibernate puede delegar el "timeout" de las transacciones en JTA. Esta funcionalidad es abstraída por elobjeto Transaction de Hibernate.

Session sess = factory.openSession();try { //set transaction timeout to 3 seconds sess.getTransaction().setTimeout(3); sess.getTransaction().begin();

// 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 sondefinidos declarativamente.

11.3. Control optimista de concurrencia

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

123 de 198 17/02/2009 09:25 a.m.

Page 124: HIBERNATE - Persistencia

El único abordaje consistente con una alta concurrencia y un alta "escalabilidad", es el control de concurrencia optimistacon 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 useconcurrencia optimista. Los casos de uso que mostraremos ocurren en el contexto de una conversación larga, pero elchequeo de versiones tiene la ventaja de también prevenir la pérdida de modificaciones en transacciones de base de datossimples.

11.3.1. Chequeo de versión hecho por la aplicación

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 alconversación transaccional. Este abordaje es el menos eficiente en términos de acceso a la base de datos, y es el másparecido a los "entity beans" de EJB.

// foo es una instancia cargada por una sesión previasession = factory.openSession();Transaction t = session.beginTransaction();

int oldVersion = foo.getVersion();session.load( foo, foo.getKey() ); // cargar el estado actualif ( oldVersion != foo.getVersion() ) throw new StaleObjectStateException();foo.setProperty("bar");

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 paracualquier cosa, especialmente en el ámbito de la computación. Si se usa "foo" como nombre en un ejemplo, casi siemprese 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 puedeusar este abordaje pero saltearse el chequeo de versiones. En tal caso, la estrategia por defecto para conversaciones largasserá "el último commit es el que gana". Tenga en cuenta que esto puede confundir a los usuarios de la aplicación, dadoque pueden experimentar pérdidas de actualizaciones sin mensajes de error, o sufrir la posibilidad de conflictos cuando loscambios se sincronicen.

Claramente, el chequeo manual de versiones sólo es factible en circunstancias muy triviales, y en la mayoría de lasaplicaciones no es práctico. A menudo hay que chequear no sólo instancias simples, sino todo un árbol de objetosmodificados. Hibernate ofrece chequeo automático de versión, usando uno de estos paradigmas de diseño: "sesiónextendida", o "instancais desprendidas".

11.3.2. Sesión extendida y versionado automático

Una sola instancia de sesión y sus clases persistentes son usadas por toda la conversación (lo cual se conoce comouna-sesión-por-conversación). Hibernate chequea las instancias de las versiones cuando ocurre el "flush", emitiendo unaexcepción si se detecta modificaciones concurrentes. El capturar y manejar estas excepciones queda a cargo delprogramador (opciones comunes que se le dan al usuario son: sincronizar los cambios manualmente, o recomenzar laconversació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 porchequear versiones o reasociar instancias desprendidas, ni tiene que recargar instancias en cada transacción de base dedatos.

// foo es una instancia cargada anteriormente por una sesíón anteriorTransaction t = session.beginTransaction(); // obtiene una nueva conexión JDBC, comienza la transac

foo.setProperty("bar");

session.flush(); // sólo para la última transacción de la conversaciónt.commit(); // tambien devuelve la conexión JDBCsession.close(); // sólo para la última transacción de la conversación

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

124 de 198 17/02/2009 09:25 a.m.

Page 125: HIBERNATE - Persistencia

El objeto foo todavía sabe en qué sesión fue cargado. Comenzar una nueva transacción de base de datos en una sesiónvieja, obtiene una nueva conexión y reanuda la sesión. Efectuar un "commit" de una transacción de base de datosdesconecta una sesión de la coinexión JDBC y devuelve la conexión al "pool". Tras la reconexión, para forzar un chequeode versión en los datos que no se estén actualizando, se puede invocar Session.lock() con LockMode.READ en cualquierobjeto que pueda haber sido actualizado por otra transacción. Usted no necesita un "lock" sobre cualquier dato que ustedesté 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 esaconversación. Por eso, sólo esta última transacción de BD incluiría la operación flush(), y luego también el llamado de lasesió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 elusuario se toma para reaccionar. Por ejemplo, una HttpSession debería mantenerse tan chica como sea posible. Como lasesión es también el caché de primer nivel, y contiene todos los objetos cargados, probablementa sólo podamos usar estaestrategia en uns pocos ciclos solicitur/respuesta. Una sesión debería usarse para una sola conversación, dado que prontocontendrá datos "pasados".

(Note que versiones anteriores de Hibernate requerían que la sesión se conectara y desconectara explícitamente. Dichosmé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 (nisiquiera 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 delcontexto de sesión actual. Para esto se necesita proveer una implementación a medida de CurrentSessionContext. Veala Wiki de Hibernate para ejemplos:

11.3.3. Objetos desprendidos y versionado automático

Cada interacción con el repositorio persistente ocurre en una nueva sesión. Sin embargo, las mismas instancias persistentesson 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(), orSession.merge().

// foo es una instancia cargada en una sesión previafoo.setProperty("bar");session = factory.openSession();Transaction t = session.beginTransaction();session.saveOrUpdate(foo); // use merge() si "foo" puede haber sido cargada anteriormentet.commit();session.close();

De nuevo, Hibernate chequeará ls versiones de instancia al ocurrir el "flush", lanzando una excepción si han ocurridoactualizaciones 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.

11.3.4. Crear un método a medida para el versionado automático

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 lapropiedad está sucia.

Los sistemas de base de datos anticuados a menudo son estáticos y no pueden ser modificados. U otras aplicacionespueden estar accediendo a dichas base de datos, sin saber cómo manejar números de versiones ni incluso timestamps. Enambos casos, el versionado no puede basarse en una columna en particular de una tabla. Para forzar un chequeo deversión sin un mapeo de versión o timestamp, con una comparación del estado de todos los campos en una fila, habiliteoptimistic-lock="all" en el mapeo de <class>. Note que esto sólo funciona, conceptualmente, si Hibernate puedecomparar 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 sesuperpongan. Si se asigna optimistic-lock="dirty" al mapear la <class>, Hibernate sólo comparará los campos sucios

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

125 de 198 17/02/2009 09:25 a.m.

Page 126: HIBERNATE - Persistencia

cuando ocurra el "flush".

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 deversión y actualizar la información. Si se desa usar persistencia transitiva para la reasociación en cascada de entidadesrelacionadas, es posible que Hibernate ejecute algunos UPDATEs innecesarios. Esto normalmente no es un problema, peropuede que se ejecuten triggers on update en la base de datos, cuando no se les ha efectuado ningún cambio a las instanciasdesprendidas. Se puede personalizar este comportamiento más a medida, asignando select-before-update="true" emel mapeo de <class>, forzando a Hibernate a practicar un SELECT de la instancia para asegurarse de que realmentehayan ocurrido cambios, antes de actualizar la fila.

11.4. "Lock" pesimista

La idea no es que los programadores inviertan demasiado tiempo preocupándose por estrategias de "lock". Normalmente,es suficiente que se especifique un nivel de asilamiento para las conexiones JDBC y después simplemente dejar que labase de datos haga todo el trabajo. No obstante, los programadores avanzados probablemente deseen a veces obtener"locks" pesimistas exclusivos, o recapturar locks al comienzo de una transacción nueva.

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 porlos mecanismos siguientes:

LockMode.WRITE se adquiere automáticamente cuando Hibernate actualiza o inserta una fila.

LockMode.UPGRADE puede ser adquirido ante una solicitud explícita del usuario, usando SELECT ... FOR UPDATEen una base de datos que soporte dicha sintaxis.

LockMode.UPGRADE_NOWAIT puede ser adquirido ante una solicitud explícita del usuario, usando SELECT ... FORUPDATE 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 unatransacción. Los objetos asociados con la sesión a través de un llamado a update() o saveOrUpdate() tambiéncomienzan en este modo de "lock".

La "solicitud (request) explícita del usuario" se expresa de alguna de las siguientes maneras:

Un llamado a Session.load(), especificando un LockMode.

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 lasesió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 oUPGRADE_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 emitiruna excepción). Esto asegura que la aplicación sea portátil.

11.5. Modos de liberación de conexiones

El antiguo comportamento de Hibernate (2.x) en relación con el manejo de conexiones JDBC era que un sesión obteníauna conexión cuando se necesitaba por primera vez, y luego se aferraba a dicha conexión hasta que la sesión se cerrara.Hibernate 3.x introdujo la sesión de "modos de liberación de conexiones". Note que la discusión siguiente, sólo espertinente para conexiones provistas a través de un ConnectionProvider; las conexiones provistas por el usuario quedanafuera del horizonte de esta discusión. Los diferentes modos de liberación se identifican con valores enumerados de

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

126 de 198 17/02/2009 09:25 a.m.

Page 127: HIBERNATE - Persistencia

org.hibernate.ConnectionReleaseMode:

ON_CLOSE: es esencialmente el comportamiento anticuado descrito anteriormente. La sesión de Hibernate obtieneuna conexión cuando es necesario efectuar algún acceso a JDBC, y retiene dicha conexión hasta que la sesión secierre.

AFTER_TRANSACTION: dice que se liberen las conexiones una vez que la org.hibernate.Transaction se hayacompletado.

AFTER_STATEMENT (también conocida como "liberación agresiva"): dice que las conexiones se liberen luego de laejecución de todos y cada uno de los comandos. Esta liberación agresiva es omitida si el comando deja recursosabiertos que aún estén asociados con la sesión actual; actualmente la única situación en la que esto ocurre es cuandose usa un org.hibernate.ScrollableResults.

El parámetro de configuración hibernate.connection.release_mode se usa para especificar qué modo de liberaciónusar. Los valores posibles son:

auto (el valor por defecto): esta opción le delega la decisión al modo de liberación recibido por el métodoorg.hibernate.transaction.TransactionFactory.getDefaultReleaseMode(). Con JTATransactionFactory,devuelve ConnectionReleaseMode.AFTER_STATEMENT; con JDBCTransactionFactory, devuelveConnectionReleaseMode.AFTER_TRANSACTION. Casi nunca es buena idea cambiar el comportamiento pordefecto, porque las fallas en relación con este valor normalmente indican errores de programación (bugs) osuposiciones incorrectas en el código.

on_close: indica que se use ConnectionReleaseMode.ON_CLOSE. Este valor se conserva por compatibilidad haciaatrás, pero su uso se desaconseja.

after_transaction: indica que se use ConnectionReleaseMode.AFTER_TRANSACTION. Este valor no deberíaser usado en entornos JTA. También note que, con ConnectionReleaseMode.AFTER_TRANSACTION, si unasesión se considera que está en modo auto-commit, las conexiones serán liberadas como si el modo de liberaciónfuera AFTER_STATEMENT.

after_statement: indica que se usa ConnectionReleaseMode.AFTER_STATEMENT. Adicionalmente, elConnectionProvider que esté configurado es consultado para comprobar si soporta este valor(supportsAggressiveRelease()). Si no, el modo de liberación es reinicializado aConnectionReleaseMode.AFTER_TRANSACTION. Este valor es seguro sólo en entornos en donde se puede o bienrecapturar la misma conexión JDBC subyacente cada vez que hacemos un llamado aConnectionProvider.getConnection(), o bien en entornos con auto-commit en donde no importa si es la mismaconexión la que nos es devuelta.

Capítulo 12. Interceptores y eventos

A menudo es útil que la aplicación reaccione a ciertos eventos que ocurren dentro de Hibernate. Esto permite laimplementación de ciertos tipos de funcionalidad genérica, y extender la funcionalidad de Hibernate.

12.1. Interceptores

La interfaz Interceptor provee métodos de retorno o "callbacks" desde la sesión a la aplicación, permitiéndole a laaplicació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 propiedadcreateTimestamp cuando se crea un Auditable, y actualiza la propiedad lastUpdateTimestamp cuando un Auditablees actualizado.

Se puede o bien implementar Interceptor directamente, o (preferentemente) extender EmptyInterceptor.

package org.hibernate.test;

import java.io.Serializable;import java.util.Date;import java.util.Iterator;

import org.hibernate.EmptyInterceptor;import org.hibernate.Transaction;

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

127 de 198 17/02/2009 09:25 a.m.

Page 128: HIBERNATE - Persistencia

import org.hibernate.type.Type;

public class AuditInterceptor extends EmptyInterceptor {

private int updates; private int creates; private int loads;

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) {

if ( entity instanceof Auditable ) { updates++; for ( int i=0; i < propertyNames.length; i++ ) { if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) { currentState[i] = new Date(); return true; } } } return false; }

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; }

public void afterTransactionCompletion(Transaction tx) { if ( tx.wasCommitted() ) { System.out.println("Creaciones: " + creates + ", Actualizaciones: " + updates, "Cargas: } updates=0; creates=0; loads=0; }

}

Los interceptores vienen en dos variantes: Con alcance de sesión (session-scoped), y con alcance de toda la fábrica desesiones (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.

Session session = sf.openSession( new AuditInterceptor() );

Un interceptor con alcance de SessionFactory se registra con el objeto Configuration antes de consctruir laSessionFactory. En este caos, el interceptor provisto será a aplicado a todas las sesiones abiertas por esa fábrica desesiones. Esto es cierto, a menos que se use una sesión que haya sido abierta especificando explícitamente otrointerceptor. 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 noalmacenar estado específico de una sesión.

new Configuration().setInterceptor( new AuditInterceptor() );

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

128 de 198 17/02/2009 09:25 a.m.

Page 129: HIBERNATE - Persistencia

12.2. Sistema de eventos

Si se tiene que reaccionar a eventos específicos de la capa de persistencia, se puede hacer uso de la arquitectura deeventos de Hibernate3. El sistema de eventos puede ser usado por añadidura a los interceptores, o en reemplazo de ellos.

Esencialmente todos los métodos de la interfaz Session están correlacionados con un evento. Hay un LoadEvent, unFlushEvent, etc. (consulte la DTD del archivo de configuración XML o el paquete org.hibernate.event para una listacompleta de los tipos de evento definidos). Cuando se invoca uno de estos métodos, la sesión de Hibernate genera elevento apropiado y lo pasa a los "listeners" (escuchas) de eventos que hayan sido configurados para ese tipo. De fábrica ypor defecto, esos "listeners" implementan el mismo procesamiento en el que esos métodos habrían resultado. De todosmodos, usted es libre de crear listener a medida, que implemente alguna de la interfaces correspondientes (por ejemplo, elevento LoadEvent es procesado por una implementación registrada de la interfaz LoadEventListener interface), en cuyocaso 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 entresolicitudes 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 lasclases utilitarias de base (o incluso los listeners que ya vienen incluidos en Hibernate, dado que fueron declarados comono-finales con ese propósito). Los listeners a medida pueden ser registrados programáticamente a través del objetoConfiguration, o especificados declarativamente en el archivo de configuración XML de Hibernate (no se soporta laconfiguración declarativa mediante el archivo ".properties"). He aquí un ejemplo de un listener a medida para el evento"load".

public class MyLoadListener implements LoadEventListener { // éste es el único método definido por la interfaz LoadEventListener public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType) throws HibernateException { if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) { throw MySecurityException("Unauthorized access"); } }}

También se necesita una entrada de configuración, indicándole a Hibernate que use este listener, además del listener pordefecto.

<hibernate-configuration>

<session-factory> ... <event type="load"> <listener class="com.eg.MyLoadListener"/> <listener class="org.hibernate.event.def.DefaultLoadEventListener"/> </event> </session-factory>

</hibernate-configuration>

O se lo puede registrar programáticamente:

Configuration cfg = new Configuration();LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };cfg.EventListeners().setLoadEventListeners(stack);

Los listeners registrados declarativamente no pueden compartir instancias. Si la misma clase es usada por muchoselementos <listener/>, cada referencia resultará en una instancia separada de esa clase. Se se necesita la capacidad decompartir 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 unaimplementación de listener puede estar implementando más de una interfaz. Al tener que especificar también el tipodurante el registro, se vuelve fácil habilitar/inhanilitar los listeners a medida durante la cofiguración.

12.3. Seguridad declarativa de Hibernate

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

129 de 198 17/02/2009 09:25 a.m.

Page 130: HIBERNATE - Persistencia

La seguridad declarativa de Hibernate usualmente se maneja en una capa de "fachada de sesión" (session façade). A partitde Hibernate3, a algunas acciones se les puede asignar permisos vía JACC, y se pueden autorizar vía JAAS. Ésta es unafuncionalidad opcional, construida encima de la arquitectura de eventos.

Primero se deben configurar los listeners de eventos, para habilitar el uso de autorizaciones JAAS.

<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/><listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/><listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>

<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>

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.

Luego, todavía en hibernate.cfg.xml, se vinculan los permisos a roles:

<grant role="admin" entity-name="User" actions="insert,update,read"/>

<grant role="su" entity-name="User" actions="*"/>

Los nombres de los roles son aquéllos que sean comprendidos por su proveedor JACC.

Capítulo 13. Procesamiento en lotes

Un abordaje ingenuo al problema de insertar 100.000 filas en una base de datos podría verse así:

Session session = sessionFactory.openSession();Transaction tx = session.beginTransaction();for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); session.save(customer);}tx.commit();session.close();

Esto fallaría. provocando un OutOfMemoryException más o menos alrededor de la línea número 50.0000. Esto se debe aque 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 (eninglés, "batch processing"), es indispensable habilitar el uso del procesamiento en lotes JDBC, si se pretende alcanzar unaperformance 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 generadorde 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 lainteracción con el caché de 2do nivel.

13.1. Inserciones en lotes

Cuando se persisten los objetos, se debe efectuar el flush() de la sesión, y luego clear() con regularidad, para controlarel tamaño del caché de 1er nivel.

Session session = sessionFactory.openSession();Transaction tx = session.beginTransaction();

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

130 de 198 17/02/2009 09:25 a.m.

Page 131: HIBERNATE - Persistencia

for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); session.save(customer); if ( i % 20 == 0 ) { //20, igual que el tamaño del lote JDBC //aplicarle "flush" a un lote de inserts y liberar memoria session.flush(); session.clear(); }}

tx.commit();session.close();

13.2. Actualizaciones en lotes

Para capturar y modificar data se aplican las mismas ideas. Adicionalmente, se necesita usar scroll() para tomar ventajade los cursores que existan del lado del servidor de BD, para consultas que devuelvan muchas filas de datos.

Session session = sessionFactory.openSession();Transaction tx = session.beginTransaction();

ScrollableResults customers = session.getNamedQuery("GetCustomers").setCacheMode(CacheMode.IGNORE).int count=0;while ( customers.next() ) { Customer customer = (Customer) customers.get(0); customer.updateStuff(...); if ( ++count % 20 == 0 ) { //aplicarle "flush" a un lote de actualizaciones y liberar memoria session.flush(); session.clear(); }}

tx.commit();session.close();

13.3. La interfaz StatelessSession

Alternativamente, Hibernate provee una API orientada a comandos, que puede ser usada por datos que fluyan desde yhacia la base de datos en forma de objetos desprendidos. Una StatelessSession (sesión sin estado) no tiene un contextode persistencia asociado ni provee mucha de la semántica de alto nivel respecto del ciclo de vida, que sí proveen lassesiones normales; en particular, no tiene un caché de 1er nivel ni interactúa con un caché de 2do nivel. No implementa unwrite-behind transaccional ni chequeo automático de datos "sucios". Las operaciones que se efectúen usando una sesiónsin estado nunca se propagan en cascada. Las sesiones sin estado ingoran las colecciones y los modelos de eventos einterceptores. También son vulnerables a los efectos de "data aliasing", dado que carecen de caché de 1er nivel. Unasesión sin estado es una abstracción de más bajo nivel, muy cercana a la JDBC subyacente.

StatelessSession session = sessionFactory.openStatelessSession();Transaction tx = session.beginTransaction();

ScrollableResults customers = session.getNamedQuery("GetCustomers") .scroll(ScrollMode.FORWARD_ONLY);while ( customers.next() ) { Customer customer = (Customer) customers.get(0); customer.updateStuff(...); session.update(customer);}

tx.commit();session.close();

Note que, en este ejemplo de código, las instancias de Customer (cliente) devueltas por la consulta son automáticamentedesprendidas. Nunca están asociadas con ningún contexto de persistencia.

Se considera que las operaciones insert(), update() y delete() definidas por la interfaz StatelessSession sonoperaciones 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.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

131 de 198 17/02/2009 09:25 a.m.

Page 132: HIBERNATE - Persistencia

13.4. Operaciones del tipo "Lenguaje de Modificacion de Datos" (DML-style)

Como se discutió anteriormente, el mapeo automático objeto/relacional automático y transparente se ocupa de laadministración del "estado" de los objetos. Esto significa que el estado de un objeto dado está disponible en memoria, porlo que manipular la BD directamente usando comandos SQL del tipo "operaciones de modificación de datos" (datamodification language o DML por sus siglas en inglés: es decir, INSERT, UPDATE, DELETE) no afectará el estado enmemoria. De todos modos, el lenguaje HQL de Hibernate provee métodos para este tipo de ejecución de comandos demodificación de datos, llamada "modificación de datos en masa" o "bulk-style DML". Véase HQL.

La pseudo-sintaxis para os comandos UPDATE y DELETE es: ( UPDATE | DELETE ) FROM? NombreDeLaEntidad (WHEREwhere_conditions)?. Algunos puntos a destacar:

en la cláusula "from", la palabra FROM es optativa

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.

La cláusula "where" también es optativa.

A modo de ejemplo: para ejecutar un HQL UPDATE, use el método Query.executeUpdate() (el método se bautizó enhonor al método de JDBC PreparedStatement.executeUpdate()):

Session session = sessionFactory.openSession();Transaction tx = session.beginTransaction();

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 esconsistente con la especificación de EJB3. Sin embargo, se puede forzar a Hibernate a reinicializar adecuadamente losvalores de las propiedades version y timestamp usando un a "actualización versionada" (versioned update). Esto selogra agregando la palabra VERSIONED luego de UPDATE keyword.

Session session = sessionFactory.openSession();Transaction tx = session.beginTransaction();String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";int updatedEntities = s.createQuery( hqlUpdate ) .setString( "newName", newName ) .setString( "oldName", oldName ) .executeUpdate();tx.commit();session.close();

Note que los tipos de versión a medida (org.hibernate.usertype.UserVersionType) no se permiten en conjunción concomandos update versioned.

Para ejecutar un HQL DELETE, use el mismo método Query.executeUpdate():

Session session = sessionFactory.openSession();Transaction tx = session.beginTransaction();

String hqlDelete = "delete Customer c where c.name = :oldName";// o también: String hqlDelete = "delete Customer where name = :oldName";int deletedEntities = s.createQuery( hqlDelete ) .setString( "oldName", oldName ) .executeUpdate();tx.commit();session.close();

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

132 de 198 17/02/2009 09:25 a.m.

Page 133: HIBERNATE - Persistencia

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 enmasa puede resultar en la ejecución de múltiples comandos SQL (para las joined-subclass, por ejemplo). El númerodevuelto indica el número real de entidades afactadas por el comando. Volviendo al ejemplo de la joined-subclass, uncomando DELETE ejecutado contra una de las subclases podría resultar en borrados no sólo en la tabla a la cual lasubclase está mapeada, sino también borrados en la tabla "raíz", y potencialmente en otras tablas de joined-subclass másabajo en la jerarquía de herencias.

La pseudo-sintaxis de los comandos INSERT es: INSERT INTO NombreDeLaEntidad lista_de_propiedadescomando_select.

Algunos puntos a destacar:

Sólo se soporta la forma INSERT INTO ... SELECT ... ; la forma INSERT INTO ... VALUES ... no.

La especificación de la lista_de_propiedades es análoga a la especificación de columnas en un comando SQLINSERT. Para entidades que estén involucradas en una herencia mapeada, sólo las propiedades que estén definidasdirectamente en ese nivel de clase en particular pueden ser usadas en la lista_de_propiedades: las propiedades de lasuperclase no están permitidas, y las de subclases no tienen sentido. En otras palabras, los comandos INSERT soninherentemente polimórficos.

el comando_select puede ser cualquier consulta válida de selección HQL, con la precaución de que los tipos deretorno deben corresponder con los tipos esperados por el insert. Actualmente, esto es verificado durante lacompilación, en lugar de relegar dicho chequeo a la base de datos. Note, sin embargo, que esto podría causarproblemas entre tipos que Hibernate considere o no "equivalentes" más que "iguales". Por ejemplo, para Hibernateno son iguales los tipos org.hibernate.type.DateType y org.hibernate.type.TimestampType, aunque la basede 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 especificarlaexplícitamente en la lista_de_propiedades (en cuyo caso el valor se toma del comando_select) o se puede omitir dela lista de propiedades (en cuyo caso se usa un valor generado). Esta última opción está disponible solamentecuando 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), ycualquier implementación de org.hibernate.id.PostInsertIdentifierGenerator.org.hibernate.id.TableHiLoGenerator tampoco se puede usar, porque no expone una manera facitble deobtener sus valores en un comando "select".

Para las propiedades mapeadas como version o timestamp, el comando INSERT da dos opciones: se puede o bienespecificarlas explícitamente en la lista_de_propiedades (en cuyo caso el valor se toma del comando_select) o se laspuede puede omitir de la lista de propiedades (en cuyo caso se usa el valor semilla o "seed value" definido pororg.hibernate.type.VersionType).

Un ejemplo de ejecución del comando HQL INSERT:

Session session = sessionFactory.openSession();Transaction tx = session.beginTransaction();

String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c whint createdEntities = s.createQuery( hqlInsert ).executeUpdate();tx.commit();session.close();

Capítulo 14. HQL: El lenguaje de consultas de Hibernate

Hibernate está equipado con un lenguaje de consultas extremadamente potente: HQL. Aunque cuenta con una sintaxisdeliberadamente parecida a SQL, HQL es totalmente orientado a objetos, capaz de entender nociones como herencia,polimorfismo y asociación.

14.1. Relevancia de mayúsculas y minúsculas

En las consultas, el uso de mayúsculas o minúsculas es irrelevante, excepto por los nombres de propiedades y clases deJava. De modo que SeLeCT es lo mismo que sELEct y lo mismo que SELECT, pero org.hibernate.eg.FOO no es lo

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

133 de 198 17/02/2009 09:25 a.m.

Page 134: HIBERNATE - Persistencia

mismo que org.hibernate.eg.Foo, y foo.barSet no es lo mismo que foo.BARSET.

Este manual usa minúsculas para las palabras HQL. A algunos usuarios les parece que las consultas escritas todas enmayúsula son más legibles, pero a nosotros esta convención nos parece fea cuando está inserta en código Java.

14.2. La cláusula "from"

La consulta de Hibernate más simple tiene esta forma:

from eg.Cat

la cual simplemente devuelve todas las instancias de la clase eg.Cat.Normalmente no necesitamos calificar el nombre de la clase de esta manera, dado que el valor por defecto esauto-import. Así que casi siempre escribimos:

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:

from Cat as cat

Esta consulta le asigna el alias cat a las instancias de Cat, de manera que podamos usar dicho alias más adelante en laconsulta. La palabra as es optativa, también podríamos escribir:

from Cat cat

Pueden aparecer varias clases, resultando en un producto cartesiano o "cross join".

from Formula, Parameter

from Formula as form, Parameter as param

Se considera una práctica buena el nombrar los alias en las consultas usando una minúscula inicial, en concordancia conlos estándares de nombrado para variables locales en Java. (por ejemplo domesticCat).

14.3. Asociaciones y "joins"

Se les puede asignar alias a entidades asociadas, o incluso a elementos de una colección de valores, usando la palabrajoin.

from Cat as cat inner join cat.mate as mate left outer join cat.kittens as kitten

from Cat as cat left join cat.mate.kittens as kittens

from Formula form full join form.parameter param

Los tipos de join son un préstamo de la especificación ANSI de SQL.

inner join

left outer join

right outer join

full join (el cual normalmente no es muy útil)

Las frases inner join, left outer join and right outer join pueden abreviarse así:

from Cat as cat join cat.mate as mate

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

134 de 198 17/02/2009 09:25 a.m.

Page 135: HIBERNATE - Persistencia

left join cat.kittens as kitten

Y se pueden especificar condiciones adicionales del join, usando la palabra with:

from Cat as cat left join cat.kittens as kitten with kitten.bodyWeight > 10.0

Adicionalmente, un join calificado con la palabra "fetch" (captura) permite que las asociaciones o colecciones de valoressean inicializados junto con sus objetos padres, usando un solo SELECT. Esto es particularmente útil en el caso de lascolecciones; efectivamente reemplaza los "outer joins" y las inicializaciones haraganas (lazy) del archivo de mapeo paraasociaciones y colecciones. Vea la Sección 19.1, “Estrategias de captura (fetch)” para más información.

from Cat as cat inner join fetch cat.mate left join fetch cat.kittens

A un join del tipo "fetch" normalmente no se le asignan alias, porque los objetos asociados que devuelve no deberían serusados en la cláusula "where", ni en ninguna otra cláusula. Además, los objetos asociados no son devueltos directamenteen 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 sepodría necesitar un alias, es si se está usando un join tipo "fetch" recursivo a otra colección más.

from Cat as cat inner join fetch cat.mate left join fetch cat.kittens child left join fetch child.kittens

Note que la construcciones con fetch no deben ser usadas en consultas que luego invoquen iterate() (aunque sí sepuede 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 cualnormalmente contendrá duplicados debido a esta "captura ansiosa", y por lo tanto la cantidad de filas no será la que seespera. Tampoco se debe usar fetch junto con condiciones "with" ad hoc. Al efectuar un join tipo "fetch" con más de unacolección, es posible crear un producto cartesiano, así que tenga cuidado en este caso. Usar joins "fetch" con múltiplescolecciones, además, da a menudo resultados inesperados con los mapeos de "bag", así que tenga cuidado acerca de cómoformula sus consultas en este caso. Por último, note que las construcciones full join fetch y right join fetch notienen sentido.

Si se está usando captura haragana (lazy fetching) a nivel de las propiedades, con instrumentación bytecode, es posibleforzar a Hibernate para que capture esas propiedades haraganas inmediatamente en la primera consulta, usando fetchall properties.

from Document fetch all properties order by name

from Document doc fetch all properties where lower(doc.name) like '%cats%'

14.4. Formas de la sintaxis de los "joins"

HQL soporta dos formas de asociación por "joins": implícita y explícita.

Las consultas mostradas en la sección anterior usan todas la forma explícita en donde la palabra "join" esexplí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) usandola 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.

from Cat as cat where cat.mate.name like '%s%'

14.5. Referirse a la propiedada identificadora

En general, hay 2 maneras de referirse a la propiedad indentificadora de una entidad:

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

135 de 198 17/02/2009 09:25 a.m.

Page 136: HIBERNATE - Persistencia

La propiedad especial id puede ser usada para referirse a la propiedad indentificadora de una entidad siempre ycuando 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-identificadorallamda "id", la propiedad identificadora compuesta sólo puede ser referida por el nombre asignado. En caso contrario, sepuede 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 ala propiedad identificadora, sin importar el verdadero nombre. A consecuencia de esto, cuando había propiedadesno-identificadoras llamadas id, las consultas no podían referirse a ellas.

14.6. La cláusula "select"

La cláusula select elige qué objetos y propiedades serán devueltos como conjunto de resultados o "resultset". Sea:

select matefrom 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 consultade una forma más compacta, como:

select cat.mate from Cat cat

Las consultas pueden devolver propiedades de cualquier tipo de valor, incluidos valores de tipo "componente":

select cat.name from DomesticCat catwhere cat.name like 'fri%'

select cust.name.firstName from Customer as cust

Las consultas pueden devolver múltiples objetos, en un array Object[],

select mother, offspr, mate.namefrom DomesticCat as mother inner join mother.mate as mate left outer join mother.kittens as offspr

o como una List,

select new list(mother, offspr, mate.name)from DomesticCat as mother inner join mother.mate as mate left outer join mother.kittens as offspr

o como un verdadero objeto Java, seguro en cuanto a tipo (typesafe)

select new Family(mother, mate, offspr)from DomesticCat as mother join mother.mate as mate left join mother.kittens as offspr

asumiendo que la clase Family tuviere el constructor apropiado

Se les pueden asignar alias a las expresiones seleccionadas usando as:

select max(bodyWeight) as max, min(bodyWeight) as min, count(*) as nfrom Cat cat

Esto es de lo más util cuando se usa en conjunción con select new map:

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

136 de 198 17/02/2009 09:25 a.m.

Page 137: HIBERNATE - Persistencia

select new map( max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n )from Cat cat

Esta consulta devuelve un Map de los alias a los valores seleccionados.

14.7. Funciones agregadas

Las consultas HQL incluso pueden devolver los resultados de funciones agregadas que fueran aplicadas a propiedades:

select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat)from Cat cat

Las funciones agregadas que se soportan son:

avg(...), sum(...), min(...), max(...)

count(*)

count(...), count(distinct ...), count(all...)

En la cláusula "select" se pueden usar operadores aritméticos, concatenación, y funciones SQL reconocidas:

select cat.weight + sum(kitten.weight)from Cat cat join cat.kittens kittengroup by cat.id, cat.weight

select firstName||' '||initial||' '||upper(lastName) from Person

Las palabras distinct y all pueden ser usadas, y con la misma semántica que en SQL.

select distinct cat.name from Cat cat

select count(distinct cat.name), count(cat) from Cat cat

14.8. Consultas polimórficas

Una consulta como:

from Cat as cat

devuelve no sólo instancias de Cat, sino también de las subclases como DomesticCat. Las consultas de Hibernate puedennombrar cualquier clase o interfaz Java en la cláusula from. La consulta devolverá las instancias de todas las clasespersistentes que extiendan o implementen dicha clase o interfaz. La siguiente consuta devuelve todos los objetospersistentes:

from java.lang.Object o

Sea una interfaz Named, implementada por varias clases persistentes:

from Named n, Named m where n.name = m.name

Note que las dos últimas consultas requerirán más de dos comandos SQL SELECT. Esto implica que un cláusula order byno ordenaría correctamente la totalidad del conjunto de resultados, y que no se puede usar Query.scroll() paranavegarlos.

14.9. la cláusula "where"

La cláusula where permite acotar la lista de instancias devueltas. Si no hay alias, uno puede referirse a las propiedades porsu nombre:

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

137 de 198 17/02/2009 09:25 a.m.

Page 138: HIBERNATE - Persistencia

from Cat where name='Fritz'

si hay un alias, úsese un nombre de propiedad calificado:

from Cat as cat where cat.name='Fritz'

devuelve las instancias de gatos llamados 'Fritz'.

select foofrom Foo foo, Bar barwhere foo.startDate = bar.date

devolverá las instancias de Foo para las cuales exista una instancia de Bar cuya propiedad date sea igual a la propiedadstartDate de Foo. Las expresiones con "path" compuesto hacen que la cláusula "where" sea extremadamente poderosa.Considere:

from Cat cat where cat.mate.name is not null

Esta consulta se traduciría en un comando SQL con varios (inner) joins. Si se escribiera algo como esto:

from Foo foowhere foo.bar.baz.customer.address.city is not null

en SQL se acabaría con una consulta que requeriría 4 joins de tablas.

El operador = puede usarse para comparar no sólo propiedades, sino también instancias:

from Cat cat, Cat rival where cat.mate = rival.mate

select cat, matefrom Cat cat, Cat matewhere cat.mate = mate

La propiedad id (en minúscula), puede usarse para hacer referencia al identificador único de un objeto. Véase laSección 14.5, “Referirse a la propiedad identificadora” para más información.

from Cat as cat where cat.id = 123

from Cat as cat where cat.mate.id = 69

La segunda consulta es eficiente, no requiere joins.

También se pueden usar propiedades de los identificadores compuestos. Supongamos que Person tiviera un identificadorcompuesto que consistiese en country y medicareNumber. (de nuevo, véase la Sección 14.5, “Referirse a la propiedadidentificadora” para más información acerca de referirse a las propiedades identificadoras):

from bank.Person personwhere person.id.country = 'AU' and person.id.medicareNumber = 123456

from bank.Account accountwhere account.owner.id.country = 'AU' and account.owner.id.medicareNumber = 123456

Nuevamente, la segunda consulta no requiere "joins"

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 suvalor de discriminador.

from Cat cat where cat.class = DomesticCat

Tambiém se pueden usar componentes, o tipos a medida compuestos, o las propiedades de dichos tipos decomponentes/tipos. Vea la Sección 14.17, “Componentes” para más información.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

138 de 198 17/02/2009 09:25 a.m.

Page 139: HIBERNATE - Persistencia

Un tipo "any" tiene las propiedades especiales id y class, que permiten expresar un join de la siguiente manera (en dondeAuditLog.item es una propiedad mapeada con <any>):

from AuditLog log, Payment paymentwhere log.item.class = 'Payment' and log.item.id = payment.id

Note que, en la consulta precedente, log.item.class y payment.class se estarían refiriendo a valores de columnascompletamente 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 +, -, *, /

operadores de comparación binaria =, >=, <=, <>, !=, like

operaciones lógicas and, or, not

paréntesis ( ), para indicar agrupamientos

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

concatención de cadenas ...||... or concat(...,...)

current_date(), current_time(), current_timestamp()

second(...), minute(...), hour(...), day(...), month(...), year(...),

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()

str() para convertir valores numéricos o temporales en una cadena legible

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 cualespueden ser cuantificadas usando some, all, exists, any, in.

cualqiuer función escalar SQL soportada por la base de datos, como sign(), trunc(), rtrim(), sin()

parámetros ppsicionales al estilo JDBC

parámetros nombrados: :name, :start_date, :x1

expresiones SQL literales 'foo', 69, 6.66E+2, '1970-01-01 10:00:01.0'

constantes public static final de java eg.Color.RAYADO

in y between pueden ser usados como sigue:

from DomesticCat cat where cat.name between 'A' and 'B'

from DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )

y la forma negada puede ser escrita:

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

139 de 198 17/02/2009 09:25 a.m.

Page 140: HIBERNATE - Persistencia

from DomesticCat cat where cat.name not between 'A' and 'B'

from DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )

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:

<property name="hibernate.query.substitutions">true 1, false 0</property>

Esto reemplazará las palabras true y false con los valores literales 1 y 0 en el SQL traducido desde este HQL.

from Cat cat where cat.alive = true

Se puede chequear el tamaño de la colección con la propiedad especial size, o con la función especial size().

from Cat cat where cat.kittens.size > 0

from Cat cat where size(cat.kittens) > 0

Para las colecciones indexadas, es preferible referirse a los valores mínimos y máximos usando las funciones minindex ymaxindex. Análogamente, se puede aludir a los elementos mínimo y máximo de una colección de un tipo básico utilizandolas funciones minelement y maxelement.

from Calendar cal where maxelement(cal.holidays) > current_date

from Order order where maxindex(order.items) > 100

from Order order where minelement(order.items) > 10000

Las funciones SQL any, some, all, exists, in se soportan cuando se les pasa como parámetro el conjunto deelementos o índices de una colección (éstas son las funciones elements e indices), o el resultado de una subconsulta(véase abajo).

select mother from Cat as mother, Cat as kitwhere kit in elements(foo.kittens)

select p from NameList list, Person pwhere p.name = some elements(list.names)

from Cat cat where exists elements(cat.kittens)

from Player p where 3 > all elements(p.scores)

from Show show where 'fizard' in indices(show.acts)

Note que las construcciones: size, elements, indices, minindex, maxindex, minelement, maxelement sólo pueden serusadas 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").

from Order order where order.items[0].id = 1234

select person from Person person, Calendar calendarwhere calendar.holidays['national day'] = person.birthDay and person.nationality.calendar = calendar

select item from Item item, Order orderwhere order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11

select item from Item item, Order order

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

140 de 198 17/02/2009 09:25 a.m.

Page 141: HIBERNATE - Persistencia

where order.items[ maxindex(order.items) ] = item and order.id = 11

La expresión dentro de [] puede incluso ser una expresión aritmética.

select item from Item item, Order orderwhere order.items[ size(order.items) - 1 ] = item

HQL también trae una función index() ya incorporada, para elementos de una asociación de-uno-a-muchos, o unacolección de valores.

select item, index(item) from Order order join order.items itemwhere index(item) < 5

Pueden ser usadas las funciones SQL escalares que la DB subyacente soporte:

from DomesticCat cat where upper(cat.name) like 'FRI%'

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 custfrom Product prod, Store store inner join store.customers custwhere prod.name = 'widget' and store.location.name in ( 'Melbourne', 'Sydney' ) and prod = all elements(cust.currentOrder.lineItems)

Para que se dé una idea, algo como:

SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_orderFROM customers cust, stores store, locations loc, store_customers sc, product prodWHERE prod.name = 'widget' AND store.loc_id = loc.id AND loc.name IN ( 'Melbourne', 'Sydney' ) AND sc.store_id = store.id AND sc.cust_id = cust.id AND prod.id = ALL( SELECT item.prod_id FROM line_items item, orders o WHERE item.order_id = o.id AND cust.current_order = o.id )

14.11. La cláusula "order by"

La lista devuelta por una consulta puede ser ordenada por cualquier propiedad de las clases o componentes devueltos:

from DomesticCat catorder by cat.name asc, cat.weight desc, cat.birthdate

Las palabras opcionales asc o desc indican orden ascendente o descendente respectivamente.

14.12. La cláusula "group by"

Una consulta que devuelva valores agregados, puede ser agrupada por cualquier propiedad de las clases o componentesdevueltos.

select cat.color, sum(cat.weight), count(cat)from Cat catgroup by cat.color

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

141 de 198 17/02/2009 09:25 a.m.

Page 142: HIBERNATE - Persistencia

select foo.id, avg(name), max(name)from Foo foo join foo.names namegroup by foo.id

También se permite la cláusula having:

select cat.color, sum(cat.weight), count(cat)from Cat catgroup by cat.colorhaving cat.color in (eg.Color.TABBY, eg.Color.BLACK)

si la base de datos subyacente lo soporta (por ejemplo, MySQL no), dentro de having se permiten funciones SQL, ydentro del order by se permiten funciones agregadas.

select catfrom Cat cat join cat.kittens kittengroup by cat.id, cat.name, cat.other, cat.propertieshaving avg(kitten.weight) > 100order 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 sonno-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 sepermiten subconsultas correlacionadas (subconsultas que hagan referencia a un alias en la consulta exterior.

from Cat as fatcatwhere fatcat.weight > ( select avg(cat.weight) from DomesticCat cat)

from DomesticCat as catwhere cat.name = some ( select name.nickName from Name as name)

from Cat as catwhere not exists ( from Cat as mate where mate.mate = cat)

from DomesticCat as catwhere cat.name not in ( select name.nickName from Name as name)

select cat.id, (select max(kit.weight) from cat.kitten kit)from Cat as cat

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.

14.14. Ejemplos de HQL

Las consultas en Hibernate pueden llegar a ser bastante potentes y complejas. De hecho, el poder del lenguaje de consultases una de las características más "vendedoras" de Hibernate. He aquí algunas consultas de ejemplo, muy similares a lasque he usado en un proyecto reciente. ¡Note que la mayoría de las consultas que usted escribirá serán mucho más simplesque éstas!

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

142 de 198 17/02/2009 09:25 a.m.

Page 143: HIBERNATE - Persistencia

La siguiente consulta devuelve el id de la orden, el número de items y el valor total de todas las órdenes impagas por uncliente 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 PRICEtiene 4 "inner joins" y una subconsulta (no correlacionada).

select order.id, sum(price.amount), count(item)from Order as order join order.lineItems as item join item.product as product, Catalog as catalog join catalog.prices as pricewhere order.paid = false and order.customer = :customer and price.product = product and catalog.effectiveDate < sysdate and catalog.effectiveDate >= all ( select cat.effectiveDate from Catalog as cat where cat.effectiveDate < sysdate )group by orderhaving sum(price.amount) > :minAmountorder by sum(price.amount) desc

¡Qué monstruo! En realidad, en la vida real no soy muy amigo de las subconsultas, así que mi consulta quedó más bien deesta manera:

select order.id, sum(price.amount), count(item)from Order as order join order.lineItems as item join item.product as product, Catalog as catalog join catalog.prices as pricewhere order.paid = false and order.customer = :customer and price.product = product and catalog = :currentCataloggroup by orderhaving sum(price.amount) > :minAmountorder by sum(price.amount) desc

La consulta siguiente cuenta el número de pagos en cada estado, exceptuando los pagos que figuren como "aprobaciónpendiente" (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.

select count(payment), status.namefrom Payment as payment join payment.currentStatus as status join payment.statusChanges as statusChangewhere payment.status.name <> PaymentStatus.AWAITING_APPROVAL or ( statusChange.timeStamp = ( select max(change.timeStamp) from PaymentStatusChange change where change.payment = payment ) and statusChange.user <> :currentUser )group by status.name, status.sortOrderorder by status.sortOrder

Si yo hubiera mapeado la colección statusChanges collection como una lista, la consulta habría sido mucho más fácil deescribir.

select count(payment), status.namefrom Payment as payment join payment.currentStatus as statuswhere payment.status.name <> PaymentStatus.AWAITING_APPROVAL or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <> :currentUsergroup by status.name, status.sortOrderorder by status.sortOrder

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

143 de 198 17/02/2009 09:25 a.m.

Page 144: HIBERNATE - Persistencia

La siguiente consulta usa la función isNull() de MS SQL Server para devolver todas las cuentas y pagos no efectuados ala organización a la cual pertenece el usuario actual. Se traduce en un SQL con tres inner joins, un outer join y unsubselect contra las tablas the ACCOUNT, PAYMENT, PAYMENT_STATUS, ACCOUNT_TYPE, ORGANIZATION y ORG_USER.

select account, paymentfrom Account as account left outer join account.payments as paymentwhere :currentUser in elements(account.holder.users) and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID)order by account.type.sortOrder, account.accountNumber, payment.dueDate

En algunas DB, debemos deshacernos del subselect correlacionado.

select account, paymentfrom Account as account join account.holder.users as user left outer join account.payments as paymentwhere :currentUser = user and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID)order by account.type.sortOrder, account.accountNumber, payment.dueDate

14.15. Actualizaciones y borrados en masa

HQL ahora soporta comandos update, delete e insert ... select .... Vea la Sección 13.4, “Operaciones del tipo"Lenguaje de Modificacion de Datos" (DML-style)” para más detalles.

14.16. Consejos y trucos

Se puede contar el número de resultados de una consulta sin necesidad de devolver todo el resultado:

( (Integer) session.createQuery("select count(*) from ....").iterate().next() ).intValue()

Para ordenar un resultado de acuerdo al tamaño de las colecciones de hijos, use la consulta siguiente:

select usr.id, usr.namefrom User as usr left join usr.messages as msggroup by usr.id, usr.nameorder by count(msg)

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.

from User usr where size(usr.messages) >= 1

si su base de datos no soporta subconsultas, use la consulta siguiente:

select usr.id, usr.namefrom User usr.name join usr.messages msggroup by usr.id, usr.namehaving count(msg) >= 1

Como esta solución no puede devolver a un User que tenga 0 mensajes a causa del inner join, la siguiente forma tambiénes útil:

select usr.id, usr.namefrom User as usr left join usr.messages as msggroup by usr.id, usr.namehaving count(msg) = 0

Se pueden asociar las propiedades de un JavaBean a parámetros nombrados de una consulta:

Query q = s.createQuery("from foo Foo as foo where foo.name=:name and foo.size=:size");

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

144 de 198 17/02/2009 09:25 a.m.

Page 145: HIBERNATE - Persistencia

q.setProperties(fooBean); // fooBean has getName() and getSize()List foos = q.list();

Las colecciones se pueden volver paginables, usando la interfaz Query más un filtro:

Query q = s.createFilter( collection, "" ); // el filtro trivialq.setMaxResults(PAGE_SIZE);q.setFirstResult(PAGE_SIZE * pageNumber);List page = q.list();

Los elementos de las colecciones pueden ser ordenados o agrupados usando un filtro de consultas:

Collection orderedCollection = s.filter( collection, "order by this.amount" );Collection counts = s.filter( collection, "select this.type, count(this) group by this.type" );

Se puede averiguar el tamaño de una colección sin inicializarla:

( (Integer) session.createQuery("select count(*) from ....").iterate().next() ).intValue();

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":

select p.name from Person p

select p.name.first from Person p

en donde la propiedad "name" de Person es un componente. Los componentes también pueden ser usados en cláusulas"where":

from Person p where p.name = :name

from Person p where p.name.first = :firstName

Los componentes también pueden ser usados en cláusulas "order by":

from Person p order by p.name

from Person p order by p.name.first

Otro uso común de los componentes, es en los constructores de valor de fila.

14.18. Sintaxis del "Constructor de Valor de Fila" (row value constructor)

HQL soporta el uso de la sintaxis de Constructores de Valor de Fila (row value constructors), a veces tambiénllamada sintaxis de "t-upla", incluso aunque la base de datos subyacente no soporte este concepto. Nos estamos refiriendoa a comparaciones multi-valores, típicamente asociadas con componentes. Considérese una entidad "Person", que defineun componente "name":

from Person p where p.name.first='John' and p.name.last='Jingleheimer-Schmidt'

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":

from Person p where p.name=('John', 'Jingleheimer-Schmidt')

También sería útil especificar esto en la cláusula "select":

select p.name from Person p

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

145 de 198 17/02/2009 09:25 a.m.

Page 146: HIBERNATE - Persistencia

Otro momento en que la sintaxis de constructor de valor de fila puede ser beneficiosa, es cuando se usensubconsultas que necesiten comparar contra valores múltiples:

from Cat as catwhere not ( cat.name, cat.color ) in ( select cat.name, cat.color from DomesticCat cat)

Una cosa a considerar, al decidir si se quiere usar esta sintaxis, es que la consulta será dependiente del orden de lassub-propiedades del componente en los metadatos.

Capítulo 15. Consultas Criteria

Hibernate posee una API para consultas "criteria" (por criterios), intuitiva y extensible.

15.1. Crear una instancia de Criteria

La interfaz org.hibernate.Criteria representa una consulta contra una clase persistente en particular. La sesión actúacomo fábrica de instancias de Criteria.

Criteria crit = sess.createCriteria(Cat.class);crit.setMaxResults(50);List cats = crit.list();

15.2. Acotar el resultado

(N.del.T):en inglés, "criterio" se dice "criterion", y en plural, es "criteria". La interfaz, muy usada, de Hibernate quedenomina a este tipo de consultas, se llama "Criteria". Una interfaz mucho menos conocida, "Criterion", en un paquetedel mismo nombre, casi nunca son usados directamente por el programador, porque le son agregados a un "Criteria"ente bambalinas.

Una "criterion" individual es una instancia de la interfaz org.hibernate.criterion.Criterion. La claseorg.hibernate.criterion.Restrictions define métodos "fábrica" para obtener ciertos tipos de Criterion que yavienen incluidos.

List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "Fritz%") ) .add( Restrictions.between("weight", minWeight, maxWeight) ) .list();

Las "Restrictions" (restricciones) pueden ser agrupadas lógicamente.

List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "Fritz%") ) .add( Restrictions.or( Restrictions.eq( "age", new Integer(0) ), Restrictions.isNull("age") ) ) .list();

List cats = sess.createCriteria(Cat.class) .add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) ) .add( Restrictions.disjunction() .add( Restrictions.isNull("age") ) .add( Restrictions.eq("age", new Integer(0) ) ) .add( Restrictions.eq("age", new Integer(1) ) ) .add( Restrictions.eq("age", new Integer(2) ) ) ) ) .list();

Hay un buen rango de tipos de "criterion" que ya vienen incluidos (subclases de Restrictions), pero uno en especialpermite especificar SQL directamente:

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

146 de 198 17/02/2009 09:25 a.m.

Page 147: HIBERNATE - Persistencia

List cats = sess.createCriteria(Cat.class) .add( Restrictions.sqlRestriction("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRI .list();

El comodín {alias} será reemplazado con el alias de fila de la entidad consultada.

Un camino alternativo para obtener un "criterion" es obtenerlo a partir de una instancia de Property. Se puede crear unaProperty invocando Property.forName().

Property age = Property.forName("age");List cats = sess.createCriteria(Cat.class) .add( Restrictions.disjunction() .add( age.isNull() ) .add( age.eq( new Integer(0) ) ) .add( age.eq( new Integer(1) ) ) .add( age.eq( new Integer(2) ) ) ) ) .add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) ) .list();

15.3. Ordenar el resultado

Se puede ordenar los resultados usando org.hibernate.criterion.Order.

List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "F%") .addOrder( Order.asc("name") ) .addOrder( Order.desc("age") ) .setMaxResults(50) .list();

List cats = sess.createCriteria(Cat.class) .add( Property.forName("name").like("F%") ) .addOrder( Property.forName("name").asc() ) .addOrder( Property.forName("age").desc() ) .setMaxResults(50) .list();

15.4. Asociaciones

Se puede especificar constraints en entidades relacionadas, navegando a través de asociaciones usandocreateCriteria().

List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "F%") ) .createCriteria("kittens") .add( Restrictions.like("name", "F%") ) .list();

note que el segundo createCriteria() devuelve una instancia nueva de Criteria, la cual se refiere a los elementos dela colección kittens.

La siguiente forma alternativa es útil en ciertas circunstancias:

List cats = sess.createCriteria(Cat.class) .createAlias("kittens", "kt") .createAlias("mate", "mt") .add( Restrictions.eqProperty("kt.name", "mt.name") ) .list();

(createAlias() no crea una instancia nueva de Criteria.)

Note que las colecciones de "kittens" (gatitos) contenidas en las instancias de Cat devueltas por las dos consultasanteriores, ¡no están pre-filtradas por el Criteria! Si desea obrener sólo los "kittens" que cumplen con el Criteria, debe usarun ResultTransformer.

List cats = sess.createCriteria(Cat.class)

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

147 de 198 17/02/2009 09:25 a.m.

Page 148: HIBERNATE - Persistencia

.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");}

15.5. Captura dinámica de asociaciones

Se puede especificar semántica de asociaciones en tiempo de ejecución, usando setFetchMode().

List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "Fritz%") ) .setFetchMode("mate", FetchMode.EAGER) .setFetchMode("kittens", FetchMode.EAGER) .list();

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.

15.6. Consultas "Example"

La clase org.hibernate.criterion.Example permite construir consultas "criterion" a partir de una instancia dada.

Cat cat = new Cat();cat.setSex('F');cat.setColor(Color.BLACK);List results = session.createCriteria(Cat.class) .add( Example.create(cat) ) .list();

Las propiedades versión, identificadores y asociaciones son ignoradas. Por defecto, las propiedades con valor nulo sonexcluidas.

Se puede ajustar cómo el Example se aplica:

Example example = Example.create(cat) .excludeZeroes() //excluir las propiedades con valor cero .excludeProperty("color") //excluir una propiedad llamada "color" .ignoreCase() //efectuar comparaciones de cadenas sin importar mayúsculas/minúscul .enableLike(); //usar "like' para las comparaciones de cadenasList results = session.createCriteria(Cat.class) .add(example) .list();

Se puede incluso usar Examples para ubicar criteria en objetos asociados:

List results = session.createCriteria(Cat.class) .add( Example.create(cat) ) .createCriteria("mate") .add( Example.create( cat.getMate() ) ) .list();

15.7. Proyecciones, agregado y agrupamiento

La clase org.hibernate.criterion.Projections (proyecciones) es una fábrica de instancias de la clase Projection.Le aplicamos una proyección a una consulta llamando a setProjection().

List results = session.createCriteria(Cat.class) .setProjection( Projections.rowCount() ) .add( Restrictions.eq("color", Color.BLACK) ) .list();

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

148 de 198 17/02/2009 09:25 a.m.

Page 149: HIBERNATE - Persistencia

List results = session.createCriteria(Cat.class) .setProjection( Projections.projectionList() .add( Projections.rowCount() ) .add( Projections.avg("weight") ) .add( Projections.max("weight") ) .add( Projections.groupProperty("color") ) ) .list();

En una consulta de tipo Criteria, no es necesario que haya explícitamente "group by"s. Algunos tipos de proyección sonlas denominadas proyecciones agrupadoras (grouping projections) las cuales aparecen en el SQL como cláusulas groupby.

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:

List results = session.createCriteria(Cat.class) .setProjection( Projections.alias( Projections.groupProperty("color"), "colr" ) ) .addOrder( Order.asc("colr") ) .list();

List results = session.createCriteria(Cat.class) .setProjection( Projections.groupProperty("color").as("colr") ) .addOrder( Order.asc("colr") ) .list();

Los métodos alias() y as() simplemente envuelven una instancia de Projection dentro de otra instancia deProjection con alias. Como atajo, se puede asignar un alias al agregar la proyección a una lista de proyecciones:

List results = session.createCriteria(Cat.class) .setProjection( Projections.projectionList() .add( Projections.rowCount(), "catCountByColor" ) .add( Projections.avg("weight"), "avgWeight" ) .add( Projections.max("weight"), "maxWeight" ) .add( Projections.groupProperty("color"), "color" ) ) .addOrder( Order.desc("catCountByColor") ) .addOrder( Order.desc("avgWeight") ) .list();

List results = session.createCriteria(Domestic.class, "cat") .createAlias("kittens", "kit") .setProjection( Projections.projectionList() .add( Projections.property("cat.name"), "catName" ) .add( Projections.property("kit.name"), "kitName" ) ) .addOrder( Order.asc("catName") ) .addOrder( Order.asc("kitName") ) .list();

También se pueda usar Property.forName() para expresar proyecciones:

List results = session.createCriteria(Cat.class) .setProjection( Property.forName("name") ) .add( Property.forName("color").eq(Color.BLACK) ) .list();

List results = session.createCriteria(Cat.class) .setProjection( Projections.projectionList() .add( Projections.rowCount().as("catCountByColor") ) .add( Property.forName("weight").avg().as("avgWeight") ) .add( Property.forName("weight").max().as("maxWeight") ) .add( Property.forName("color").group().as("color" ) ) .addOrder( Order.desc("catCountByColor") ) .addOrder( Order.desc("avgWeight") ) .list();

15.8. Consultas y subconsultas desprendidas (detached)

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

149 de 198 17/02/2009 09:25 a.m.

Page 150: HIBERNATE - Persistencia

La clase DetachedCriteria permite crear una consulta por fuera del alcance de una sesión, y más tarde ejecutarla usandoalguna sesión arbitraria.

DetachedCriteria query = DetachedCriteria.forClass(Cat.class) .add( Property.forName("sex").eq('F') );

Session session = ....;Transaction txn = session.beginTransaction();List results = query.getExecutableCriteria(session).setMaxResults(100).list();txn.commit();session.close();

Una DetachedCriteria también puede usarse para expresar una subconsulta. Instancias de Criterion que involucrensubconsultas pueden ser obtenidas via subconsultas o Property.

DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class) .setProjection( Property.forName("weight").avg() );session.createCriteria(Cat.class) .add( Property.forName("weight").gt(avgWeight) ) .list();

DetachedCriteria weights = DetachedCriteria.forClass(Cat.class) .setProjection( Property.forName("weight") );session.createCriteria(Cat.class) .add( Subqueries.geAll("weight", weights) ) .list();

Incluso son posibles las subconsultas correlacionadas:

DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2") .setProjection( Property.forName("weight").avg() ) .add( Property.forName("cat2.sex").eqProperty("cat.sex") );session.createCriteria(Cat.class, "cat") .add( Property.forName("weight").gt(avgWeightForSex) ) .list();

15.9. Consultas por identificador natural

Para la mayoría de las consultas, incluidas las consultas Criteria, el caché de consultas no es muy eficiente, porque lainvalidación del caché ocurre con demasiada frecuencia. Pero hay un tipo especial de consulta con la cual se puedeoptimizar el algoritmo de invalidación: la búsqueda por clave natural constante. En algunas aplicaciones, este tipo deconsulta ocurre frecuentemente. La API de Criteria posee disposiciones especiales para este caso de uso.

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.

A continuación, hay que habilitar el caché de consultas de Hibernate.

Finalmente, Restrictions.naturalId() permite hacer un uso más eficiente del algoritmo de caché.

session.createCriteria(User.class) .add( Restrictions.naturalId() .set("name", "gavin")

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

150 de 198 17/02/2009 09:25 a.m.

Page 151: HIBERNATE - Persistencia

.set("org", "hb") ).setCacheable(true) .uniqueResult();

Capítulo 16. SQL nativo

También se puede expresar consultas en el dialecto SQL nativo de su base de datos. Esto es útil si se quiere utilizarcaracterísticas particulares de una base de datos, como "hints" en las consultas, o la palabra CONNECT en Oracle. Tambiénprovee un estadio de migración claro desde una aplicación basada en SQL/JDBC hacia Hibernate.

Hibernate3 permite especificar SQL escrito a mano (incluyendo procedimientos almacenados o "stored procedures") paratodas las operaciones de creación, modificación, borrado y carga.

16.1. Usar un SQLQuery

La ejecución de SQL nativo se controla a través de la interfaz SQLQuery, la cual se obtiene invocandoSession.createSQLQuery(). A continuación se describe cómo usar esta API para consultar:

16.1.1. Consultas escalares

La consulta SQL más básica consiste en obtener una lista de valores escalares.

sess.createSQLQuery("SELECT * FROM CATS").list();sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();

Estas dos consultas devolverán una List de arrays de Objects (Object[]) con valores escalares para cada columna de latabla 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().

sess.createSQLQuery("SELECT * FROM CATS") .addScalar("ID", Hibernate.LONG) .addScalar("NAME", Hibernate.STRING) .addScalar("BIRTHDATE", Hibernate.DATE);

Esta consulta especificó:

la cadena SQL de la consulta

las columnas y tipos a devolver

Esto aún devolverá un array de Objetcs, pero ahora no usará ResultSetMetadata sino que obtendrá explícitamente lascolumnas ID, NAME and BIRTHDATE como un Long, una String y un Short, respectivamente, a partir del resultadosubyacente. Esto tambíen significa que sólo serán devueltas esas 3 columnas, incluso cuando la consulta esté usando * ypueda devolver más columnas que las 3 listadas.

Es posible omitir la información de tipo para algunos o todos los escalares:

sess.createSQLQuery("SELECT * FROM CATS") .addScalar("ID", Hibernate.LONG) .addScalar("NAME") .addScalar("BIRTHDATE");

Ésta es esencialmente la misma consulta que antes, pero ahora se usa ResultSetMetaData par decidir el tipo de NAME YBIRTHDATE, 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 tipoen particular no está mapeado o no se resuelve en el tipo esperado, es posible modificarlo a medida, usando llamados aregisterHibernateType en el dialecto.

16.1.2. Consultas con entidades

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

151 de 198 17/02/2009 09:25 a.m.

Page 152: HIBERNATE - Persistencia

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 deaddEntity().

sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);

Esta consulta especificó:

la cadena SQL de la consulta

la entidad devuelta por la consulta

Suponiendo que Cat esté mapeado como clase, con las columnas ID, NAME y BIRTHDATE, las 2 consultas precedentesdevolverá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 devolvertambién eso al efectuar la consulta nativa, de otro modo ocurrirá un error específico de base de datos del tipo "columna noencontrada". Las columnas adicionales serán devueltas automáticamente cuando se use la notación *, pero se prefiere laforma explícita, como en el siguiente ejemplo en donde tenemos una relación 'de-muchos-a-uno' con un Dog:

sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);

Esto permitirá que cat.getDog() funcione correctamente.

16.1.3. Manipular colecciones y asociaciones

Es posible asociar Dog Dog en forma "ansiosa" a fin de evitar el posible viaje extra ida y vuelta a la base de datos parainicializar el proxy. Esto se hace usando el método addJoin(), el cual permite efectuar el vínculo con asociaciones ocolecciones.

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 adicionalde ida y vuelta a la BD. Note que hemos agregado un nombre de alias ("cat") para poder especificar la propiedad dedestino del join. Es posible hacer este mismo tipo de join "ávido" para colecciones, por ejemplo, si el Cat tuviera variosDogs 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 amejorar su SQL para que sean usables en Hibernate. El problema comienza cuando se devuelven múltiples entidades delmismo tipo, o cuando los alias/nombres de columna empleados por defecto no son suficientes.

16.1.4. Devolver múltiples entidades

En los ejemplos precedentes, se asume que los nombres de columna del resultado son los mismos que los nombres decolumna 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:

sess.createSQLQuery("SELECT c.*, m.* FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID") .addEntity("cat", Cat.class) .addEntity("mother", Cat.class);

Lo que se intenta es que esta consulta devuelva dos instancias de Cat por fila: un gato y su madre. Esto fallará, dado quehay un conflicto de nombres, porque están mapeados a los mismos nombres de columna, y, en algunas bases de datos, losalias de columna devueltos muy probablemente tendrán la forma "c.ID", "c.NAME", etc., lo cual no coincide con las

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

152 de 198 17/02/2009 09:25 a.m.

Page 153: HIBERNATE - Persistencia

columnas especificadas en el mapeo ("ID" y "NAME").

La forma siguiente no es vulnerable a esta duplicación del nombre de columna:

sess.createSQLQuery("SELECT {cat.*}, {mother.*} FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID") .addEntity("cat", Cat.class) .addEntity("mother", Cat.class);

Esta consulta especificó:

la cadena de la consulta SQL, con parámetros de sustitución (placeholders) para que hibernate inyecte los alias decolumna

las entidades devueltas por la consulta

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 columnaSQL para cada propiedad. El parámetro de sustitución para un alias de columna es simplemente el nombre de la propiedadcalificado por el alias de la tabla. En el ejemplo siguiente, obtenemos los Cats (gatos) y sus madres desde una tabladiferente (cat_log) a la declarada en los metadatos de mapeo. Note que incluso se puede usar los alias de propiedad en lacláusula "where" si se quiere.

String sql = "SELECT ID as {c.id}, NAME as {c.name}, " + "BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " + "FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID";

List loggedCats = sess.createSQLQuery(sql) .addEntity("cat", Cat.class) .addEntity("mother", Cat.class).list();

16.1.4.1. Alias y referencias a propiedades

Par la mayoría de los casos, inyección de alias es todo lo que se necesita. Pero para consultas relacionadas con mapeosmás complejos, como discriminadores de herencia o propiedades compuestas, hay algunos alias específicos que Hibernatedebe 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 resultadoson ejemplos, cada alias tendrá un nombre único y probablemente diferente cuando se use.

Tabla 16.1. Nombres de inyección de alias

Descripción Sintaxis EjemploUna propiedad simple {[aliasname].[propertyname] A_NAME as {item.name}

Una propiedad compuesta {[aliasname].[componentname].[propertyname]}

CURRENCY as {item.amount.currency},VALUE as {item.amount.value}

El discriminador de unaentidad {[aliasname].class} DISC as {item.class}

Todas las propiedades deuna entidad {[aliasname].*} {item.*}

Una clave de colección {[aliasname].key} ORGID as {coll.key}

El id de una colección {[aliasname].id} EMPID as {coll.id}

El elemento de unacolección {[aliasname].element} XID as {coll.element}

Una propiedad de elementoen la colección

{[aliasname].element.[propertyname]} NAME as {coll.element.name}

Todas las propiedades delelemento en la colección {[aliasname].element.*} {coll.element.*}

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

153 de 198 17/02/2009 09:25 a.m.

Page 154: HIBERNATE - Persistencia

Descripción Sintaxis EjemploTodas las propiedades de lacolección {[aliasname].*} {coll.*}

16.1.5. Devolver entidades no administradas

Es posible aplicarles un ResultTransformer a las consultas SQL nativas, permitiendo, por ejemplo, devolver entidadesno-manejadas.

sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS") .setResultTransformer(Transformers.aliasToBean(CatDTO.class))

La consulta especificó:

la cadena SQL de la consulta

un transformador de resultado (result transformer)

La consulta precedente devolverá una lista de CatDTO, la cual habrá sido instanciada y habrá inyectado los valores deNAME y BIRTHNAME en las propiedades o campos correspondientes.

16.1.6. Manejar herencia

Las consultas de SQL nativas que consulten entidades que estén mapeadas como parte de una herencia, deben incluirtodas 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:

Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class);List pusList = query.setString(0, "Pus%").list();

query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class);List pusList = query.setString("name", "Pus%").list();

16.2. Consultas SQL nombradas

Las consultas SQL nombradas (named SQL queries) pueden definirse en el documento de mapeo y ser invocadasexactamente en la misma forma que una consulta HQL nombrada. En este caso, no necesitamos invocar addEntity().

<sql-query name="persons"> <return alias="person" class="eg.Person"/>

SELECT person.NAME AS {person.name}, person.AGE AS {person.age}, person.SEX AS {person.sex} FROM PERSON person WHERE person.NAME LIKE :namePattern</sql-query>

List people = sess.getNamedQuery("persons") .setString("namePattern", namePattern) .setMaxResults(50) .list();

Los elementos <return-join> y <load-collection> se usan, respectivamente, para asociaciones de tipo "join", y paradefinir consultas que inicialicen colecciones.

<sql-query name="personsWith"> <return alias="person" class="eg.Person"/> <return-join alias="address" property="person.mailingAddress"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

154 de 198 17/02/2009 09:25 a.m.

Page 155: HIBERNATE - Persistencia

SELECT person.NAME AS {person.name}, person.AGE AS {person.age}, person.SEX AS {person.sex}, address.STREET AS {address.street}, address.CITY AS {address.city}, address.STATE AS {address.state}, address.ZIP AS {address.zip} FROM PERSON person JOIN ADDRESS address ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' WHERE person.NAME LIKE :namePattern</sql-query>

Una consulta SQL nombrada puede devolver un valor escalar. Hya que declarar el alias de la columna y el tipo deHibernate usando el elemento <return-scalar> element:

<sql-query name="mySqlQuery"> <return-scalar column="name" type="string"/> <return-scalar column="age" type="long"/>

SELECT p.NAME AS name, p.AGE AS age, FROM PERSON p WHERE p.NAME LIKE 'Hiber%'</sql-query>

Se puede externalizar el resultado, mapeando información en un elemento <resultset> para reutilizarlo, ya sea en otrasconsultas 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>

<sql-query name="personsWith" resultset-ref="personAddress"> SELECT person.NAME AS {person.name}, person.AGE AS {person.age}, person.SEX AS {person.sex}, address.STREET AS {address.street}, address.CITY AS {address.city}, address.STATE AS {address.state}, address.ZIP AS {address.zip} FROM PERSON person JOIN ADDRESS address ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' WHERE person.NAME LIKE :namePattern</sql-query>

Alternativamente, se puede usar la información de mapeo del resultset contenida en los archivos hbm, directamente en elcódigo Java.

List cats = sess.createSQLQuery( "select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id" ) .setResultSetMapping("catAndKitten") .list();

16.2.1. Usar return-property para especificar nombres de columna/alias explícitamente

Con <return-property> se le puede deicr a Hibernate explícitamente qué alias de columna usar, en lugar de usar lasintaxis {} para dejar que Hibernate inyecte sus propios alias.

<sql-query name="mySqlQuery">

<return alias="person" class="eg.Person"> <return-property name="name" column="myName"/> <return-property name="age" column="myAge"/> <return-property name="sex" column="mySex"/> </return>

SELECT person.NAME AS myName,

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

155 de 198 17/02/2009 09:25 a.m.

Page 156: HIBERNATE - Persistencia

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 nopermite 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>

SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer}, STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate}, REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY FROM EMPLOYMENT WHERE EMPLOYER = :id AND ENDDATE IS NULL ORDER BY STARTDATE ASC

</sql-query>

Note que en este ejemplo utilizamos <return-property> en combinación con la sintaxis {} para inyectar, permitiéndoleal 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.

16.2.2. Usar procedimientos almacenados para efectuar consultas

Hibernate 3 introduce soporte para consultas vía procedimientos almacenados (stored procedures) y funciones. Lamayoría de la documentación que sigue es equivalente para ambos. El procedimiento/función debe devolver un conjuntoresultado o "resultset" como primer parámetro de salida, a fin de poder trabajar con Hibernate. A continuacíón, un ejemplode función en Oracle 9 o superior:

CREATE OR REPLACE FUNCTION selectAllEmployments RETURN SYS_REFCURSORAS st_cursor SYS_REFCURSOR;BEGIN OPEN st_cursor FOR SELECT EMPLOYEE, EMPLOYER, STARTDATE, ENDDATE, REGIONCODE, EID, VALUE, CURRENCY FROM EMPLOYMENT; RETURN st_cursor; END;

Para usar esta consulta en Hibernate, hay que mapearla con una consulta "nombrada".

<sql-query name="selectAllEmployees_SP" callable="true"> <return alias="emp" class="Employment"> <return-property name="employee" column="EMPLOYEE"/>

<return-property name="employer" column="EMPLOYER"/> <return-property name="startDate" column="STARTDATE"/> <return-property name="endDate" column="ENDDATE"/> <return-property name="regionCode" column="REGIONCODE"/> <return-property name="id" column="EID"/> <return-property name="salary">

<return-column name="VALUE"/> <return-column name="CURRENCY"/> </return-property> </return> { ? = call selectAllEmployments() }</sql-query>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

156 de 198 17/02/2009 09:25 a.m.

Page 157: HIBERNATE - Persistencia

Note que los procedimientos almacenados actualmente sólo devuelven escalares o entidades. <return-join> y <load-collection> no se soportan.

16.2.2.1. Reglas y limitaciones en el uso de procedimientos almacenados

Para usar procedimientos almacenados con Hibernate, el procedimiento o función debe seguir ciertas reglas. Si no sigueesas reglas, es incompatible con Hibernate. Si aún se desea usar estos procedimientos incompatibles, hay que ejecutarlosvia session.connection(). Las reglas son diferentes para cada base de datos, dado que cada proveedor de base de datostiene una semántica y una sintaxis distinta pars los procedimientos almacenados.

Los procedimientos almacenados no pueden ser paginados con setFirstResult()/setMaxResults().

La invocación recomendada es el estádar de SQL92: { ? = call functionName(<parameters>) } or { ? = callprocedureName(<parameters>}. Los llamdos nativos no se soportan.

Para Oracle, se aplican las siguientes reglas:

Las funciones deben devolver un resultado "resultset". El primer parámetro de los procedimientos almacenadosdebe ser un parámeto de tipo "OUT" que devuelva un resultset. Esto se logra usando un tipo SYS_REFCURSOR enOracle 9 o 10. En Oracle hay que definir un tipo REF CURSOR, consulte la literatura de Oracle.

Para Sybase o MS SQL server se aplican las siguientes reglas:

El procedimiento debe devolver un resultset. Note que, como estos servidores son capaces de devolver múltiplesresultsets y contadores de actualización, Hibernate recorrerá los resultados y tomará el primer resultado que searesultset. El resto se descarta.

Si usted puede habilitar SET NOCOUNT ON en su procedimiento, seguramente éste será más eficiente, pero esto no esobligatorio.

16.3. SQL a medida para crear, modificar o borrar

Hibernate3 puede usar comandos SQL a medida para operaciones de creación, actualización y borrado. Los "persistores"de clases y colecciones en Hibernate ya contienen un conjunto de cadenas (insertsql, deletesql, updatesql etc.) generadasen tiempo de configuración. Las tags de mapeo <sql-insert>, <sql-delete>, y <sql-update> suplantan easas cadenas

<class name="Person">

<id name="id"> <generator class="increment"/> </id> <property name="name" not-null="true"/>

<sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert> <sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update> <sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>

</class>

El SQL es ejecutado directamente en su base de datos, así que usted es libre de utilizar cualquier dialecto que desee. Porsupuesto, usar SQL específico de una base de datos reducirá la "portabilidad" de su aplicación.

Los procedimientos almacenados se soportan, si se configura el atributo callable:

<class name="Person"> <id name="id"> <generator class="increment"/> </id>

<property name="name" not-null="true"/> <sql-insert callable="true">{call createPerson (?, ?)}</sql-insert> <sql-delete callable="true">{? = call deletePerson (?)}</sql-delete>

<sql-update callable="true">{? = call updatePerson (?, ?)}</sql-update></class>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

157 de 198 17/02/2009 09:25 a.m.

Page 158: HIBERNATE - Persistencia

El orden de los parámetros posicionales es ahora esencial, dado que deben estar en la misma secuencia en que Hibernatelos espera.

Se puede ver el orden esperado, habilitando logueo a nivel "debug" en org.hibernate.persister.entity. Con estacategorí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 almacenadosdevuelvan el número de filas insertadas/actualizadas/borradas, porque Hibernate realiza algunos chequeos en tiempo deejecución para verificar el éxito del comando. Hibernate siempre registra el primer parámetro del "statement" como desalida y numérico, para las operaciones de ABM (alta-baja-modificación):

CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2) RETURN NUMBER ISBEGIN

update PERSON set NAME = uname, where ID = uid;

return SQL%ROWCOUNT;

END updatePerson;

16.4. SQL a medida para cargar

Se puede también declarar comandos SQL (o HQL) a medida para cargar entidades:

<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. Sepuede hacer referencia a esta consulta nombrada en el mapeo de clases:

<class name="Person"> <id name="id"> <generator class="increment"/> </id>

<property name="name" not-null="true"/> <loader query-ref="person"/></class>

Esto funciona incluso con procedimientos almacenados.

Se puede incluso definir un a consulta para cargar la colección:

<set name="employments" inverse="true"> <key/>

<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>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

158 de 198 17/02/2009 09:25 a.m.

Page 159: HIBERNATE - Persistencia

Hasta se puede definir un "cargador de entidades" que cargue una coleccíón mediante captura con "join":

<sql-query name="person">

<return alias="pers" class="Person"/> <return-join alias="emp" property="pers.employments"/>

SELECT NAME AS {pers.*}, {emp.*} FROM PERSON pers LEFT OUTER JOIN EMPLOYMENT emp ON pers.ID = emp.PERSON_ID WHERE ID=?</sql-query>

Capítulo 17. Filtrar datos

Hibernate3 provee un enfoque innovador para manejar datos que tengan "reglas de visibilidad". Un filtro de Hibernate esun filtro global, nombrado y parametrizado, que puede estar habilitado o inhabilitado para una sesión en particular.

17.1. Filtros de Hibernate

Hibernate3 incorpora la capacidad de predefinir criterios de filtrado, y adjuntar esos filtros tanto a nivel de la clase como anivel de la colección. Un cirterio de filtrado es la capacidad de definir una cláusula de restricción, muy similar al atributo"where" disponible en la clase y varios elementos de colecciones. Excepto que estas condiciones de filtrado pueden serparametrizadas. La aplicación puede, entoces, tomar decisiones en tiempo de ejecución acerca de si un filtro debería estarhabilitado, y cuáles deberían ser sus parámetros. Los filtros pueden ser usados como vistas (views) de la base de datos,pero parametrizados dentro de la aplicación.

Para usar filtros, primero tienen que ser definidos y adjuntados al elemento de mapeo correspondiente. Para definir unfiltro, use el elemento <filter-def/> dentro de un elemento de <hibernate-mapping/>:

<filter-def name="myFilter"> <filter-param name="myFilterParam" type="string"/></filter-def>

Luego, este filtro puede ser adosado a una clase:

<class name="myClass" ...> ... <filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/></class>

o a una colección:

<set ...> <filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/></set>

o incluso a ambas, o a varias de ambas, al mismo tiempo.

Los métodos en Session son: enableFilter(String filterName), getEnabledFilter(String filterName), ydisableFilter(String filterName). Por defecto, los filtros no están habilitados para una sesión dada; deben serhabilitados explícitamente mediante el uso del método Session.enableFilter(), el cual devuelve una instancia de lainterfaz Filter. Usando el filtro simple definido antes, esto se vería así:

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 enHibernate.

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"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

159 de 198 17/02/2009 09:25 a.m.

Page 160: HIBERNATE - Persistencia

</filter-def>

<class name="Employee" ...>

... <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 name="Department" ...>... <set name="employees" lazy="true"> <key column="dept_id"/> <one-to-many class="Employee"/> <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/> </set>

</class>

Luego, para asegurarse de que siempre se obtiene el registro más actual, simplemente habilite el filtro en la sesión, antes decapturar datos de empleados:

Session session = ...;session.enableFilter("effectiveDate").setParameter("asOfDate", new Date());List results = session.createQuery("from Employee as e where e.salary > :targetSalary") .setLong("targetSalary", new Long(1000000)) .list();

En el HQL precedente, incluso si sólo se mencionó una condición relativa al salario en los resultados, la consultadevolverá los empleados que con un salario mayor a un millón y, a causa del filtro habilitado, que estén actualmenteactivos.

Nota: si se planea usar filtros con "outer joins" (sea a través de HQL o de estrategias de "fetch") tenga cuidado con ladirección de la expresión de condición. Lo más seguro es configurarlo como "left outer join"; en general, ponga elpará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 definiruna condición por defecto, sea como un atributo, o como CDATA:

<filter-def name="myFilter" condition="abc > xyz">...</filter-def><filter-def name="myOtherFilter">abc=xyz</filter-def>

Entonces, esta condición por defecto será usada siempre que el filtro se adjunte a algo sin haber especificado unacondición. Note que esto significa que se puede dar una condición específica como parte de la adjunción del filtro, quesuplanta a la condición por defecto para ese caso en particular.

Capítulo 18. Mapeo XML

Note que ésta es una característica experimental en Hibernate 3.0, y se encuentra actualmente en muy activodesarrollo.

18.1. Trabajar con datos XML

Hibernate le permite trabajar con datos persistentes en XML de la misma forma en que lo haría con POJOs. Un árbol XMLválido puede ser concebido como sencillamente otra forma de representar los datos relacionales a nivel de objetos, enlugar de los POJOs.

Hibernate soporta la API dom4j para manipular los árboles XML. Se puede escribir consultas que obtengan árboles dom4j

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

160 de 198 17/02/2009 09:25 a.m.

Page 161: HIBERNATE - Persistencia

desde la base de datos, y lograr que cualquier modificación practicada al XML se sincronice automáticamente a la base dedatos. Incluso se puede tomar un documento XML, analizarlo usando dom4j, y escribirlo en la base de datos concualquiera 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 oSOAP, 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 documentoXML a la base de datos, o, si no hay ninguna clase para mapear, sólo el documento XML.

18.1.1. Especificar el mapeo XML y el mapeo de la clase al mismo tiempo

He aquí un ejemplo de mapeo de un POJO y XML simultáneamente:

<class name="Account" table="ACCOUNTS" node="account">

<id name="accountId" column="ACCOUNT_ID" node="@id"/>

<many-to-one name="customer" column="CUSTOMER_ID" node="customer/@id" embed-xml="false"/>

<property name="balance" column="BALANCE" node="balance"/> ...</class>

18.1.2. Especificar sólo un mapeo XML

He aquí un ejemplo en el cual no hay clase POJO:

<class entity-name="Account" table="ACCOUNTS" node="account">

<id name="id" column="ACCOUNT_ID" node="@id" type="string"/>

<many-to-one name="customerId" column="CUSTOMER_ID" node="customer/@id" embed-xml="false" entity-name="Customer"/>

<property name="balance" column="BALANCE" node="balance" type="big_decimal"/>

...

</class>

Este mapeo permite acceder a los datos como si fueran un árbol dom4j, o una representación en forma de paresnombre/valor (Maps de Java). Los nombres de las propiedades son construcciones puramente lógicas, a las que se puedehacer referencia en consultas HQL.

18.2. Metadatos de mapeo XML

Muchos elementos de mapeo de Hibernate aceptan el atributo node (nodo). Esto permite especificar el nombre de unatributo o elemento XML que contenga la propiedad o dato de entidad. El formato del atributo node debe ser uno de lossiguientes:

"element-name": mapea al elemento XML especificado

"@attribute-name": mapea al atributo XML especificado.

".": mapea al elemento padre.

"element-name/@attribute-name": mapea al elemento especificado del atributo especificado.

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

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

161 de 198 17/02/2009 09:25 a.m.

Page 162: HIBERNATE - Persistencia

de "value type"s) y para las colecciones no aparecerá nada.

Debería tenerse cuidado de no asignarles embed-xml="true" a demasiadas asociaciones, ¡dado que XML no sabe lidiarmuy bien con recursividad!

<class name="Customer" table="CUSTOMER" node="customer">

<id name="id" column="CUST_ID" node="@id"/>

<map name="accounts" node="." embed-xml="true">

<key column="CUSTOMER_ID" not-null="true"/>

<map-key column="SHORT_DESC" node="@short-desc" type="string"/> <one-to-many entity-name="Account" embed-xml="false" node="account"/>

</map>

<component name="name" node="name"> <property name="firstName" node="first-name"/> <property name="initial" node="initial"/> <property name="lastName" node="last-name"/> </component>

...

</class>

en este caso, hemos decidido incrustar una colección de ids de cuentas (accounts), pero no los datos de las cuentaspropiamente dichas. La siguiente consulta HQL:

from Customer c left join fetch c.accounts where c.lastName like :lastName

Devolvería datos como éstos:

<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>

Si se asigna embed-xml="true" en el mapeo <one-to-many>, los datos se verían así:

<customer id="123456789">

<account id="987632567" short-desc="Savings"> <customer id="123456789"/> <balance>100.29</balance> </account>

<account id="985612323" short-desc="Credit Card"> <customer id="123456789"/> <balance>-2370.34</balance> </account>

<name> <first-name>Gavin</first-name> <initial>A</initial> <last-name>King</last-name> </name> ...</customer>

18.3. Manipular datos XML

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

162 de 198 17/02/2009 09:25 a.m.

Page 163: HIBERNATE - Persistencia

Vamos a leer y actualizar documentos XML en la aplicación. Lo hacemos obteniendo una sesión dom4j:

Document doc = ....;

Session session = factory.openSession();Session dom4jSession = session.getSession(EntityMode.DOM4J);Transaction tx = session.beginTransaction();

List results = dom4jSession .createQuery("from Customer c left join fetch c.accounts where c.lastName like :lastName") .list();for ( int i=0; i<results.size(); i++ ) { //le agrega los datos del cliente (customer) al documento XML Element customer = (Element) results.get(i); doc.add(customer);}

tx.commit();session.close();

Session session = factory.openSession();Session dom4jSession = session.getSession(EntityMode.DOM4J);Transaction tx = session.beginTransaction();

Element cust = (Element) dom4jSession.get("Customer", customerId);for ( int i=0; i<results.size(); i++ ) { Element customer = (Element) results.get(i); //cambia el nombre del cliente en el XML y en la BD Element name = customer.element("name"); name.element("first-name").setText(firstName); name.element("initial").setText(initial); name.element("last-name").setText(lastName);}

tx.commit();session.close();

Combinar esta funcionalidad con la operación replicate() es extremadamente útil para implementar laimportación/exportación de datos basados en XML:

Capítulo 19. Mejorar la performance

19.1. Estrategias de captura (fetch)

Una estrategia de captura es la estrategia que Hibernate usará para obtener los objetos asociados, cuando Hibernatenecesite navegar una asociación. Las estrategias de captura pueden ser declaradase en los metadatos de mapeo, o bien sersustituidas para una consulta HQL o Criteria en particular.

Hibernate3 define las siguientes estategias de captura:

captura por Join Hibernate obtiene la instancia o colección asociada, agregándole un OUTER JOIN al SELECTmismo.

captura por Select: se usa un segundo SELECT para obtener la entidad o colección asociada. A menos que seinhabilite 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 sehabían obtenido en el SELECT anterior. A menos que se inhabilite la captura haragana (lazy fetching) especificandolazy="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 nouna, sino un lote (batch) de instancias de entidad o colección en un solo SELECT, especificando no una, sino unalista de claves primarias o foráneas.

Hibernate también distingue entre:

captura inmediata: la asociación. colección o atributo son capturados inmediatamente, cuando el dueño es cargado.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

163 de 198 17/02/2009 09:25 a.m.

Page 164: HIBERNATE - Persistencia

captura haragana de colecciones: una colección es capturada sólo cuando la aplicacíón invoca alguna operación enesa colección (éste es el comportamiento por defecto para las colecciones).

captura "súper-haragana" de colecciones (extra-lazy collection fetching): se accede a elementos individuales de lacolección desde la base de datos, sólo en la medida en que se necesita. Hibernate no intenta capturar toda lacolección en memoria, a menos que sea absolutamente necesario (esto es lo adecuado para colecciones muyextensas).

captura por Proxy: una asociación de un solo valor se captura cuando se invoca sobre éste cualquier otro métodoque 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 . Encomparación con la captura por proxy, este abordaje es menos haragán (se accede a la asociación incluso si sólo seusa el identificador), pero más transparente, dado que no hay ningún proxy visible para la aplicacíón. Este abordajerequiere 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 lainstancia o asociación. Este abordaje requiere instrumentación bytecode de tiempo de compilación, y sólo raramentees necesario.

Aquí tenemos dos nociones ortogonales: cuándo la asociación se captura, y cómo se captura (qué SQL se usa). ¡A noconfundir una con la otra! Se usa la palabra fetch para ajustar la eprformance. Se puede usar la palabra lazy para definirun contrato sobre qué estará disponible y qué estará desprendido para una clase en particular.

19.1.1. Trabajar con asociaciones haraganas

Hibernate3 usa por defecto "select haragán" para las colecciones, y "captura haragana por proxy" para las asociaciones devalor simple. Estos valores por defecto tienen sentido casi siempre, y para casi todas las asociaciones.

Nota: si se inicializa la variable de configuración hibernate.default_batch_fetch_size, Hibernate usará laoptimización de captura por lotes para las capturas haraganas. Esta optimización puede también ser habilitada a un nivelmás granular.

De todos modos, la captura haragana plantea un problema del cual hay que ser consciente: acceder a asociacionesharaganas 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();

User u = (User) s.createQuery("from User u where u.name=:userName") .setString("userName", userName).uniqueResult();Map permissions = u.getPermissions();

tx.commit();s.close();

Integer accessLevel = (Integer) permissions.get("accounts"); // ¡Error!

Como la colección "permissions" no estaba inicializada cuando la sesión fue cerrada, la colección será incapaz de cargarsu estado. Hibernate no soporta la inicialización haragana de objetos desprendidos. La solución es mover el código quelee 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 dela asociación. De todos modos, se espera que se use inicialización haragana en casi todos los casos. Si se definendemasiadas asociaciones no haraganas, ¡Hibernate terminará cargando toda la base de datos en memoria para cadatransacción!

Por otra parte, a menudo querremos elegir captura por join (la cual que es naturalmente no haragana) en lugar de capturapor select, para una transacción en particular. Ahora veremos cómo diseñar una estrategia de captura a medida. EnHibernate3, los mecanismos para elegir una estrategia de captura son idénticos para las asociaciones de valor simple y paralas colecciones.

19.1.2. Ajustar las estrategias de captura

La captura por select (el comportamiento por defecto) es extremadamente vulnerable a los problemas de N+1 SELECTS.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

164 de 198 17/02/2009 09:25 a.m.

Page 165: HIBERNATE - Persistencia

Así que tal vez querramos habilitar la captura por join en el documento de mapeo.

<set name="permissions" fetch="join"> <key column="userId"/> <one-to-many class="Permission"/></set

<many-to-one name="mother" class="Cat" fetch="join"/>

La estrategia de captura (fetch) definida en el documentod e mapeo afecta a:

la obtención de datos via get() o load()

la obtención de datos que ocurre implícitamente cuando se navega la asociación

las consultas Criteria

las consultas HQL (si se usa la captura por subselect )

Sin importar qué estrategia de captura se use, se garantiza que el árbol "no haragán" definido se cargará en memoria. Noteque 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 elcomportamiento 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 laAPI 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 consultaCriteria, por ejemplo:

User user = (User) session.createCriteria(User.class) .setFetchMode("permissions", FetchMode.JOIN) .add( Restrictions.idEq(userId) ) .uniqueResult();

(É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.

19.1.3. Proxies de asociaciones de un solo extremo

La captura haragana para colecciones se implementa usando la implementación para colecciones persistentes de Hibernatemismo. Sin embargo, se necesita un mecanismo diferente para el comportamiento haragán en las asociaciones de extremosimple. A la entidad blanco de la asociación se le debe crear un "proxy". Hibernate implementa los proxies deinicialización haragana para objetos persistentes usando "mejora de bytecode en tiempo de ejecución" (a través de laexcelente biblioteca CGLIB).

Hibernate3 genera proxies por defecto (durante el arranque) para todas las clases persistentes, y los usa para habilitar lacaptura 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 lecrea 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 clasespolimórficas; por ejemplo:

<class name="Cat" proxy="Cat"> ...... <subclass name="DomesticCat"> ..... </subclass></class>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

165 de 198 17/02/2009 09:25 a.m.

Page 166: HIBERNATE - Persistencia

Primero que nada, las instancias de la clase Cat jamás podrán ser convertidas con "cast" a DomesticCat, incluso cuandola 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! ....}

En segundo lugar, es posible romper el operador ==.de proxies.

Cat cat = (Cat) session.load(Cat.class, id); // instancia un proxy de CatDomesticCat dc = (DomesticCat) session.load(DomesticCat.class, id); // adquiere un nuevo proxy parSystem.out.println(cat==dc); // falso

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:

cat.setWeight(11.0); // llama a la base de datos para inicializar el proxySystem.out.println( dc.getWeight() ); // 11.0

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 oconstructores por defecto), entonces dichos recursos serán también adquiridos por el proxy. La clase proxy es unaverdadera subclase de la clase persistente.

Estos problemas se deben todos a limitaciones fundamentales del modelo de herencia única de Java. Si usted quiere evitarestos problemas, cada una de sus clases persistentes debe implementar una interfaz que declare sus métodos de negocio. Yusted debe especificar estas interfaces en el documento de mapeo. Por ejemplo:

<class name="CatImpl" proxy="Cat"> ...

<subclass name="DomesticCatImpl" proxy="DomesticCat"> ... </subclass></class>

en donde CatImpl implementa la interfaz Cat y DomesticCatImpl implementa la interfaz DomesticCat. Así, los proxiespara instancias de Cat y DomesticCat pueden ser devueltos por load() o por iterate(). (Note que list()normalmente no devuelve proxies.)

Cat cat = (Cat) session.load(CatImpl.class, catid);Iterator iter = session.createQuery("from CatImpl as cat where cat.name='fritz'").iterate();Cat fritz = (Cat) iter.next();

Las relaciones también son incializadas en forma haragana. Esto significa que toda propiedad debe ser declarada como detipo Cat, no CatImpl.

Ciertas operaciones no requieren inicialización de proxies:

equals(), si la clase persistente no sustituye equals()

hashCode(), si la clase persistente no sustituye hashCode()

el getter del método identificador

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 asociadoscon 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.

19.1.4. Inicializar proxies y colecciones

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

166 de 198 17/02/2009 09:25 a.m.

Page 167: HIBERNATE - Persistencia

Si se trata de acceder a una colección o proxy no inicializados fuera del alcance de una sesión, Hibernate emitirá unaLazyInitializationException. Es decir, cuando la entidad que posea la colección o que tienga una referencia al proxyesté 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 estosería confuso para quien leyere el código, e inconveniente para cualquier código que intentare ser genérico.

Los métodos estáticos Hibernate.initialize() e Hibernate.isInitialized() le proveen a la aplicación una formaconveniente de trabajar con colecciones o proxies que hayan sido inicializados en forma haragana.Hibernate.initialize(cat) forzará la inicialización del proxy, cat, siempre y cuando su sesión esté aún abierta.Hibernate.initialize( cat.getKittens() ) tiene un efecto similar para la colección de kittens (gatitos).

Otra opción es mantener la sesión abierta hasta que todas las colecciones y proxies necesarios hayan sido cargados. En laarquitectura de algunas aplicaciones, particularmente en donde el código que accede a Hibernate y el código que lo usaestán en distinas capas, o en distintos procesos físicos, asegurarse de que una única sesión se mantenga abierta cuando unacolecció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 alfinal de la request, una vez que la presentación de la interfaz de usuario (view) ha sido completada (el patrón deprogramación Open Session in View ). Por supuesto, esto crea una pesada responsabilidad en cuando a la correccióndel manejo de excepciones por parte de la infraestructura de la aplicación. Es de vital importancia que la sesión seacerrada y la transacción concluida antes de retornar el control al usuario, incluso cuando ocurriere una excepciónmientras se presentare la interfaz de usuario. Vea la wiki de Hibernate para ejemplos de este patrón "Open Sessionin View".

En aplicaciones que tengan una capa de negocios separada, la lógica de negocio debe "preparar" todas lascolecciones que vayan a ser necesitadas por la capa de web, antes de retornar. Esto significa que la capa denegocios debería cargar todos los datos que hagan falta para un caso en particular, y devolvérselos a la capa deinterfaz de usuario/web. Normalmente, la aplicación llama a Hibernate.initialize() para cada colección quevaya a ser necesaria en la capa de web (este llamado ocurre antes de que la sesión se cierre), u obtiene la colecciónen 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 SessionFaçade.

También se puede adjuntar un objeto previamente cargado a una nueva sesión, con merge() o lock() antes deacceder 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 deella (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:

( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()

El método createFilter() se usa también para obtener eficientemente subconjuntos de una colección sin necesidad deinicializarla toda.

s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();

19.1.5. Usar la captura por lotes

Hibernate puede hace un uso eficiente de la captura por lotes, es decir que puede cargar varios "proxies" no inicializadoscuando se accede a uno. (O colecciones. La captura por lotes es una optimización del tipo de captura por "SELECTharagá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 deejecución: Hay 25 instancias de Cat cargadas en la sesión; cada Cat tiene una referencia a su dueño, una instancia dePerson. La clase Person está mapeada con un proxy, lazy="true". Si se itera a través de todos los gatos (cats) y seinvoca getOwner() en cada uno de ellos, Hibernate por defecto ejecutará 26 comandos SELECT, para obtener los proxiesde los dueños. Se puede ajustar este comportamiento especificando un batch-size (tamño de lote) en el mapeo dePerson:

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

167 de 198 17/02/2009 09:25 a.m.

Page 168: HIBERNATE - Persistencia

<class name="Person" batch-size="10">...</class>

Hibernate ahora ejecutará sólo 3 consultas: el patrón es 10, 10, 5.

También se puede habilitar la captura en lotes de colecciones. Por ejemplo, si cada Person tiene un colección haragana deCats, y en un momento hay 10 personas cargadas en la sesión, iterar a través de todas esas personas generaría 10SELECTs, uno por cada llamado a getCats(). Si se habilita la captura por lotes para las colecciones de cats en el mapeode Person, Hibernate pre-capturará las colecciones:

<class name="Person">

<set name="cats" batch-size="3"> ... </set></class>

Con un a batch-size igual a 3, Hibernate cargará 3, 3, 3, 1 colecciones en 4 SELECTs. De nuevo, el valor del atributodependerá 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 deMateriales" necesarios para construir una pieza (bill of Materials o BOM, por sus siglas en inglés). (Aunque para árbolesde "mayormente lectura" un set anidado o un "path materialziado" serían mejores opciones) .

19.1.6. Usar la captura por subselect

Si una colección o proxy de valor simple haraganes tienen que ser capturados, Hibernate los carga todos, volviendo aejecutar la consulta original en un subselect. Esto funciona de la misma manera que la captura por lotes, pero sin la carga"parte por parte".

19.1.7. Usar la captura de propiedades haragana

Hibernate3 soporta la captura haragana de propiedades individuales. Esta técnica de optimización también se conocecomo grupos de captura (en inglés "fetch groups"). Por favor, dese cuenta de que esto es un extra más publicitario quefuncional, dado que, en la práctica, optmizar las lecturas de filas es mucho más importante que optimizar las lecturas decolumna. De todos modos, leer sólo algunas propiedades de una clase puede ser útil en algunos casos extremos, en dondetablas 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 clasespersistentes han sido mejoradas, Hibernate ignorará silenciosamente la configuración de propiedades a "lazy", y seresignará a usar la captura inmediata usual.

Para la instrumentación bytecode, use la siguiente tarea de Ant:

<target name="instrument" depends="compile"> <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask"> <classpath path="${jar.path}"/> <classpath path="${classes.dir}"/> <classpath refid="lib.class.path"/> </taskdef>

<instrument verbose="true"> <fileset dir="${testclasses.dir}/org/hibernate/auction/model"> <include name="*.class"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

168 de 198 17/02/2009 09:25 a.m.

Page 169: HIBERNATE - Persistencia

</fileset> </instrument></target>

Una manera diferente (y tal vez mejor) de evitar lecturas de columa innecesarias, al menos en transacciones desólo-lectura, es usar las capacidades de proyección (Projections) que tienen las consultas HQL y Criteria. Esto evita lanecesidad 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.

19.2. El caché de 2do nivel

Una sesión (Session) de Hibernate, es un caché de datos persistentes a nivel de la transacción. Es posible configurar uncaché a nivel de cluster, o a nivel de la JVM (a nivel de la SessionFactory o fábrica de sesiones), que se aplique a una omás clases en particular, y/o a una o más colecciones en particular. Se puede incluso "enchufar" un caché en cluster. Hayque tener cuidado, porque los cachés nunca están al tanto de los cambios que le hayan sido hechos al repositorio de datospersistente por otra aplicación (aunque sí pueden ser configurados para que sus datos cacheados expiren regularmenteluego de un cierto tiempo).

Existe la opción de decirle a Hibernate qué implementación de cacheo usar, especificando el nombre de una clase queimplemente 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 acontinuación); además, se puede implementar un caché propio y enchufarlo como se indicó anteriormente. Nota: lasversiones hasta 3.2 exclusive usaban EhCache como su proveedor por defecto. Éste ya no es el caso a partir de 3.2.

Table 19.1. Prveedores de Caché

Caché Clase del proveedor TipoSeguro parausar enclusters

Soporta elcaché deconsultas(Query)

Hashtable(no se esperaque seausado enproducción)

org.hibernate.cache.HashtableCacheProvider memoria sí

EHCache org.hibernate.cache.EhCacheProvider memoria, disco sí

OSCache org.hibernate.cache.OSCacheProvider memoria, disco sí

SwarmCache org.hibernate.cache.SwarmCacheProviderencluster(multicastde ip)

sí(invalidaciónde cluster)

JBoss Cache1.x org.hibernate.cache.TreeCacheProvider

en cluster(multicast deip),transaccional

sí(replicación)

sí (requieresincronizaciónde relojes)

JBoss Cache2 org.hibernate.cache.jbc2.JBossCacheRegionFactory

en cluster(multicast deip),transaccional

sí(replicaciónoinvalidación)

sí (requieresincronizaciónde relojes)

19.2.1. Mapeos de caché

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)

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

169 de 198 17/02/2009 09:25 a.m.

Page 170: HIBERNATE - Persistencia

/>

(1)usage (obligatorio) especifica la estrategia de cacheo transactional, read-write, nonstrict-read-write orread-only

(2)region (optativo, por defecto, el nombre de rol de la clase o colección): especifica el nombre de la región delcaché de 2do nivel.

(3)include (optativo, por defecto, all) non-lazy especifica que las propiedades de la entidad mapeadas conlazy="true" no deben ser mapeadas cuando esté habilitada la captura haragana a nivel de cada atributo.

Alternativamente (y tal vez preferiblemente) se puede especificar elementos <class-cache> y <collection-cache> enel archivo hibernate.cfg.xml.

EL atributo usage especifica una estrategia de concurrencia de caché.

19.2.2. Estrategia de sólo-lectura

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 uncluster.

<class name="eg.Immutable" mutable="false">

<cache usage="read-only"/> ....</class>

19.2.3. Estrategia de lecto-escritura

Si la aplicación necesita actualizar datos, puede ser apropiado usar una caché de lecto-escritura (read-write). Estaestrategia de cacheo nunca debería ser usada si se requiere aislamiento de transacciones serializables. Si el caché se usa enun entorno JTA, se debe especificar la propiedad hibernate.transaction.manager_lookup_class, especificando unaestrategia para obtener la propiedad TransactionManager de JTA. En otros entornos, hay que asegurarse de que latransacción esté completa para cuando ocurran Session.close() o Session.disconnect(). Si se quiere usar estaestrategia en un cluster, hay que asegurarse de que la implementación subyacente de caché soporta "locking". Losproveedores de caché que vienen ya incorporados no lo soportan.

<class name="eg.Cat" .... >

<cache usage="read-write"/> .... <set name="kittens" ... > <cache usage="read-write"/> .... </set></class>

19.2.4. Estrategia de lecto-escritura no estricta

Si la aplicación necesita actualizar datos, pero sólo ocasionalmente (es decir, si es improbable que dos transacciones tratende actualizar el mismo ítem simultáneamente), y no se requiere un aislamiento de transacciones estricto, puede serapropiado un caché "de lecto-escritura no estricta" (nonstrict-read-write). Si el caché se usa en un entorno JTA, sedebe especificar hibernate.transaction.manager_lookup_class. En otros entornos, hay que asegurarse de que latransacción esté completa para cuando ocurran Session.close() o Session.disconnect().

19.2.5. Estrategia transaccional

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

170 de 198 17/02/2009 09:25 a.m.

Page 171: HIBERNATE - Persistencia

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 especificarhibernate.transaction.manager_lookup_class.

19.2.6. Compatibilidad entre el proveedor de caché y la estrategia de concurrencia

Importante

Ninguno de los proveedores de caché soporta todas las estrategias de concurrencia.

La siguiente table muestra qué proveedores son compatibles con qué estrategias de concurrencia.

Tabla 19.2. Soporte de estrategias de concurrencia de caché

Caché sólo-lectura lecto-escritura noestricta

lecto-escritura transaccional

Hashtable (no concebida para uso enproducción) sí sí sí

EHCache sí sí sí

OSCache sí sí sí

SwarmCache sí sí

JBoss Cache 1.x sí sí

JBoss Cache 2 sí sí

19.3. Admninistrar los cachés

Siempre que un objeto se le pase a save(), update() o saveOrUpdate(), y cada vez que se obtenga un objeto usandoload(), get(), list(), iterate() o scroll(), dicho objeto se agrega al caché interno de la sesión.

Cuando a continuación se invoca flush(), el estado de dicho objeto será sincronizado con la base de datos. Si no sequiere que esta sincronización ocurra, o si se está procesando una cantidad inmensa de objetos que haga necesario manejarla memoria eficientemente, se puede usar el método evict() para quitar objetos y sus colecciones del caché de primernivel.

ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //un resultset inmensowhile ( cats.next() ) { Cat cat = (Cat) cats.get(0); doSomethingWithACat(cat); sess.evict(cat);}

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.

sessionFactory.evict(Cat.class, catId); //desaloja una instancia de Cat en particularsessionFactory.evict(Cat.class); //desaloja todos los CatssessionFactory.evictCollection("Cat.kittens", catId); //desaloja un colección de kittens en particusessionFactory.evictCollection("Cat.kittens"); //desaloja todas lsa colecciones de kittens

El argumento CacheMode controla cómo una sesión en particular interactúa con el caché de 2do nivel.

CacheMode.NORMAL: lee y escribe items en el caché de 2do nivel

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

171 de 198 17/02/2009 09:25 a.m.

Page 172: HIBERNATE - Persistencia

CacheMode.GET: lee items del caché de 2do nivel, pero no escribe en el caché de 2do nivel excepto cuando seactualicen datos.

CacheMode.PUT:escribe items en el caché de 2do nivel, pero no lee.

CacheMode.REFRESH: escribe items en el caché de 2do nivel, pero lee del caché de 2do nivel, elude los efectos dehibernate.cache.use_minimal_puts, forzando un refresco del caché de 2do nivel para todos los items leídos dela 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:

Map cacheEntries = sessionFactory.getStatistics() .getSecondLevelCacheStatistics(regionName) .getEntries();

Necesitará habilitar estadísticas, y, optativamente, forzar a Hibernate a que escriba las entradas de caché en un formatomás legible por seres humanos:

hibernate.generate_statistics truehibernate.cache.use_structured_entries true

19.4. El caché de consultas (query cache)

Los resultados de las consultas también pueden ser cacheados. Esto sólo esútil para consultas que son ejecutadasfrecuentemente, y con los mismos parámetros. Para usar el caché de consutas, primero hay que habilitarlo:

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 delas consultas (org.hibernate.cache.StandardQueryCache), y la otra conteniendo la fecha y hora de las actualizacionesmá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 losidentificadores, y los resultados de tipo "value type". Así que el caché de consultas siempre debería ser usado enconjunció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 pordefecto. Par habilitar el cacheo, invoque Query.setCacheable(true). Este llamado permite que la consulta, al serejecutada, 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 decaché para una consulta en particular, invocando Query.setCacheRegion().

List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger") .setEntity("blogger", blogger) .setMaxResults(15) .setCacheable(true) .setCacheRegion("frontpages") .list();

Si la consulta forzare un refresco de esta región de caché en particular, habría que invocarQuery.setCacheMode(CacheMode.REFRESH). Esto es particularmente útil para los casos en donde los datos subyacentespudieren haber sido actualizados por un proceso separado (por ejemplo, no por Hibernate), y le permite a la aplicaciónrefrescar selectivamente un resultado en particular. Esto es más eficiente que el desalojo de toda una región de cachés viaSessionFactory.evictQueries().

19.5. Comprender la performance de las colecciones

Ya hemos invertido bastante tiempo en hablar sobre las colecciones. En esta sección, enfatizaremos un par de asuntosmás, referentes a cómo se comportan las colecciones en tiempo de ejecución.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

172 de 198 17/02/2009 09:25 a.m.

Page 173: HIBERNATE - Persistencia

19.5.1. Taxonomía

Hibernate define tres tipos básicos de colección:

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 quenecesitamos saber acerca del modelo relacional. Para comprender cabalmente la estructura relacional y las característicasde performance, debemos considerar también la estructura de la clave primaria que es usada por Hibernate para actualizaro 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 serindexada eficientemente y una fila en particular puede ser localizada eficientemente cuando Hibernate trate de actualizarlao borrarla.

Los sets tienen una clave primaria que consiste en una <key> y columnas elemento. Esto puede ser menos eficiente paraalgunos 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, paraasociaciones de-uno-a-muchos o de-muchos-a-muchos, particularmente cuando se usen identificadores sintéticos, es muyprobable que sea igual de eficiente. (Nota aparte: si se quiere que SchemaExport realmente cree por sí solo la claveprimaria 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 puededefinir ninguna clave primaria. Hibernate no tiene forma de distinguir entre filas duplicadas. Hibernate resuelve esteproblema borrando completamente la colección (con un simple DELETE) y recreándola cada vez que algo cambia. Estopuede 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 labase 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 setspermiten 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 ocolecciones de valores: a causa de la estructura de un Set, Hibernate nunca efectúa un UPDATE de una fila cuando unelemento se "cambia". Los cambios en un Set siempre requieren INSERT(s) y DELETE(s) de filas individuales. Estaconsideració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 (noinversos) de colección con mejor performance, seguidos de cerca por los sets. Los sets son el tipo más común de colecciónen 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 sonen realidad asociaciones de-uno-a-muchos con inverse="true". Para estas asociaciones, el UPDATE es manejado por elextremo 'de-muchos-a-uno' de la asociación, así que estas disquisiciones sobre la performance al actualizar coleccionessimplemente no son relevantes.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

173 de 198 17/02/2009 09:25 a.m.

Page 174: HIBERNATE - Persistencia

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) tienenmucha mejor performance que los sets. Para una colección con inverse="true" (la relación bidireccional estándarestá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() siempretienen que devolver "true" para una bag o list, al contrario de lo que ocurre con un Set. Esto puede volver el códigosiguiente mucho más rápido:

Parent p = (Parent) sess.load(Parent.class, id);Child c = new Child();c.setParent(p);p.getChildren().add(c); // ¡no es necesario capturar toda la colección!sess.flush();

19.5.4. Borrado en una pasada

Ocasionalmente, borrar elementos de una colección puede volverse extremadamente ineficiente. Hibernate no escompletamente estúpido, así que sabe que no lo tiene que hacer en el caso de una colección nueva y vacía, si uno invocalist.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. Hibernateemitirá 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 dosmaneras de proceder:

borrar 18 filas una por una, y luego insertar 3 filas

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ásrá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 (esdecir des-referenciando) la colección original, y devolviendo una colección recientemente instanciada con todos loselementos 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 coninverse="true".

19.6. Monitorear la performance

Optimizar no es muy útil sin monitoreo y acceso a medidas concretas de performance. Hibernate provee un rango muycompleto de valores numéricos acerca de sus operaciones internas. Las estadísticas en Hibernate son accesibles por cadaSessionFactory.

19.6.1. Monitorear una SessionFactory

Se puede acceder a mediciones de la SessionFactory de dos maneras: La primera opción es llamar asessionFactory.getStatistics() y leer o mostrar la Statistics usted mismo.

Hibernate también puese usar JMX para publicar mediciones, si se habilita el MBean StatisticsService MBean. Sepuede habilitar un solo MBean para todas las SessionFactorys, o uno para cada una. Vea el siguiente código paraejemplos mínimos de configuración:

// servicio de registro de MBean para una SessionFactory específicaHashtable tb = new Hashtable();tb.put("type", "statistics");tb.put("sessionFactory", "myFinancialApp");ObjectName on = new ObjectName("hibernate", tb); // nombre del objeto MBean

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

174 de 198 17/02/2009 09:25 a.m.

Page 175: HIBERNATE - Persistencia

StatisticsService stats = new StatisticsService(); // implementación del MBeanstats.setSessionFactory(sessionFactory); // liga las estadísticas a la SessionFactoryserver.registerMBean(stats, on); // registra el Mbean em el servidor

// servicio de registro de MBean para todas las SessionFactorysHashtable tb = new Hashtable();tb.put("type", "statistics");tb.put("sessionFactory", "all");ObjectName on = new ObjectName("hibernate", tb); // nombre del objeto MBean

StatisticsService stats = new StatisticsService(); // implementación del MBeanserver.registerMBean(stats, on); // Registra el MBean en el servidor

A hacer: esto no tiene sentido. En el primer caso, obtenemos y usamos el MBean dorectamente. En el segundo, debemosdar el nombre JNDI en el cual la fábrica de sesiones está contenida antes de usarla. UsarhibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name")

Se puede activar/desactivar el monitoreo para una SessionFactory:

en tiempo de configuración, asígnesele a hibernate.generate_statistics el valor false

en tiempo de ejecución, sf.getStatistics().setStatisticsEnabled(true)ohibernateStatsBean.setStatisticsEnabled(true)

Las estadísticas pueden ser reinicializadas en forma programática, usando el método clear(). Se puede mandar unsumario 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 esrelevante en ciertos escenarios. Todos los contadores relevantes están descriptos en la API de Statistics, en trescategorías:

Las mediciones relacionadas con el uso de la sesión en general, tales como el número de desiones abiertas, elnúmero de conexiones JDBC obtenidas, etc.

Mediciones relacionadas con las entidades, colecciones, consultas y cachés como un todo (es decir, medicionesglobales).

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 elcaché (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 simplesmétodos "getter". Se puede acceder a las mediciones de una entidad, colección o región de caché en particular a través delnombre, y a través de su representación en HQL o en SQL para las consultas. Por favor refiérase a los JavaDocs de lasAPIs de Statistics, EntityStatistics, CollectionStatistics, SecondLevelCacheStatistics, yQueryStatistics para más información. El siguiente código muestra un ejemplo simple:

Statistics stats = HibernateUtil.sessionFactory.getStatistics();

double queryCacheHitCount = stats.getQueryCacheHitCount();double queryCacheMissCount = stats.getQueryCacheMissCount();double queryCacheHitRatio = queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);

log.info("Query Hit ratio:" + queryCacheHitRatio);

EntityStatistics entityStats = stats.getEntityStatistics( Cat.class.getName() );long changes = entityStats.getInsertCount() + entityStats.getUpdateCount() + entityStats.getDeleteCount();log.info(Cat.class.getName() + " changed " + changes + "times" );

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

175 de 198 17/02/2009 09:25 a.m.

Page 176: HIBERNATE - Persistencia

Para trabajar en todas las entidades, colecciones, consultas y regiones de caché, se pued obtener la lista de nombres deentidades, colecciones, consultas y regiones con los siguientes métodos: getQueries(), getEntityNames(),getCollectionRoleNames(), y getSecondLevelCacheRegionNames().

Capítulo 20. Guía de las herramientas (Toolset)

Hay un conjunto de plugins de Eclipse, tasks de Ant y herramientas de consola, que permiten uar Hibernate para practicaringeniería reversa, y también "de ida" (roundtrip engineering).

Las Herramientas de Hibernate (Hibernate Tools) actualmente incluyen plugins para el entorno ECLIPSE, así comoherramientas 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 desintaxis. También soporta auto-compleción semántica para los nombres de las clases y para los nombres de camposy 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 laconfiguración de la consola, también permite la ejecución de consultas HQL contra la base de datos, y nevegar losresultados 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 otrade manera secuencial, para permitirle al usuario realizar una operación compleja guiándolo a través de unaspocas opciones simples por vez.Se puede usar un "wizard" para generar rápidamente archivos de configuración de Hibernate, o se se le puede haceruna ingeniería reversa completa a un esquema de DB existente, convirtiéndolo en archivos fuente tipo POJO yarchivos de mapeo de Hibernate. El wizard para ingeniería reversa soporta patrones (templates) configurables amedida.

Tareas (tasks) de Ant:

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 usadainstantáneamente desde dentro de Hibernate): SchemaExport, también llamada hbm2ddl.

20.1. Generación automática del esquema de base de datos

El lenguaje de definición de datos (o DDL por su siglas en inglés) puede ser generado por una utilidad de Hibernate apartir de los archivos de mapeo. El esquema generado incluye las constraints de integridad referencial (claves primarias yforáneas) para las tablas de entidades y colecciones. También se crean tablas y secuencias para los generadores deidentificadores.

Se debe especificar el dialecto SQL (Dialect) a través de la propiedad hibernate.dialect cuando se use estaherramienta, dado que el DDL es altamente dependiente del proveedor de DB.

Primero, retoque sus archivos de mapeo para mejorar el esquema a generar.

20.1.1. Retocar el esquema de base de datos

Muchos elementos de mapeo de Hibernate definen atributos opcionales llamados length, precision y scale. Se puedeconfigurar el largo, precisión y escala respectivamente de una columna con estos atributos.

<property name="zip" length="5"/>

<property name="balance" precision="12" scale="2"/>

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).

<many-to-one name="bar" column="barId" not-null="true"/>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

176 de 198 17/02/2009 09:25 a.m.

Page 177: HIBERNATE - Persistencia

<element column="serialNumber" type="long" not-null="true" unique="true"/>

Se puede usar un atributo unique-key para agrupar columnas en una simple constraint de unicidad. Al presente, el valorespecificado del atributo unique-key no es usado para nombrar la constraint en el DDL generado, solamente para agrupara las columnas en el archivo de mapeo.

<many-to-one name="org" column="orgId" unique-key="OrgEmployeeId"/><property name="employeeId" unique-key="OrgEmployee"/>

Un atributo index especifica el nombre de un índice a crear utilizando la columna o columnas mapeadas. Se puede agruparmúltiples columnas en en mismo índice, simplemente especificando el mismo nombre de índice.

<property name="lastName" index="CustName"/><property name="firstName" index="CustName"/>

Un atributo foreign-key puede ser usado para sustituir el nombre generado de cualquier constraint de clave foránea.

<many-to-one name="bar" column="barId" foreign-key="FKFooBar"/>

Muvhos elementos de mapeo también aceptan un elemento <column> hijo. Esto es particularmente útil al mapear tiposmulticolumna:

<property name="name" type="my.customtypes.Name"/> <column name="last" not-null="true" index="bar_idx" length="30"/> <column name="first" not-null="true" index="bar_idx" length="20"/> <column name="initial"/></property>

El atributo default permite especificar un valor por defecto pra la columna (debería asignársele el mismo valor a lapropiedad mapeada antes de grabar una nueva instancia de la clase mapeada).

<property name="credits" type="integer" insert="false"> <column name="credits" default="10"/></property>

<version name="version" type="integer" insert="false"> <column name="version" default="0"/></property>

El atributo sql-type le permite al usuario sustituir el mapeo por defecto de un tipo de dato Hibernate a un tipo de datoSQL.

<property name="balance" type="float"> <column name="balance" sql-type="decimal(13,3)"/></property>

El atributo check permite especificar una constraint de chequeo.

<property name="foo" type="integer"> <column name="foo" check="foo > 10"/></property>

<class name="Foo" table="foos" check="bar < 100.0">

... <property name="bar" type="float"/></class>

Tabla 20.1. Sumario

Atributo Valores Interpretaciónlargo numéricos largo de la columna

precision numéricos precisión decimal de la columna

scale numérico escala decimal de la columna

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

177 de 198 17/02/2009 09:25 a.m.

Page 178: HIBERNATE - Persistencia

Atributo Valores Interpretaciónnot-null true|false especifica que la columna debería ser no anulable

unique true|false especifica que la columna debería tener una constraint de unicidad

index index_name especifica el nombre de un índice multicolumna

unique-key unique_key_name especifica el nombre de una constraint de unicidad multicolumna

foreign-key foreign_key_name

especifica el nombre de una constraint de clave foránea generada para unaasociación, para un elemento de mapeo <one-to-one>, <many-to-one>, <key>,o <many-to-many>. Note que los extremos con inverse="true" seránignorados por el SchemaExport.

sql-type tipo SQL de lacolumna

sustituye el tipo de columna por defecto (atributo del elemento <column>solamente)

default expresión SQL especifica un valor por defecto para la columna

check expresión SQL crea una constraint de chequeo, sea para la tabla o para la columna

El elemento <comment> permite especificar comentarios en el esquema generado.

<class name="Customer" table="CurCust"> <comment>Current customers only</comment>

...</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.

20.1.2. Ejecutar la herramienta

La herramienta SchemaExport imprime un script DDL en la salida estándar y/o ejecuta los comandos DDL.

java -cp hibernate_classpaths org.hibernate.tool.hbm2ddl.SchemaExport options mapping_files

Table 20.2. Opciones de línea de comandos para SchemaExport

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

También se puede incrustar un SchemaExport dentro de la aplicación

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

178 de 198 17/02/2009 09:25 a.m.

Page 179: HIBERNATE - Persistencia

Configuration cfg = ....;new SchemaExport(cfg).create(false, true);

20.1.3. Propiedades

Se puede especificar propiedades de base de datos:

como propiedades de sistema con -D<propiedad>

en hibernate.properties

en un archivo nombrado, con --properties

Las propiedades que se necesitan son:

Tabla 20.3. Porpiedades de conexión de SchemaExport

Nombre de la propiedad Descripciónhibernate.connection.driver_class clase del driver jdbc

hibernate.connection.url URL jdbc

hibernate.connection.username usuario de la base de datos database user

hibernate.connection.password clave del usuario

hibernate.dialect dialecto

20.1.4. Usar Ant

Se puede llamar al SchemaExport desde un script de Ant

<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>

20.1.5. Actualizaciones incrementales del esquema de base de datos

La herramienta SchemaUpdate actualizará un esquema de base de datos existente con cambios "incrementales". Note queSchemaUpdate depende grandemente de la API de metadatos de JDBC, así que no funcionará con todos los drivers deJDBC.

java -cp hibernate_classpaths org.hibernate.tool.hbm2ddl.SchemaUpdate options mapping_files

Table 20.4. SchemaUpdate Command Line Options

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

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

179 de 198 17/02/2009 09:25 a.m.

Page 180: HIBERNATE - Persistencia

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

Se puede incrustar un SchemaUpdate en la aplicación

Configuration cfg = ....;new SchemaUpdate(cfg).execute(false);

20.1.6. Utilizar Ant para las actualizaciones incrementales del esquema de base de datos

Se puede invocar a SchemaUpdate desde el script de Ant:

<target name="schemaupdate"> <taskdef name="schemaupdate" classname="org.hibernate.tool.hbm2ddl.SchemaUpdateTask" classpathr

<schemaupdate properties="hibernate.properties" quiet="no"> <fileset dir="src"> <include name="**/*.hbm.xml"/> </fileset> </schemaupdate></target>

20.1.7. Validación del esquema de base de datos

La herramienta SchemaValidator validará si el esquema de BD existente "corresponde" con los archivos de mapeo. Noteque el SchemaValidator depende grandemente de la API de metadatos de JDB, así que no funcionará con todos losdrivers JDBC, Esta herramienta es extremadamente útil para tests.

java -cp hibernate_classpaths org.hibernate.tool.hbm2ddl.SchemaValidator options mapping_files

Tabla 20.5. Opciones de línea de comando para SchemaValidator

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

Se puede incrustar un archivo SchemaValidator en la aplicación

Configuration cfg = ....;new SchemaValidator(cfg).validate();

20.1.8. Usar Ant para la validación de un esquema de base de datos

Se puede llamar al SchemaValidator desde el script de Ant:

<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>

Capítulo 21. Ejemplo: Padre/Hijo

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

180 de 198 17/02/2009 09:25 a.m.

Page 181: HIBERNATE - Persistencia

Una de las primeras cosas que los nuevos usuarios tratan de hacer con Hibernate, es modelar una relación del tipopadre/hijo (en inglés, "parent" y "child" respectivamente). Hay dos abordajes diferentes para esto. Por varias razones, elabordaje más conveniente, especialmente para usuarios nuevos, es modelar tanto Padre como Hijo como clases deentidad, con una asociación de-uno-a-muchos (<one-to-many>) del Padre al Hijo. (El enfoque alternativo es declarar elHijo como un elemento compuesto ( <composite-element>). Ahora bien, ocurre que la semántica por defecto de unaasociación de-uno-a-muchos (en Hibernate), es mucho más parecida a la semántica usual de una relación padre/hijo, que ala semántica del mapeo de un elemento compuesto. Explicaremos cómo usar una asociación 'de-uno-a-muchosbidireccional con propagación en cascada' para modelar una relación padre/hijo eficiente y elegantemente. ¡No es nadadifícil!

21.1. Nota sobre las colecciones

Las colecciones de Hibernate se consideran como una parte lógica de la entidad que las posee, nunca de las entidadescontenidas. ¡Esta distinción es crucial! Y tiene las siguientes consecuencias:

Cuando quitamos/agregamos un objeto de una colección, el número de versión del dueño de la colección seincrementa.

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 elestado interno de una entidad no debería causar que la entidad asociada se esfume! Del mismo modo, agregarle unaentidad 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ínculoentre las dos entidades, y quitársela borra dicho vínculo. Esto es muy apropiado para todos los casos, excepto para el casoen que la vida de un hijo dependa del ciclo de vida del padre.

21.2. de-uno-a-muchos bidireccional

Supongamos que se empieza con una simple asociación de-uno-a-muchos de padre a hijo.

<set name="children"> <key column="parent_id"/> <one-to-many class="Child"/></set>

If we were to execute the following code

Parent p = .....;Child c = new Child();p.getChildren().add(c);session.save(c);session.flush();

Hibernate emitirá los siguientes comandos SQL:

un INSERT para crear el registro para c

un UPDATE para crear el vínculo de p a c

Esto es no solamente ineficiente, sino que viola cualquier constraint NOT NULL que pudiere haber en la columnaparent_id column. Podemos areglar la violación de la constraint de nulabilidad especificando not-null="true" en elmapeo de la colección:

<set name="children"> <key column="parent_id" not-null="true"/> <one-to-many class="Child"/></set>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

181 de 198 17/02/2009 09:25 a.m.

Page 182: HIBERNATE - Persistencia

De todos modos, ésta no es la solución que se recomienda.

La causa subyacente de este comportamiento es que el vínculo (la clave foránea parent_id) de p a c no se consideracomo parte del estado del objeto Child y por lo tanto no es creado durante el INSERT. Así que la solución es volver alvínculo que es parte del mapeo del objeto Child.

<many-to-one name="parent" column="parent_id" not-null="true"/>

(También es necesario agragar la propiedad parent a la clase Child).

Ahora que la entidad Child está manejando el estado del vínculo, le indicamos a la colección que no actualice el vínculoella. Para esto se usa el atributo inverse.

<set name="children" inverse="true"> <key column="parent_id"/> <one-to-many class="Child"/></set>

El código siguiente sería usado para agregar un nuevo Child:

Parent p = (Parent) session.load(Parent.class, pid);Child c = new Child();c.setParent(p);p.getChildren().add(c);session.save(c);session.flush();

¡Y ahora, sólo se emite un comando INSERT!

Para afinar las cosas un poco más, podríamos crear un método addChild() en la clase Parent.

public void addChild(Child c) { c.setParent(this); children.add(c);}

y ahora, el cófigo para agregar un Child se ve así:

Parent p = (Parent) session.load(Parent.class, pid);Child c = new Child();p.addChild(c);session.save(c);session.flush();

21.3. Ciclo de vida de la propagación en cascada

El llamado explícito a save() aún es molesto. Nos ocuparemos de esto usando la propagación en cascada.

<set name="children" inverse="true" cascade="all"> <key column="parent_id"/> <one-to-many class="Child"/></set>

Esto simplifica el código anterior a

Parent p = (Parent) session.load(Parent.class, pid);Child c = new Child();p.addChild(c);session.flush();

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 todossus hijos de la base de datos:

Parent p = (Parent) session.load(Parent.class, pid);session.delete(p);session.flush();

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

182 de 198 17/02/2009 09:25 a.m.

Page 183: HIBERNATE - Persistencia

Sin embargo, este código

Parent p = (Parent) session.load(Parent.class, pid);Child c = (Child) p.getChildren().iterator().next();p.getChildren().remove(c);c.setParent(null);session.flush();

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 NOTNULL). Hay que borrar al (con delete()) explícitamente al Child.

Parent p = (Parent) session.load(Parent.class, pid);Child c = (Child) p.getChildren().iterator().next();p.getChildren().remove(c);session.delete(c);session.flush();

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".

<set name="children" inverse="true" cascade="all-delete-orphan"> <key column="parent_id"/> <one-to-many class="Child"/></set>

Nota: aún cuando el mapeo de la colección especifica inverse="true", lás propagaciones en cascada sí se procesaniterando a través de los elementos de la colección. Así que si se necesita que una colección sea grabada, borrada oactualizada en cascada, se debe agregar el atributo "cascade" a la colección. No basta con invocar setParent().

21.4. Las propagaciones en cascada y unsaved-value

Supóngase que cargamos un Parent en una sesión, hacemos algunos cambios en la interfaz de usuario, y deseamospersistir dichos cambios en una nueva sesión, invocando update(). El Parent contendrá una colección de hijos, y comola propagación en cascada para "update" está habilitada, Hibernate necesita saber cuáles de los hijos han sidorecientemente instanciados, y cuáles representan filas ya existentes en la base de datos. Asumamos que tanto Parentcomo Child tienen identificadores generados del tipo Long. Hibernate Hibernate usará el identificador y los valores depropiedad de version/timestamp para determinar cuáles de los hijos son nuevos (Vea Sección 10.7, “Detección automáticade estado”). En Hibernate3, ya no es necesario especificar un unsaved-value explícitamente.

El siguiente código actualizará tanto al padre como al hijo, e insertará un nuevo hijo (newChild).

//tanto el padre como el hijo fueron cargados en una sesión anteriorparent.addChild(child);Child newChild = new Child();parent.addChild(newChild);session.update(parent);session.flush();

Todo esto está muy bien en el caso de un identificador autogenerado, pero ¿qué pasas con los identificadores asignados, ocon los identificadores compuestos? Esto es más difícil, dado que Hibernate no puede usar la propiedad identificadora paradistinguir entre un objeto recientemente instanciado (con un identificador asignado por el usuario) y un objeto cargado enuna sesión previa. En este caso, Hibernate usará o bien las propiedades version/timestamp, o bien consultará el caché de2do 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 yfunciona en una forma muy linda. La mayoría de las aplicaciones Hibernate utilizan el patrón padre/hijo en muchoslugares.

En el primer párrafo mencionábamos una alternativa. En el caso de los mapeos con <composite-element> no existeninguno de estos problemas, que tienen exactamente la semántica de una relación padre/hijo. Desafortunadamente, existendos grandes limitaciones al trabajar con clases de elementos compuestos: un elemento compuesto no puede contenercolecciones, y ellos mismos sólo pueden ser hijos de una única entidad padre.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

183 de 198 17/02/2009 09:25 a.m.

Page 184: HIBERNATE - Persistencia

Capítulo 22. Ejemplo: la aplicación Weblog

22.1. Clases persistentes

Las clases pesistentes representan: un weblog (bitácora de web), y un item publicado en el weblog. Tienen que sermodeladas según una relación padre/hijo estándar, pero utulizaremos una bag ordenada, en lugar de un set.

package eg;

import java.util.List;

public class Blog { private Long _id; private String _name; private List _items;

public Long getId() { return _id; } public List getItems() { return _items; } public String getName() { return _name; } public void setId(Long long1) { _id = long1; } public void setItems(List list) { _items = list; } public void setName(String string) { _name = string; }}

package eg;

import java.text.DateFormat;import java.util.Calendar;

public class BlogItem { private Long _id; private Calendar _datetime; private String _text; private String _title; private Blog _blog;

public Blog getBlog() { return _blog; } public Calendar getDatetime() { return _datetime; } public Long getId() { return _id; } public String getText() { return _text; } public String getTitle() { return _title; } public void setBlog(Blog blog) { _blog = blog; } public void setDatetime(Calendar calendar) { _datetime = calendar; } public void setId(Long long1) { _id = long1; } public void setText(String string) { _text = string;

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

184 de 198 17/02/2009 09:25 a.m.

Page 185: HIBERNATE - Persistencia

} public void setTitle(String string) { _title = string; }}

22.2. Mapeos de Hibernate

Los mapeos de XML deberían resultar bastante sencillos.

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="eg">

<class name="Blog" table="BLOGS">

<id name="id" column="BLOG_ID"> <generator class="native"/> </id>

<property name="name" column="NAME" not-null="true" unique="true"/>

<bag name="items" inverse="true" order-by="DATE_TIME" cascade="all"> <key column="BLOG_ID"/> <one-to-many class="BlogItem"/> </bag>

</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 name="BlogItem" table="BLOG_ITEMS" dynamic-update="true">

<id name="id" column="BLOG_ITEM_ID"> <generator class="native"/> </id>

<property name="title" column="TITLE" not-null="true"/>

<property name="text" column="TEXT" not-null="true"/>

<property name="datetime" column="DATE_TIME" not-null="true"/>

<many-to-one name="blog" column="BLOG_ID" not-null="true"/>

</class>

</hibernate-mapping>

22.3. Código Hibernate

La clase siguiente demuestra algunas de las cosas que se puede hacer con estas clases, usando Hibernate.

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;

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

185 de 198 17/02/2009 09:25 a.m.

Page 186: HIBERNATE - Persistencia

import org.hibernate.Session;import org.hibernate.SessionFactory;import org.hibernate.Transaction;import org.hibernate.cfg.Configuration;import org.hibernate.tool.hbm2ddl.SchemaExport;

public class BlogMain {

private SessionFactory _sessions;

public void configure() throws HibernateException { _sessions = new Configuration() .addClass(Blog.class) .addClass(BlogItem.class) .buildSessionFactory(); }

public void exportTables() throws HibernateException { Configuration cfg = new Configuration() .addClass(Blog.class) .addClass(BlogItem.class); new SchemaExport(cfg).create(true, true); }

public Blog createBlog(String name) throws HibernateException {

Blog blog = new Blog(); blog.setName(name); blog.setItems( new ArrayList() );

Session session = _sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); session.persist(blog); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return blog; }

public BlogItem createBlogItem(Blog blog, String title, String text) throws HibernateException

BlogItem item = new BlogItem(); item.setTitle(title); item.setText(text); item.setBlog(blog); item.setDatetime( Calendar.getInstance() ); blog.getItems().add(item);

Session session = _sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); session.update(blog); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return item; }

public BlogItem createBlogItem(Long blogid, String title, String text) throws HibernateExceptio

BlogItem item = new BlogItem(); item.setTitle(title); item.setText(text); item.setDatetime( Calendar.getInstance() );

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

186 de 198 17/02/2009 09:25 a.m.

Page 187: HIBERNATE - Persistencia

Session session = _sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); Blog blog = (Blog) session.load(Blog.class, blogid); item.setBlog(blog); blog.getItems().add(item); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return item; }

public void updateBlogItem(BlogItem item, String text) throws HibernateException {

item.setText(text);

Session session = _sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); session.update(item); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } }

public void updateBlogItem(Long itemid, String text) throws HibernateException {

Session session = _sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); BlogItem item = (BlogItem) session.load(BlogItem.class, itemid); item.setText(text); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } }

public List listAllBlogNamesAndItemCounts(int max) throws HibernateException {

Session session = _sessions.openSession(); Transaction tx = null; List result = null; try { tx = session.beginTransaction(); Query q = session.createQuery( "select blog.id, blog.name, count(blogItem) " + "from Blog as blog " + "left outer join blog.items as blogItem " + "group by blog.name, blog.id " + "order by max(blogItem.datetime)" ); q.setMaxResults(max); result = q.list(); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; }

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

187 de 198 17/02/2009 09:25 a.m.

Page 188: HIBERNATE - Persistencia

finally { session.close(); } return result; }

public Blog getBlogAndAllItems(Long blogid) throws HibernateException {

Session session = _sessions.openSession(); Transaction tx = null; Blog blog = null; try { tx = session.beginTransaction(); Query q = session.createQuery( "from Blog as blog " + "left outer join fetch blog.items " + "where blog.id = :blogid" ); q.setParameter("blogid", blogid); blog = (Blog) q.uniqueResult(); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return blog; }

public List listBlogsAndRecentItems() throws HibernateException {

Session session = _sessions.openSession(); Transaction tx = null; List result = null; try { tx = session.beginTransaction(); Query q = session.createQuery( "from Blog as blog " + "inner join blog.items as blogItem " + "where blogItem.datetime > :minDate" );

Calendar cal = Calendar.getInstance(); cal.roll(Calendar.MONTH, false); q.setCalendar("minDate", cal);

result = q.list(); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return result; }}

Capítulo 23. Ejemplo: Mapeos varios

Estos capítulos exponen algunos mapeos de asociación más complejos.

23.1. Empleador/Empleado

El modelo siguiente de la relación entre empleador y empleado (Employer y Employee) usa una verdadera clase deentidad, Employment (empleo) para representar la asociacón. Esto se hace porque puede haber másde un período deempleo para las mismas dos partes involucradas. Para modelar valores monetarios y nombres de empleados se usancomponentes.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

188 de 198 17/02/2009 09:25 a.m.

Page 189: HIBERNATE - Persistencia

He aquí un documento de mapeo posible:

<hibernate-mapping>

<class name="Employer" table="employers"> <id name="id"> <generator class="sequence"> <param name="sequence">employer_id_seq</param> </generator> </id> <property name="name"/> </class>

<class name="Employment" table="employment_periods">

<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"/>

<component name="hourlyRate" class="MonetaryAmount"> <property name="amount"> <column name="hourly_rate" sql-type="NUMERIC(12, 2)"/> </property> <property name="currency" length="12"/> </component>

<many-to-one name="employer" column="employer_id" not-null="true"/> <many-to-one name="employee" column="employee_id" not-null="true"/>

</class>

<class name="Employee" table="employees"> <id name="id"> <generator class="sequence"> <param name="sequence">employee_id_seq</param> </generator> </id> <property name="taxfileNumber"/> <component name="name" class="Name"> <property name="firstName"/> <property name="initial"/> <property name="lastName"/> </component> </class>

</hibernate-mapping>

Y éste es el esquema de tablas generado por SchemaExport.

create table employers (

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

189 de 198 17/02/2009 09:25 a.m.

Page 190: HIBERNATE - Persistencia

id BIGINT not null, name VARCHAR(255), primary key (id))

create table employment_periods ( id BIGINT not null, hourly_rate NUMERIC(12, 2), currency VARCHAR(12), employee_id BIGINT not null, employer_id BIGINT not null, end_date TIMESTAMP, start_date TIMESTAMP, primary key (id))

create table employees ( id BIGINT not null, firstName VARCHAR(255), initial CHAR(1), lastName VARCHAR(255), taxfileNumber VARCHAR(255), primary key (id))

alter table employment_periods add constraint employment_periodsFK0 foreign key (employer_id) referalter table employment_periods add constraint employment_periodsFK1 foreign key (employee_id) refercreate sequence employee_id_seqcreate sequence employment_id_seqcreate 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 laasociación entre Author y Person como de-uno-a-uno. Otra posibilidad sería hacer que Author extendiere Person.

El siguiente documento de mapeo representa estas relaciones correctamente:

<hibernate-mapping>

<class name="Work" table="works" discriminator-value="W">

<id name="id" column="id"> <generator class="native"/> </id>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

190 de 198 17/02/2009 09:25 a.m.

Page 191: HIBERNATE - Persistencia

<discriminator column="type" type="character"/>

<property name="title"/> <set name="authors" table="author_work"> <key column name="work_id"/> <many-to-many class="Author" column name="author_id"/> </set>

<subclass name="Book" discriminator-value="B"> <property name="text"/> </subclass>

<subclass name="Song" discriminator-value="S"> <property name="tempo"/> <property name="genre"/> </subclass>

</class>

<class name="Author" table="authors">

<id name="id" column="id"> <!-- el Autor tiene que tener el mismo identificador que la Persona --> <generator class="assigned"/> </id>

<property name="alias"/> <one-to-one name="person" constrained="true"/>

<set name="works" table="author_work" inverse="true"> <key column="author_id"/> <many-to-many class="Work" column="work_id"/> </set>

</class>

<class name="Person" table="persons"> <id name="id" column="id"> <generator class="native"/> </id> <property name="name"/> </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 cuales generado por SchemaExport.

create table works ( id BIGINT not null generated by default as identity, tempo FLOAT, genre VARCHAR(255), text INTEGER, title VARCHAR(255), type CHAR(1) not null, primary key (id))

create table author_work ( author_id BIGINT not null, work_id BIGINT not null, primary key (work_id, author_id))

create table authors ( id BIGINT not null generated by default as identity, alias VARCHAR(255), primary key (id))

create table persons ( id BIGINT not null generated by default as identity, name VARCHAR(255), primary key (id))

alter table authors add constraint authorsFK0 foreign key (id) references persons

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

191 de 198 17/02/2009 09:25 a.m.

Page 192: HIBERNATE - Persistencia

alter table author_work add constraint author_workFK0 foreign key (author_id) references authorsalter 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 yOrder, pero ¿cómo deberíamos representar Order / LineItem / Product? Elijo mapear LineItem como una clase deasociació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>

<class name="Customer" table="customers"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <set name="orders" inverse="true"> <key column="customer_id"/> <one-to-many class="Order"/> </set> </class>

<class name="Order" table="orders"> <id name="id"> <generator class="native"/> </id> <property name="date"/>

<many-to-one name="customer" column="customer_id"/> <list name="lineItems" table="line_items"> <key column="order_id"/> <list-index column="line_number"/> <composite-element class="LineItem"> <property name="quantity"/> <many-to-one name="product" column="product_id"/> </composite-element> </list> </class>

<class name="Product" table="products"> <id name="id"> <generator class="native"/> </id> <property name="serialNumber"/> </class>

</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.

create table customers ( id BIGINT not null generated by default as identity, name VARCHAR(255),

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

192 de 198 17/02/2009 09:25 a.m.

Page 193: HIBERNATE - Persistencia

primary key (id))

create table orders ( id BIGINT not null generated by default as identity, customer_id BIGINT, date TIMESTAMP, primary key (id))

create table line_items ( line_number INTEGER not null, order_id BIGINT not null, product_id BIGINT, quantity INTEGER, primary key (order_id, line_number))

create table products ( id BIGINT not null generated by default as identity, serialNumber VARCHAR(255), primary key (id))

alter table orders add constraint ordersFK0 foreign key (customer_id) references customersalter table line_items add constraint line_itemsFK0 foreign key (product_id) references productsalter table line_items add constraint line_itemsFK1 foreign key (order_id) references orders

23.4. Ejemplos misceláneos de asociación

Estos ejemplos han sido extraídos todos ellos de la suite de tests de Hibernate. Allí pueden encontrarse muchos otrosejemplos útiles. Busque en el directorio test de la distribución de Hibernate.

A HACER: revestir esto con palabras

23.4.1. Asociación de-uno-a-uno "con tipo"

<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="Address" batch-size="2" check="addressType in ('MAILING', 'HOME', 'BUSINESS')"> <composite-id> <key-many-to-one name="person" column="personName"/> <key-property name="type" column="addressType"/> </composite-id> <property name="street" type="text"/> <property name="state"/> <property name="zip"/></class>

23.4.2. Ejemplo de clave compuesta

<class name="Customer">

<id name="customerId" length="10"> <generator class="assigned"/> </id>

<property name="name" not-null="true" length="100"/> <property name="address" not-null="true" length="200"/>

<list name="orders" inverse="true" cascade="save-update">

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

193 de 198 17/02/2009 09:25 a.m.

Page 194: HIBERNATE - Persistencia

<key column="customerId"/> <index column="orderNumber"/> <one-to-many class="Order"/> </list>

</class>

<class name="Order" table="CustomerOrder" lazy="true"> <synchronize table="LineItem"/> <synchronize table="Product"/>

<composite-id name="id" class="Order$Id"> <key-property name="customerId" length="10"/> <key-property name="orderNumber"/> </composite-id>

<property name="orderDate" type="calendar_date" not-null="true"/>

<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"/>

<bag name="lineItems" fetch="join" inverse="true" cascade="save-update"> <key> <column name="customerId"/> <column name="orderNumber"/> </key> <one-to-many class="LineItem"/> </bag>

</class>

<class name="LineItem">

<composite-id name="id" class="LineItem$Id"> <key-property name="customerId" length="10"/> <key-property name="orderNumber"/> <key-property name="productId" length="10"/> </composite-id>

<property name="quantity"/>

<many-to-one name="order" insert="false" update="false" not-null="true"> <column name="customerId"/> <column name="orderNumber"/> </many-to-one>

<many-to-one name="product" insert="false" update="false" not-null="true" column="productId"/>

</class>

<class name="Product">

<synchronize table="LineItem"/>

<id name="productId" length="10"> <generator class="assigned"/> </id>

<property name="description" not-null="true" length="200"/> <property name="price" length="3"/>

<property name="numberAvailable"/>

<property name="numberOrdered"> <formula> ( select sum(li.quantity)

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

194 de 198 17/02/2009 09:25 a.m.

Page 195: HIBERNATE - Persistencia

from LineItem li where li.productId = productId ) </formula> </property>

</class>

23.4.3. de-muchos-a-muchos con atributo compartido de clave compuesta

<class name="User" table="`User`"> <composite-id> <key-property name="name"/> <key-property name="org"/> </composite-id> <set name="groups" table="UserGroup">

<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="Group" table="`Group`"> <composite-id> <key-property name="name"/> <key-property name="org"/> </composite-id> <property name="description"/> <set name="users" table="UserGroup" inverse="true"> <key> <column name="groupName"/> <column name="org"/> </key> <many-to-many class="User"> <column name="userName"/> <formula>org</formula> </many-to-many> </set></class>

23.4.4. Discriminación basada en el contenido

<class name="Person" discriminator-value="P">

<id name="id" column="person_id" unsaved-value="0"> <generator class="native"/> </id>

<discriminator type="character"> <formula> case when title is not null then 'E' when salesperson is not null then 'C' else 'P' end </formula> </discriminator>

<property name="name" not-null="true" length="80"/>

<property name="sex" not-null="true" update="false"/>

<component name="address"> <property name="address"/> <property name="zip"/> <property name="country"/> </component>

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

195 de 198 17/02/2009 09:25 a.m.

Page 196: HIBERNATE - Persistencia

<subclass name="Employee" discriminator-value="E"> <property name="title" length="20"/> <property name="salary"/> <many-to-one name="manager"/> </subclass>

<subclass name="Customer" discriminator-value="C"> <property name="comments"/> <many-to-one name="salesperson"/> </subclass>

</class>

23.4.5. Asociaciones en claves alternativas

<class name="Person">

<id name="id"> <generator class="hilo"/> </id>

<property name="name" length="100"/>

<one-to-one name="address" property-ref="person" cascade="all" fetch="join"/>

<set name="accounts" inverse="true"> <key column="userId" property-ref="userId"/> <one-to-many class="Account"/> </set>

<property name="userId" length="8"/>

</class>

<class name="Address">

<id name="id"> <generator class="hilo"/> </id>

<property name="address" length="300"/> <property name="zip" length="5"/>

<property name="country" length="25"/> <many-to-one name="person" unique="true" not-null="true"/>

</class>

<class name="Account"> <id name="accountId" length="32"> <generator class="uuid"/> </id>

<many-to-one name="user" column="userId" property-ref="userId"/> <property name="type" not-null="true"/>

</class>

Capítulo 24. Prácticas recomendadas

Escriba clases con gran nivel de detalle, y mapéelas usando <component>.

Use una clase Direccion para encapsular calle, barrio, provincia, CodigoPostal. Esto alienta la reutilizaciónde código y simplifica la reingeniería.

Declare propiedades identificadoras en las clases persistentes.

Hibernate hace que las propiedades identificadoras sean opcionales, pero existe todo tipo de razones por las cualeshabría que usarlos siempre. Recomendamos que estos identificadores sean "sintéticos" (generados, sin ningún

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

196 de 198 17/02/2009 09:25 a.m.

Page 197: HIBERNATE - Persistencia

significado de negcocios).

Identifique claves naturales.

Identifique claves naturales para todas las entidades, y mapéelas usando <natural-id>. Implemente equals() yhashCode() para comparar las propiedades que componen la clave natural.

Ubique cada mapeo de clase en su propio archivo.

No use un único documento de mapeo monolítico. Mapee com.eg.Foo en el archivo com/eg/Foo.hbm.xml. Estotiene sentido, particularmente en un entorno de programación en equipo.

Cargue los archivos de mapeo como recursos.

Despliegue los mapeos junto con las clases que mapean.

Considere externalizar las cadenas SQL de las consultas.

Ésta es una buena práctica si sus consultas utilizan funciones SQL que no sean estándar de ANSI. Externalizarlasvolverá la aplicación más portátil.

Use variables de vínculo.

Como en JDBC, siempre reemplace valores no constantes con "?". ¡Nunca use manipulación de cadenas para unirun valor no-constante a una consulta! Mejor aún, considere usar parámetros nombrados en las consultas.

No maneje sus propias conexiones JDBC.

Hibernate le permite a la aplicación manejar sus conexiones JDBC, pero esto debe considerarse como un últmorecurso. Si no puede usar los proveedores de conexiones que ya vienen incluidos, considere proveer su propiaimplementación de org.hibernate.connection.ConnectionProvider.

Considere usar un tipo a medida.

Suponga que tiene un tipo Java, digamos de una biblioteca, que necesita ser persistido, pero no provee los métodosde acceso necesarios para mapearlo como un componente. Usted debería considerar implementarorg.hibernate.UserType. Esta estrategia libera al código de la aplicación de implementar transformaciones desdeo hacia un tipo Hibernate.

En cuellos de botella, use JDBC manual.

En áreas de performance críticas de un sistema, algunos tipos de operación podrían beneficiarse al usar JDBCdirectamente. Pero por favor, espere hasta estar seguro de que algo constituye un cuello de botella. Y no asuma queJDBC es necesariamente más rápida. Si necesita utilizar JDBC directamente, puede valer la pena abrir una sesión yusar esa conexión JDBC. De este modo, usted aún puede aprovechar la estrategia de transacciones y el proveedorde conexiones subyacente.

Comprenda el "flush" de sesión

De tanto en tanto, la sesión sincroniza su estado persistente con la base de datos. La performance se resentirá si esteproceso 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ónen particular.

En una arquitectura de tres capas, considere usar objetos desprendidos.

Cuando se usa una arquitectura de servlet/session bean, se pueden pasar objetos persistentes cargados en el sessionbean desde y hacia la capa de servlets/JSP. Use una nueva sesión para servir a cada solicitud (request). UseSession.merge() o Session.saveOrUpdate() para sincronizar objetos con la base de datos.

En una arquitectura de dos capas, considere usar contextos largos de persistencia.

Las transacciones de DB tienen que ser lo más cortas posible, para que la aplicación sea escalable. Sin embargo, aveces es necesario implementar transacciones "de aplicación" de largo aliento, una unidad de trabajo única desde elpunto de vista del usuario. Una "transacción de aplicación" puede extenderse por varios ciclos solicitud/respuestadel cliente. Es común usar objetos desprendidos para implementar transacciones de aplicación. Una alternativa,

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

197 de 198 17/02/2009 09:25 a.m.

Page 198: HIBERNATE - Persistencia

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 laconexión JDBC al final de cada slicitud, reconectándose al comenzar la solicitud siguiente. Nunca comparta unamisma sesión a través de más de una "transacción de aplicación" o estará trabajando con datos rancios.

No trate las excepciones como si fueran recuperables.

Esto una práctica necesaria más que una "práctica recomendada". Cuando ocurra una excepción, invoque unrollback de la transacción, y cierre la sesión. Si no lo hace, Hibernate no puede garantizar que el estado en memoriarepresente correctamente el estado persistente. Como corolario de esto, no use Session.load() para determinar siuna instancia con un identificador dado existe en la base de datos. En lugar de eso, use Session.get() o unaconsulta.

Prefiera la captura haragana (lazy fetching) para las asociaciones.

Use la captura ansiosa (eager fetching) con mesura. Use proxies y colecciones haraganas para la mayoría de lasasociaciones a clases que no es probable que sean retenidas en el caché de 2do nivel. Para asociaciones a clasescacheadas, en donde exista una probabilidad extremadamante alta de ubicarla en el caché, inhabilite explícitamentela captura haragana usando lazy="false". Cuando, para un caso de uso en particular, sea apropiado usar unacaptura 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 condatos no capturados.

Hibernate libera al programador de la tediosa tarea de escribir objetos de transferencia de datos (DTO por sus siglasen inglés). En una arquitectura EJB tradicional, los DTOs tienen un doble propósito: uno, sortear el problema de quelos entity beans no son serializables; el otro, que implícitamente definen una fase de ensamble en donde todos losdatos usados por la interfaz de usuario o "view" tiene que ser capturados y puestos en orden en DTOs, antes de queel control le sea devuelto a la capa de presentación. Hibernate elimina el primer propósito, pero aún se necesita unafase de ensamble (imagínese a los métodos de negocio como si tuvieran un estricto contrato con la capa depresentación acerca de qué datos hay disponibles en los objetos desprendidos), a menos que se esté dispuesto amantener la sesión abierta a través de la capa de presentación. Esto no es una limitación de Hibernate, sino unrequerimiento fundamental para un acceso seguro a datos transaccionales.

Considere abstraer su lógica de negocios, separándola de Hibernate.

Oculte el código (Hibernate) de acceso a datos detrás de una interfaz. Combine los partrones DAO y Sesión ThreadLocal. Se puede hacer incluso que algunas clases sean persistidas por JDBC escrita a mano, y asociadas a Hibernatepor medio de un UserType. (Este consejo se aplica sólo a aplicaciones "lo suficientemente largas", ¡no es adecuadopara una aplicación con 5 tablas!)

No use mapeos de asociación exóticos.

Los casos de uso bien definidos, que involucren verdaderas asociaciones de-muchos-a-muchos, son raros. Engeneral se necesita que la "tabla de vínculo" almacene información adicional. En ese caso, es mucho mejor usar dosasociaciones de-uno-a-muchos a una clase de vínculo intermedia. De hecho, creemos que la mayoría de lasasociaciones son de-uno-a-muchos y de-uno-a-uno. Hay que tener cuidado al usar cualquier otro estilo deasociación, y preguntarse si es verdaderamente necesario.

Prefiera las asociaciones bidireccionales.

Las asociaciones unidireccionales son más difíciles de consultar. En aplicaciones grandes, casi todas las asociacionesdeben ser navegables por consultas en ambas direcciones.

HIBERNATE - Persistencia Relacional para Java Idiomático http://www.hibernar.org/documentacion_es/castellano.html

198 de 198 17/02/2009 09:25 a.m.