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 2do nivel y caché de consultas
3.4.5. Sustituciones en el lenguaje de consultas.
3.4.6. Estadísticas de Hibernate
3.5. Logueo (logging, bitácora)
3.6. Implementando una NamingStrategy
3.7. Archivo de configuración XML
3.8. Integración con los Servidores de Aplicación J2EE
3.8.1. Configuración de una estrategia transaccional
3.8.2. SessionFactory ligada a JNDI
3.8.3. Manejo del contexto actual de la sesión con JTA
3.8.4. Despliegue de JMX
4. Clases Persistentes
4.1. Un simple ejemplo de POJO
4.1.1. Implemente un constructor sin argumentos
4.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 herencia
4.3. Implementar equals() y hashCode()
4.4. Modelos dinámicos
4.5. T-uplizadores
4.6. Extensiones
5. Mapeo O/R básico
5.1. Declaración del mapeo
5.1.1. El Doctype o "tipo de documento XML"
5.1.1.1. EntityResolver
5.1.2. hibernate-mapping
5.1.3. class
5.1.4. id
5.1.4.1. Generator
5.1.4.2. El algoritmo hi/lo
5.1.4.3. El argoritmo UUID
5.1.4.4. Columnas de identidad y secuencias
5.1.4.5. Identificadores asignados
5.1.4.6. Claves primarias assignadas por triggers
5.1.5. Generadores de identificador mejorados.
5.1.6. Optimización de los generadores de identificador
5.1.7. composite-id
5.1.8. discriminator
5.1.9. version (optativo)
5.1.10. timestamp (optativo)
5.1.11. property
5.1.12. many-to-one
5.1.13. one-to-one
5.1.14. natural-id
5.1.15. component, dynamic-component
5.1.16. properties
5.1.17. subclass
5.1.18. joined-subclass
5.1.19. union-subclass
5.1.20. join
5.1.21. key
5.1.22. elementos column y formula
5.1.23. import
5.1.24. any
5.2. Tipos de Hibernate
5.2.1. Entidades y "value types"
5.2.2. "Value types" básicos
5.2.3. "Value types" hechos a medida
5.3. Mapear una misma clase más de una vez
5.4. Identificadores de SQL entrecomillados
5.5. Alternativas de meta-datos
5.5.1. Usar marcadores de XDoclet
5.5.2. Usar anotaciones de JDK 5.0
5.6. Propiedades generadas
5.7. Objetos auxiliares de base de datos
6. Mapeo de Colecciones
6.1. Colecciones persistentes
6.2. Mapeo de colecciones
6.2.1. Claves foráneas de las colecciones
6.2.2. Elementos de la colección
6.2.3. Colecciones indexadas
6.2.4. Colecciones de valores y asociaciones de-muchos-a-muchos
6.2.5. Asociaciones de-uno-a-muchos
6.3. Mapeos de colección avanzados
6.3.1. Colecciones ordenadas
6.3.2. Asociaciones bidireccionales
6.3.3. Asociaciones bidireccionales con colecciones indexadas
6.3.4. Asociaciones ternarias
6.3.5. Usar una <idbag>
6.4. Ejemplos de colecciones
7. Mapeo de asociaciones
7.1. Introducción
7.2. Asociaciones unidireccionales
7.2.1. de-muchos-a-uno
7.2.2. de-uno-a-uno
7.2.3. de-uno-a-muchos
7.3. Asociaciones unidireccionales con tablas de unión
7.3.1. de-uno-a-muchos
7.3.2. de-muchos-a-uno
7.3.3. de-uno-a-uno
7.3.4. de-muchos-a-muchos
7.4. Asociaciones bidireccionales
7.4.1. de-uno-a-muchos / de-muchos-a-uno
7.4.2. de-uno-a-uno
7.5. Asociaciones bidireccionales con tablas de unión
7.5.1. de-uno-a-muchos / de-muchos-a-uno
7.5.2. de-uno-a-uno
7.5.3. de-muchos-a-muchos
7.6. Mapeos de asociacoones más complejas
8. Mapeo de componentes
8.1. Objetos dependientes
8.2. Colecciones de objetos dependientes
8.3. Comonentes usados como índices de un Map
8.4. Componentes usados como identificadores compuestos
8.5. Componentes dinámicos
9. Mapeo de herencia
9.1. Las tres estrategias
9.1.1. Una tabla por jerarquía de clases
9.1.2. Una tabla por subclase
9.1.3. Una tabla por subclase, usando un discriminador
9.1.4. Mezclar "una tabla por jerarquía de clases" con "una tabla por subclase"
9.1.5. Una tabla por cada clase concreta
9.1.6. Una tabla por cada clase concreta, usando polimorfismo implícito
9.1.7. Mezclar polimorfismo implícito con otras estrategias de mapeo de herencia
9.2. Limitaciones
10. Trabajar con objetos
10.1. Estados de un objeto de Hibernate
10.2. Hacer que los objetos se vuelvan persistentes
10.3. Cargar un objeto
10.4. Consultas
10.4.1. Ejecutar consultas
10.4.1.1. Iterar resultados
10.4.1.2. Consultas que devuelven T-uplas
10.4.1.3. Resultados escalares
10.4.1.4. Parámetros vinculados
10.4.1.5. Paginación
10.4.1.6. Iteración navegable
10.4.1.7. Externalizar consultas nombradas
10.4.2. Filtrar colecciones
10.4.3. Consultas "Criteria"
10.4.4. Consultas en SQL nativo
10.5. Modificar objetos persistentes
10.6. Modificar objetos desprnendidos
10.7. Detección automática de estado
10.8. Borrar objetos persistentes
10.9. Replicar un objeto entre dos repositorios de datos distintos
10.10. "Flush" de la sesión
10.11. Persistencia transitiva
10.12. Usar metadatos
11. Transacciones y concurrencia
11.1. La sesión y el alcance (scope) de las transacciones
11.1.1. Unidad de trabajo
11.1.2. Conversaciones largas
11.1.3.Considerar la identidad de los objetos
11.1.4. Problemas comunes
11.2. Demarcación de las transacciones de base de datos
11.2.1. Entornos no administrados
11.2.2. Usar JTA
11.2.3. Manejo de excepciones
11.2.4. Expiración de transacciones
11.3. Control optimista de concurrencia
11.3.1. Chequeo de versión hecho por la aplicación
11.3.2. Sesión extendida y versionado automático
11.3.3. Objetos desprendidos y versionado automático
11.3.4. Crear un método a medida para el versionado automático
11.4. "Lock" pesimista
11.5. Modos de liberación de conecciones
12. Interceptores y eventos
12.1. Interceptores
12.2. Sistema de eventos
12.3. Seguridad declarativa de Hibernate
13. Procesamiento en lotes
13.1. Inserciones en lotes
13.2. Actualizaciones en lotes
13.3. La interfaz StatelessSession
13.4. Operaciones del tipo "Lenguaje de Modificacion de Datos" (DML-style)
14. HQL: El lenguaje de consultas de Hibernate
14.1. Relevancia de mayúsculas y minúsculas
14.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 identificadora
14.6. La cláusula "select"
14.7. Funciones agregadas
14.8. Consultas polimórficas
14.9. La cláusua "where"
14.10. Expresiones
14.11. La cláusula "order by"
14.12. La cláusula "group by"
14.13. Subconsultas
14.14. Ejemplos de HQL
14.15. Actualizaciones y borrados en masa
14.16. Consejos y trucos
14.17. Componentes
14.18. Sintaxis del "Constructor de Valor de Fila" (row value constructor)
15. Consultas "Criteria"
15.1. Crear una instancia de Criteria
15.2. Acotar el resultado
15.3. Ordenar el resultado
15.4. Asociaciones
15.5. Captura dinámica de asociaciones
15.6. Consultas "Example"
15.7. Proyecciones, agregado y agrupamiento
15.8. Consultas y subconsultas desprendidas
15.9. Consultas por identificador natural
16. SQL nativo
16.1. Usar un SQLQuery
16.1.1. Consultas escalares
16.1.2. Consultas con entidades
16.1.3. Manipular colecciones y asociaciones
16.1.4. Devolver múltiples entidades
16.1.4.1. Alias y referencias a propiedades
16.1.5. Devolver entidades no administradas
16.1.6. Manejar herencia
16.1.7. Parámetros
16.2. Consultas SQL nombradas
16.2.1. Usar return-property para especificar nombres de columna/alias explícitamente
16.2.2. Usar procedimientos almacenados (stored procedures) para efectuar consultas
16.2.2.1. Reglas y limitaciones en el uso de procedimientos almacenados
16.3. SQL a medida para crear, almacenar y borrar
16.4. SQL a medida para cargar
17. Filtrar datos
17.1. Filtros de Hibernate
18. Mapeo XML
18.1. Trabajar con datos XML
18.1.1. Especificar el mapeo XML y el mapeo de la clase al mismo tiempo
18.1.2. Especificar sólo un mapeo XML
18.2. Metadatos del mapeo XML
18.3. Manipulación de los datos XML
19. Mejorar la performance
19.1. Estrategias de captura (fetch)
19.1.1. Trabajar con asociaciones haraganas
19.1.2. Ajustar las estrategias de captura
19.1.3. Proxies de las asociaciones de un solo extremo
19.1.4. Inicializar colecciones y proxies
19.1.5. Usar la captura por lotes
19.1.6. Usar la captura mediante subselects
19.1.7. Usar la captura de propiedades haragana
19.2. El caché de 2do nivel
19.2.1. Mepeos de caché.
19.2.2. Estrategia de sólo lectura
19.2.3. Estrategia de lecto/escritura
19.2.4. Estrategia de lecto/escritura no estricta
19.2.5. Estrategia transaccional
19.2.6. Compatibilidad entre el proveedor de caché y la estrategia de concurrencia
19.3. Admninistrar los cachés
19.4. El caché de consultas (query cache)
19.5. Comprender la performance de las colecciones
19.5.1. Taxonomía
19.5.2. Las lists, maps, idbags y sets son las colecciones más eficientes de actualizar
19.5.3. Las bags y lists son las colecciones inversas más eficientes
19.5.4. Borrado en una pasada
19.6. Monitorear la performance
19.6.1. Monitorear una SessionFactory
19.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 datos
20.1.2. Ejecutar la herramienta
20.1.3. Propiedades
20.1.4. Usar Ant
20.1.5. Actualizaciones incrementales del esquema de base de datos
20.1.6. Utilizar Ant para las actualizaciones incrementales del esquema de base de datos
20.1.7. Validación del esquema de base de datos
20.1.8. Usar Ant para la validación del esquema de base de datos
21. Ejemplo: Padre/Hijo
21.1. Nota sobre las colecciones
21.2. de-uno-a-muchos bidireccional
21.3. Ciclo de vida de las propagaciones en cascada
21.4. Propagaciones en cascada y unsaved-value
21.5. Conclusión
22. Ejemplo: La aplicación Weblog
22.1. Clases persistentes
22.2. Mapeos de Hibernate
22.3. Código Hibernate
23. Ejemplo: Mapeos varios
23.1. Empleador/Empleado
23.2. Autor/Obra
23.3. Cliente/Orden/Producto
23.4. Ejemplos misceláneos de asociación
23.4.1. Asociación de-uno-a-uno "con tipo"
23.4.2. Ejemplo de clave compuesta
23.4.3. de-muchos-a-muchos con atributo compartido de clave compuesta
23.4.4. Discriminación basada en el contenido
23.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. El término "mapeo objeto/relacional" (ORM por sus siglas en inglés) se refiere a esta técnica de "mapear" la representación de los datos desde un modelo de objetos hacia un modelo de datos relacional, con un esquema de base de datos basado en SQL.

Hibernate no sólo se hace cargo del mapeo de clases Java a las tablas de una base de datos (y de los tipos Java a los tipos de la base de datos), sino que también provee utilidades para consulta y captura de datos, y puede reducir considerablemente el tiempo que, de otra manera, habría que invertir con el manejo manual de datos mediante SQL y JDBC.

La meta de Hibernate es aliviar al programador del 95% de las tareas más comunes relacionadas con persistencia. Probablemente, Hibernate no sea la mejor solución para aplicaciones data-céntricas que tengan casi toda su lógica de negocios en procedimientos almacenados (stored procedures) en la base de datos; es más útil con modelos orientados a objetos cuya lógica de negocio reside en la capa intermedia. Sin embargo, Hibernate puede ayudarlo a encapsular o eliminar código SQL que sea específico de un proveedor de BD, y ayudará en la tarea usual de traducir desde una representación tabular a un gráfico de objetos.

Si usted es nuevo en Hibernate y en lo que respecta al Mapeo objeto/relacional, o incluso nuevo en Java, por favor siga los siguientes pasos:

  1. Lea el siguiente instructivo: Capítulo 1, Introducción a Hibernate, el cual es una especie de manual con instrucciones paso a paso. El código fuente del instructivo está incluido en la distribución descargable, en el directorio doc/reference/tutorial/

  2. Lea Capítulo 2, Arquitectura para entender en qué entornos Hibernate puede ser usado.

  3. Échele un vistazo al directorio eg/ en la distribución de Hibernate. Contiene una simple aplicación autosuficiente. Copie su driver de JDBC al directorio lib/ y edite etc/hibernate.properties, especificando valores correctos para su base de datos. Desde la consola, situado en el directorio de distribución, tipee ant eg (usando Ant), o desde Windows, tipee build eg.

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

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

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

  7. El Área Comunitaria del sitio de web de Hibernate es un buen recurso acerca de patrones de diseño y varias soluciones de integración (Tomcat, JBoss AS, Struts, EJB, etc).

Si tiene preguntas, utilice el foro en el sitio de Hibernate. También proveemos un sistema JIRA de seguimiento de problemas, para reportes de defectos (bugs) y pedidos de mejoras. Si a usted le interesa la programación de Hibernate, únase a la lista de correo de programación de Hibernate. Si le interesa traducir este documento, póngase en contacto con nosotros en la lista de correo de programación.

A través de JBoss Inc, hay disponible soporte para desarrollo comercial y de producción, y entrenamiento para Hibernate. (véase http://www.hibernate.org/SupportTraining/). Hibernate es un componente vital del paquete de productos conocido como "Sistema Empresarial JBoss de Middleware" (JEMS, por sus siglas en inglés).

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 a pasos pequeños, fáciles de comprender. Este instructivo se basa en otro anterior, escrito por Michael Gloegl. Todo el código está contenido en el directorio tutorials/web del código fuente del proyecto.

Importante

Este instructivo sobreentiende que el usuario ya tiene conocimiento de Java y de SQL. Si cualquiera de ambos es nuevo para usted, o no se maneja bien ellos, le recomendamos que comience con una buena introducción a estas tecnologías, antes de adentrarse en Hibernate. A la larga, le ahorrará tiempo y esfuerzo.

Nota

Hay otra aplicación a modo de ejemplo/instructivo en el directorio del código fuente /tutorials/eg. Dicho ejemplo se basa en línea de comandos (consola), y como tal no depende de un contenedor de servlets para poder ser ejecutado. El montaje (setup) básico es el mismo que para las instrucciones a continuación.

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 queremos asistir, e información acerca del anfitrión (o anfitriones) de dichos eventos. Vamos a usar una base de datos residente en memoria, llamada HSQLDB, para evitar describir la instalación y configuración de cualquier base de datos en particular. Siéntase en libertad de usar cualquier base de datos con la que esté familiarizado.

Lo primero que tenemos que hacer es montar nuestro entorno de desarrollo, y, específicamente, instalar todas las dependencias que Hibernate necesita, así como otras bibliotecas (libraries). Hibernate se construye usando Maven, el cual, entre otras cosas, provee manejo de dependencias. Más aún: provee un manejo transitivo de dependencias, lo cual simplemente significa que para usar Hibernate podemos definir nuestras dependencias dentro de él: Hibernate mismo define las dependencias que necesita, las cuales se convierten en "transitivas".

.
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    ...

    <dependencies>
        <dependency>
            <groupId>${groupId}</groupId>

            <artifactId>hibernate-core</artifactId>
        </dependency>

        <!-- Como ésta es una aplicación de web, también necesitamos una dependencia a la API de servlets (servle-api). -->
        <dependency>
            <groupId>javax.servlet</groupId>

            <artifactId>servlet-api</artifactId>
        </dependency>
    </dependencies>

</project>

Nota

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

Consejo

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

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

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. Hibernate puede aceder a los campos directamente; la ventaja de los métodos de acceso es proveer mayor solidez a la hora de refinar ("refactoring") el código. El constructor sin argumentos sí es obligatorio, para poder instanciar el objeto mediante reflexión.

La propiedad identificadora o id contiene un identificador único para un evento en particular. Todas las clases de entidad persistentes (las hay menos importantes también) necesitarán dicho id, si queremos usar a pleno las capacides de Hibernate. De hecho, la mayoría de las aplicaciones (especialmente aplicaciones de web), ya necesitan distinguir objetos por id, así que esta característica debería considerarse una ventaja, más que una limitación. De todos modos, usualmente no manipulamos directamente la identidad de un objeto, así que el método "setter" del id debería ser privado. Sólo Hibernate asigna ids, cuando el objeto es grabado. Hibernate puede acceder a métodos en cualquier nivel de acceso (protected, public, private, etc) directamente. La opción de qué nivel de acceso utilizar es suya, según el diseño de su aplicación.

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

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

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

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 archivo de mapeo. Éste le dice a Hibernate a qué tabla en qué base de datos tiene que acceder, y qué columnas de dicha tabla tiene que utilizar.

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

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

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

Nótese que la DTD de Hibernate es bastante sofisticada. La puede usar para autocompletar elementos de mapeo y atributos de XML en su indetfaz gráfica (IDE). También puede abrir la el archivo de la DTD en su procesador de texto - es la forma más fácil de tener una visión general de todos los elementos, y de ver los valores por defecto, junto con algunos comentatios. Sepa que Hibernate no buscará la DTD en la red, sino en el classpath. La DTD está incluida en el archivo, hibernate3.jar, así como en el directorio src/ de la distribución de Hibernate.

En los ejemplos sucesivos, omitiremos la declaración de la DTD, por brevedad. Por supuesto, ésta no es optativa.

Entre las dos tags hibernate-mapping, incluya un elemento class. Todas las clases de entidad persistente (de nuevo: hay clases dependientes, como veremos luego, que no son entidades de primer nivel) necesitan dicho mapeo, a una tabla en la base de datos SQL.

<hibernate-mapping>

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

    </class>

</hibernate-mapping>

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

<hibernate-mapping>

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

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

</hibernate-mapping>

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

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

<hibernate-mapping>

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

</hibernate-mapping>

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

¿Por qué el mapeo de la propiedad date incluye el atributo column, pero el title no? A falta del atributo column, Hibernate por defecto usa el nombre de la propiedad como nombre de columna. Esto funciona para title. Pero date es una palabra reservada en la mayoría de las base de datoss, así que mejor la mapeamos con un nombre diferente.

Otra cosa interesante, es que el mapeo title también carece del atributo type. Los tipos que usamos en los archivos de mapeo no son, como es de esperarse, tipos Java. Tampoco son tipos SQL. A estos tipos se los llama Tipos de mapeo de Hibernate, conversores que podemos traducir de tipos Java a SQL y viceversa. De nuevo, Hibernate tratará de determinar la conversión adecuada e incluso el tipo mismo, si el atributo type no se especifica en el mapeo. En algunos casos, esta detección automática (que usa Java Reflection) puede no generar el valor por defecto que usted esperaba o necesita. Ése es el caso con la propiedad date. Hibernate no puede saber si la propiedad, que es del tipo java.util.Date, debería ser mapeada a una columna timestamp, o a una columna time. En este caso, mantengamos la información completa (de día y hora) mapeando la propiedad al conversor timestamp.

Este achivo de mapeo debería ser grabado como Event.hbm.xml, justo en el mismo directorio que el archivo Java de la clase Event. El nombre de los archivos de mapeo puede ser arbitrario, pero los sufijos hbm.xml son una convención en la comunidad de programadores de Hibernate. Ahora la estructura de directorios debería verse así:

.
+lib
  <bibliotecase de Hibernate y de terceros>
+src
  +events
    Event.java
    Event.hbm.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 del sitio de web de HSQL DB (http://hsqldb.org/). En ralidad, usted sólo necesita hsqldb.jar de dicha descarga. Coloque este archivo en el directorio lib/ del directorio de desarrollo.

Cree un directorio llamado data en la raíz del directorio de desarrollo - es ahí en donde HSQL DB almacenará sus archivos de datos. Ahora, haga arrancar la base de datos ejecutando: java -classpath ../lib/hsqldb.jar org.hsqldb.Server en este directorio de datos. Usted podrá ver que arranca y está ligada a un socket TPC/IP. Ahí es donde nuestra aplicacíón se conectará luego. Si quiere comenzar con una base de datos nueva en el transcurso de este instructivo, cierre la base de datos HSQL DB pulsando CTRL + C en la ventana, borre todos los archivos en el subdirectorio data/, y arranque HSQL DB nuevamente.

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

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

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

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

<hibernate-configuration>

    <session-factory>

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

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

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

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

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

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

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

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

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

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

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

    </session-factory>

</hibernate-configuration>

Fíjese en que este archivo de configuración XML usa una DTD distinta. Configuramos la SessionFactory de Hibernate - una fabrica global responsable de una base de datos en particular. Si tiene varias base de datos, use varias configuraciones de <session-factory> por lo común en otros tantos archivos de configuración (para un arranque más fácil).

Los primeros 4 elementos property contienen la configuración necesaria para la conexíón JDBC. La propiedad "dialect" especifica la variante de SQL en particular que Hibernate genera. El manejo automático de sesiones por contextos de persistencia será muy útil, como pronto veremos. La opción hbm2ddl.auto activa la generación automática de esquemas - directamente en la BD. Esto por supuesto puede ser desactivado (quitando esta opción) o ser redirigido a un archivo, con la ayuda de la tarea de Ant SchemaExport. Fnalmente, agregamos el o los archivo de mapeo para las clases persistentes a la configuración.

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

1.2.4. Construir con Ant

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

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

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

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

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

    <target name="clean">

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

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

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

    </target>

</project>

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

C:\hibernateTutorial\>ant
Buildfile: build.xml

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

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

BUILD SUCCESSFUL
Total time: 1 second 

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 de código "infraestructural". Tenemos que hacer arrancar Hibernate. Dicho arranque incluye construir un objeto SessionFactory global, y almacenarlo en algún lugar de fácil acceso para el código de la aplicación. Una SessionFactory puede abrir nuevos objetos Session. Cada objeto Session representa una "unidad de trabajo" de un solo Thread. El código de SessionFactory es thread-safe, y es instanciado sólo una vez.

Vamos a crear una clase de ayuda llamada HibernateUtil, que se encargará del arranque y hará que el acceso a SessionFactory sea más conveniente. Veamos cómo implementarla:

package util;

import org.hibernate.*;
import org.hibernate.cfg.*;

public class HibernateUtil {

    private static final SessionFactory sessionFactory;

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

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

}

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

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

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

.
+lib
  <Hibernate y las bibliotecas de terceros>

+src
  +events
    Event.java
    Event.hbm.xml
  +util
    HibernateUtil.java
  hibernate.cfg.xml
+data
build.xml

Esto debería poder compilarse sin problemas. Finalmente, necesitamos configurar un sistema de logueo (bitácora, logging) - Hibernate usa commons logging y le deja a usted la opción entre log4j y el logging específico de Java. La mayoría de los programadores prefiere Log4j. Copie log4j.properties de la distribución de Hibernate (está en el directorio etc/) a su directorio src, al lado de hibernate.cfg.xml. Dele un vistazo a la configuración de ejemplo, y cambie los valores si desea una salida más locuaz. Por defecto, sólo los mensajes de arranque de Hibernate se muestran en la salida estándar.

La parte infraestructural del instructivo ha finalizado. Ahora estamos listos para efectuar verdadero trabajo con Hibernate.

1.2.6. Cargar y almacenar objetos

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

package events;
import org.hibernate.Session;

import java.util.Date;

import util.HibernateUtil;

public class EventManager {

    public static void main(String[] args) {
        EventManager mgr = new EventManager();

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

        HibernateUtil.getSessionFactory().close();
    }

    private void createAndStoreEvent(String title, Date theDate) {

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

        session.beginTransaction();

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

        session.save(theEvent);

        session.getTransaction().commit();
    }

}

Creamos un objeto Event, y se los pasamos a Hibernate. Ahora Hibernate se encarga del SQL, y ejecuta INSERTs en la base de datos. Echémosle un vistazo a la sesión, y al código de manejo de transacciones antes de ejecutarlo.

Usa sesión (Session) es una unidad de trabajo. Por ahora mantendremos todo simple y asumiremos una correspondencia uno-a-uno entre una sesión de Hibernate y una transacción de BD. Para "escudar" nuestro codigo respecto del sistema subyacente de transacciones (en este caso, sólo JDBC, pero podría haber sido JTA), usamos la API para transacciones que está disponible en la clase Session.

¿Qué hace sessionFactory.getCurrentSession()? Primero, a este método se lo puede llamar desde dondequiera, y cuantas veces se desee, una vez que obtenemos una SessionFactory. (fácilmente, gracias a la HibernateUtil). El código getCurrentSession() siempre devuelve la unidad "actual" de trabajo. ¿Recuerda que configuramos una opción con valor "thread" en hibernate.cfg.xml? Debido a esto, la unidad actual de trabajo está ligada al thread de Java que se esté ejecutando en ese momento en su aplicación. De todos modos, ésta no es toda la historia: también hay que considerar el alcance (scope), cuándo una unidad de trabajo empieza y cuándo termina.

Una sesión comienza cuando se la necesita por primera vez, cuando se hace la primera llamada a getCurrentSession(). Entonces, es ligada al thread actual por Hibernate. Cuando la transacción termina, Hibernate desliga la sesión del thread, y la cierra por usted. Si usted llama getCurrentSession() de nuevo, obtiene una nueva sesión y comienza una nueva unidad de trabajo. El modo de programación "ligado a threads" (thread-bound), es la forma más difundida de usar Hibernate, dado que permite una distribución en capas muy flexible: el código de delimitación de transacciones puede separarse del código de acceso a datos, como veremos más adelante.

En relación al alcance de la unidad de trabajo: Una sesión ¿debería usarse para ejecutar una sola operación de base de datos, o varias? El ejemplo precedente usa una sesión para una operación. Esto es simple casualidad, el ejemplo no es lo suficientemente complejo como para demostrar ningún otro enfoque. El alcance de una sesión de Hibernate es flexible, pero nunca se debe designar una aplicación de maneera que utilice una sesión para cada operación de base de datos. Así que, incluso si usted lo ve en algunos pocos de los ejemplos siguientes, considere la práctica de "una sesión por operación" como algo a evitar (un "anti-pattern"). Una aplicación real (de web) se analiza más adelante en este instructivo.

Échele un vistazo al capítulo Capítulo 11, Transacciones y Concurrencia acerca del manejo de transacciones y su delimitación. También hemos salteado cualquier manejo de errores en el ejemplo precedente.

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

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

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

</target>

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

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

Después de la compilación, usted debería ver que que Hibernate arracna, y, dependiendo de su configuración, un montón de salida de logueo. Al final encontrará la siguiente línea:

[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)

Ése es el código INSERT ejecutado por Hibernate. Los signos de interrogación representan parámetros JDBC ligados. Para ver los valores de dichos parámetros, o para reducir la locuacidad del archivo de log, revise su archivo log4j.properties.

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

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

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

private List listEvents() {

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

    session.beginTransaction();

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

    session.getTransaction().commit();

    return result;
}

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

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

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

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

Si usted ahora invocara Ant con -Daction=list, debería ver los eventos que haya almacenado hasta ese moento. Por supuesto, puede también invocar la acción store un par de veces más.

Nota: A esta altura, la mayoría de los usuarios de Hibernate experimenta problemas, y aparecen seguido mensajes del tipo Table not found . De todos modos, si usted sigue los pasos que acabamos de describir cuidadosamente, no tendrá este problema, ya que hbm2ddl crea el esquema de base de datos la primera vez, y las veces subsiguientes en que la aplicación recomienza utilizan dicho esquema. Si usted en algún momento cambia algo del mapeo o del esquema, debe rehabilitar hbm2dll nuevamente para recrear la BD.

1.3. Parte 2 - Mapear asociaciones

Hemos mapeado una clase persistente a una tabla. Ahora, partiendo de esta base, agreguemos algunas asociaciones de clase. Primero, agregaremos algunas personas a nuestra aplicación, y almacenaremos una lista de eventos en los cuales participan.

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 los eventos tienen participantes. Las cuestiones de diseño con las que tenemos que lidiar son: multiplicidad, direccionalidad, y comportamiento 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 para una determinada persona sin tener que ejecutar una consulta explícita, simplemente llamando aPerson.getEvents(). Usamos una colección de Java, un Set, porque esta colección no contendrá elementos duplicados, y el orden no es relevante para nosotros.

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

public class Person {

    private Set events = new HashSet();

    public Set getEvents() {
        return events;
    }

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

Antes de mapear esta asociación, piense en el otro lado (los eventos). Claramente, podemos simplemente mantener esta asociación unidireccional. O si no, podríamos crear otra colección en la clase Event, si deseamos navegar en forma bidireccional, es decir, anEvent.getParticipants(). Esto no es estrictamente necesario, desde un punto de vista funcional, siempre se puede ejecutar una consulta explícita para obtener los participamntes de un determinado evento. Esta es una opción de diseño que le dejamos a usted; pero lo que sacamos en limpio de esta discusión, es la multiplicidad de la asociación: hay "muchos valores" desde ambos lados. A esto se lo llama una asociación "de-muchos-a-muchos" (many-to-many). Por esto, usamos el mapeo many-to-many de Hibernate.

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

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

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

</class>

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

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

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

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 las colecciones. Como puede usted ver, no hay llamados explícitos a update() o a save(), Hibernate detecta automáticamente que la colección ha sido modificada y necesita ser actualizada. Esto se llama "dirty checking automático", y usted puede experimentar con ello cmabiando la propiedad "date" en cualquiera de sus objetos. Siempre y cuando se mantengan en un estado persistente (es decir, ligados a una sesión de Hibernate, por haber sido cargados o grabados en una unidad de trabajo), Hibernate monitoreará cualquier cambio y ejecutará SQL entre bambalinas. El proceso de sincronizar el estado de lo que está en memoria con la base de datos, generalmente al final de la unidad de trabajo, se denomina "nivelar, desagotar" la sesión (en inglés, flush). En nuestro código, la unidad de trabajo termina con un "commit" (o "rollback") de la transacción de base de datos, tal como se define por la opción de configuración thread para la clase CurrentSessionContext.

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

private void addPersonToEvent(Long personId, Long eventId) {

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

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

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

    session.getTransaction().commit();

    // fin de la primera unidad de trabajo

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

    // comienzo de la segunda unidad de trabajo

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

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

    session2.getTransaction().commit();
}

El llamado a update convierte de nuevo en persistente un objeto que estaba desprendido. Se podría decir que lo liga a una nueva unidad de trabajo, de manera que cualquier modificación que se le hubiera hecho mientras estaba desprendido pueda ser grabada en la base de datos. Esto incluye cualquier modificación que se le hubiera hecho a la colección de ese objeto entidad.

Bueno, esto no es muy útil en la situación actual, pero es un concepto importante, que usted puede aplicar al diseño de su propia aplicación. Por ahora, simplemente complete este ejercicio agregando una nueva acción al método main() de EventManager, e invóquelo desde la línea de comandos. Si necesita los identificadores de una persona y de un evento, el método save() los devuelve (tal vez usted deba modificar alguno de los métodos previos, para que devuelvan dicho identificador).

else if (args[0].equals("addpersontoevent")) {
    Long eventId = mgr.createAndStoreEvent("My Event", new Date());
    Long personId = mgr.createAndStorePerson("Foo", "Bar");
    mgr.addPersonToEvent(personId, eventId);
    System.out.println("Added person " + personId + " to event " + eventId);
}

Éste fue un ejemplo de una asociación entre dos clases igualmente importantes, dos "entidades". Como se mencionó anteriormente, hay otras clases y tipos "menos importantes" en un modelo típico. Algunos, usted ya los ha visto, como los int o String. A esas clases las llamamos value types, y sus instancias dependen de una entidad en particular. Las instancias de estos tipos no tienen su propia identidad, ni se pueden compartir entre entidades (dos personas no pueden, por ejemplo, hacer referencia al mismo objeto firstname o primer nombre, incluso si son tocayos). Por supuesto, estos value types pueden ser encontrados no solamente en la JDK. De hecho, en una aplicación Hibernate todas las clases JDK son consideradas "value types", pero usted puede escribir sus propias clases dependientes, para representar una dirección o una cantidad de dinero, por ejemplo.

También se puede diseñar una colección de "value types". Esto es conceptualmente bien distinto de una colección de referencias a otras entidades, pero en el código Java ambas se ven casi igual.

1.3.4. Colección de valores

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

private Set emailAddresses = new HashSet();

public Set getEmailAddresses() {
    return emailAddresses;
}

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

El mapeo de este Set:

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

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

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

Echémosle un vistazo al nuevo esquema de DB

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

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

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

private void addEmailToPerson(Long personId, String emailAddress) {

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

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

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

    session.getTransaction().commit();
}

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

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 ambos lados en Java. Desde luego, el esquema de base de datos no cambia, todavía tenemos una multiplicidad de-muchos-a-muchos (many-to-many). Una base de datos relacional es más flexible que un lenguaje de programación, no necesita cosas como una "dirección de navegación", los datos pueden ser adquiridos de cualquier manera posible.

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

private Set participants = new HashSet();

public Set getParticipants() {
    return participants;
}

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

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

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

Como puede ver, éstos son mapeos set normales en ambos documentos de mapeo. Fíjese en que los nombres de columna en ambos documentos han sido alternados. La adición más importante es el atributo inverse="true" en el elemento set del mapeo de la colección de Event.

Lo que esto significa, es que Hibernate debería rferirse al otro lado (el lado "Person"), cuando necesite obtener información sobre el vínculo entre los dos. Esto será más fácil de entender una vez que veamos cómo se crea el vínculo bidireccional entre las dos entidades.

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 un vínculo entre personas y eventos en el ejemplo unidireccional? Agregando una instancia de Event a la colección de referencias contenida en una instancia de Person. Así que, obviamente, si queremos que este vínculo funcione también en sentido inverso, tenemos que hacer lo mismo del otro lado: agregar una referencia a Person a la colección en Event. Esto de "establecer el vínculo de los dos lados" es absolutamente necesario, y usted jamás debe olvidarse de hacerlo.

Muchos programadores trabajan "defensivamente", y crean métodos facilitadores que asignan correctamente los vínculos a ambos lados. Por ejemplo, en la clase Person:

protected Set getEvents() {
    return events;
}

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

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

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

Fíjese en que los métodos "get" y "et" de la colección ahora son "protected" - esto les permite a las clases en el mismo paquete, y a las subclases, acceder a los métodos, pero les impide a todos los demás inmiscuirse con estas colecciones directamente (bueno... casi). Usted debería, probablemente, hacer lo mismo con las colecciones del otro lado.

Y ¿qué hay del atributo de mapeo inverse? Para usted (y para Java), un vínculo bidrecciolal es simplemente cuestión de asignar correctamente las referencias en ambos lados .Hibernate, sin embargo, no tiene información suficiente como para organizar adecuadamente sus llamados a comandos SQL INSERT y UPDATE. Marcar uno de los lados de la asociación como inverse, básicamente le dice a Hibernate que lo ignore, que lo considere como un espejo de lo que ocurre en el otro lado. Eso es todo lo que se necesita para que Hibernate resuelva todos los problemas que derivan de transformar un modelo de navegación bidireccional en un esquema de base de datos. Las reglas que usted denbe recordar son son simples: Toda asociación bidireccional necesita que uno de sus lados sea "inverse". En una asociación de-uno-a-muchos (one-to-many) tiene que ser el lado "many". En una asociación de-muchos-a-muchos (many to many), elija cualquier lado, da lo mismo.

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 vimos anteriormente. Sin embargo, es conveniente usar algunos patrones de programación ("patterns"). Escribamos un servlet EventManagerServlet que pueda listar los eventos alamacenados en la base de datos, y provea un formulario HTML para ingresar nuevos eventos.

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 mediante el primer llamado a getCurrentSession() de la SessionFactory. Entonces, ss inicia una transacción de base de datos (todo el código de acceso a datos ocurre dentro de la transacción, no importa si para lectura o escritura; en las aplicaciones no usamos auto-commit).

Nunca cree una nueva sesión de Hibernate para cada operación de base de datos. Use una sesión de Hibernate a lo largo de toda la request, es decir, que tenga un "alcance" de toda la sesión. Use getCurrentSession(), de manera que quede automáticamente ligada al Thread actual.

A continuación, las acciones posibles de la request son procesadas, y es generada la respuesta HTML. Enseguida nos dedicaremos a esa parte.

Finalmente, la "unidad de trabajo" finaliza, cuando el procesamiento y presentación hayan sido completados. Si ocurrió algún problema durante este procesamiento o presentación, se lanzará una excepción y la se ejecutará un "rollback" de la transacción. Esto cubre el "pattern" session-per-request. En lugar de escribir código para demarcar la transacción en cada request, usted debería crea un Servlet Filter. Refiérase al sitio de web de Hibernate y a la Wiki para más información acerca de este "pattern", llamado Open Session in View u OSIV, algo así como "apertura de una sesión por cada vista". Este patrón se necesitará en cuanto usted considere presentar sus "vistas" usando JSP en lugar de servlets.

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ágina
printEventForm(out);
listEvents(out, dateFormatter);

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

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

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

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

private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) {

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

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

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

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

Eso es todo. El servlet está completo. Una request al servlet será procesada en una única sesión y transacción. Tal como ocurrió en el ejemplo anterior, Hibernate puede ligar estos objetos al Thread actual de ejecución. Esto le da a usted la libertad de acceder a la SessionFactory de cualquier manera que guste. Normalmente, usted usaría un diseño más sofisticado, y movería el código de acceso a datos (DAO, por sus siglas en inglés) a otra "capa". Refiérase a la Wiki de Hibernate para más ejemplos.

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 archivo build.xml:.

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

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

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

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

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

    <servlet>
        <servlet-name>Event Manager</servlet-name>
        <servlet-class>events.EventManagerServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Event Manager</servlet-name>
        <url-pattern>/eventmanager</url-pattern>
    </servlet-mapping>

</web-app>

Antes de compilar y desplegar, como ésta es una aplicación de web, note que se necesita una biblioteca adicional : jsdk.jar. Es el kit de herramientas o "developer kit" para los servlets de Java. Si aún no tiene esta biblioteca, descárguela del sitio de web de Sun y colóquela en su directorio de bibliotecas (lib). De todos modos, se la usa para compilar solamente, no se incluye en los paquetes WAR.

Para construir (build) y desplegar (deploy), invoque el comando ant war situado en el directorio de su proyecto, y luego copie el archivo hibernate-tutorial.war en el directorio webapp de Tomcat. Si no tiene Tomcat instalado, descárguelo y siga las instrucciones. No es necesario que cambie nada en la configuración de Tomcat para que este ejemplo ande.

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

1.5. Sumario

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

Si ya va tomando confianza con Hibernate, continúe hojeando la tabla de contenidos en la documentación de referencia, buscando temas que le interesen. Los más solicitados son el procesamiento de transacciones (Capítulo 11, Transacciones y Concurrencia), la performance de las capturas de datos o "fetch performance" (Capítulo 19, Mejorar la performance), el uso de las API (interfaces de programación) (Capítulo 10, Trabajar con objetos) y las características de las consultas (Sección 10.4, “Consultar”).

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

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

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

SessionFactory (org.hibernate.SessionFactory)

Un caché thread-safe e inmutable de mapeos, compilados para una base de datos en particular. Una fábrica (factory) de sesiones, y cliente de ConnectionProvider. Puede contener un caché optativo (llamado "de 2do nivel") que es reusable entre transacciones, a nivel de proceso o de cluster.

Session (org.hibernate.Session)

Un objeto de thread simple y de corta visa, que representa una conversación entre la aplicación y el repositorio persistente. Envuelve a una fábrica de conexiones JDBC por transacción. Contiene un caché obligatorio (llamado "de primer nivel") de objetos persistentes, que se usa al navegar el árbol de objetos, o cuando se buscan los objetos por identificador.

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

Objetos y colecciones transitorios y desprendidos

Instancias de clases persistenteses que, por el momento, no están asociadas con una sesión. Pudieron haber sido instanciadas por la aplicacíón, y aún no haber sido asociadas con una sesión, o bien haber sido instanciadas por una sesión que en ese momento esté cerrada.

Transacción (org.hibernate.Transaction)

(Optativo) un objeto de un solo thread y corta vida, usado por la aplicacíon para especificar unidades atómicas de trabajo. Abstrae a la aplicación de la transacción JDBC, JTA o CORBA subyacente. Una sesíón puede extenderse a lo largo de varias transacciones en algunos casos. ¡Pero, sea como sea, el código para demarcar transacciones (ya sea utilizando APIs subyancentes o la interfaz Transaction) nunca es optativo!

ConnectionProvider (org.hibernate.connection.ConnectionProvider)

(Optativo) Una fábrica y repositorio (pool) de conexiones JDBC. Abstrae la aplicación de la fuente de datos (Datasource) o del gerente de driver (DriverManager) subyacentes. No está expuesto directamente a la aplicación, pero puede ser implementado o extendido por el programador.

TransactionFactory (org.hibernate.TransactionFactory)

(Optativo) Una fábrica de instancias de Transaction. No está expuesta directamente a la aplicación, pero puede ser implementada o extendida por el programador.

Interfaces de extensión

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

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

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 a un 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 clave primaria) y, tal vez, un valor correspondiente en la base de datos. Para un contexto de persistencia determinado, Hibernate garantiza que la identidad persistente equivale a la "identidad Java" (ubicación en memoria del objeto).

desprendida (detached)

La instancia estuvo alguna vez asociada con un contexto de persistencia, pero dicho contexto está cerrado, o la instancia ha sido serializada a otro proceso. Tiene una identidad persistente y, tal vez, el correspondiente registro en la base de datos. Hibernate no ofrece ninguna garantía acerca de la relación entre identida persistente e identidad Java.

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 servicio JMX estándar. Nosotros proveemos una implementación de MBean en la distribución: org.hibernate.jmx.HibernateService.

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

  • Manejo de sesiones: El ciclo de vida de una sesión de Hibernate puede ser automáticamente ligado al alcance (scope) de una transacción JTA. Esto significa que usted ya no necesita abrir y cerrar manualmente las sesiones, de esto se encarga el interceptor de JBoss. Tampoco debe preocuparse ya por demarcar la transacción en su código (a menos que quiera escribir usted mismo una capa de persistencia portátil, use la API Transaction de Hibernate para eso). Puede llamar a HibernateContext para acceder a una sesión.

  • Despliegue del Archivo de Hibernate (HAR por sus siglas en inglés): Normalmente se despliega el servicio JMX de Hibernate usando el "descriptor de despliegue" (deployment descriptor), en un archivo EAR o SAR, el cual soporta las opciones de configuración de una SessionFactory de Hibernate. De todos modos, para esto aún se debe nombrar a todos los archivos de mapeo en el descriptor de despliegue. En cambio, si decide usar el despliegue HAR, JBoss detecta automáticamente todos los archivos de mapeo en su archivo HAR.

Consulte la guía del usuario del servidor de aplicaciones JBoss para mayor información acerca de estas opciones.

Otra característica disponible en forma de servicio JMX son las estadísticas de Hibernate en tiempo de ejecución. Vea la Sección 3.4.6, “Estadísticas de Hibernate”

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, pero note 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ón determinada esté en efecto a lo largo del "scope" o alacance de un contexto determinado. De todos modos, entre las distintas aplicaciones, la definición de lo que constituye un "contexto" suele diferir, así como la definición de qué constituye el contexto "actual". Las aplicaciones que usaban Hibernate antes de la versión a 3.0, tendían a utilizar o bien versiones "caseras" de sesión contextual (basadas en ThreadLocal) o bien utilizaban frameworks de terceras partes (como Spring o Pico), los cuales proveían sesiones contextuales basadas en proxies e intercepción.

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

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

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

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

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

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

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

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

Capítulo 3. Configuración

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

3.1. Configuración programática

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3.2. Obtener una SessionFactory

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

SessionFactory sessions = cfg.buildSessionFactory();

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

3.3. Conexiones JDBC

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

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

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

Para que esto funcione, necesitamos pasarle algunas propiedades de conexión JDBC a Hibernate. Todos los nombres y semántica de las propiedades Hibernate están definidos en la clase org.hibernate.cfg.Environment. Vamos a describir los valores más importantes para configurar la conexión JDBC.

Hibernate obtendrá las conexiones (y las administrará en un "pool") usando un java.sql.DriverManager si usted configura las siguientes propiedades:

Table 3.1. Hibernate JDBC Properties

Nombre de la propiedad Propósito
hibernate.connection.driver_class clase del driver JDBC
hibernate.connection.url URL de JDBC
hibernate.connection.username usuario de la base de datos
hibernate.connection.password clave del usuario de la base de datos
hibernate.connection.pool_size número máximo de conexiones en el "pool"

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

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

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

hibernate.connection.driver_class = org.postgresql.Driver
hibernate.connection.url = jdbc:postgresql://localhost/mydatabase
hibernate.connection.username = myuser
hibernate.connection.password = secret
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statements=50
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect

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

Table 3.2. Propiedades para fuente de datos de Hibernate

Nombre de la propiedad Propósito
hibernate.connection.datasource nombre JNDI de la fuente de datos
hibernate.jndi.url URL del proveedor de JNDI (optativo)
hibernate.jndi.class clase del InitialContextFactory de JNDI (optativo)
hibernate.connection.username usuario de la base de datos (optativo)
hibernate.connection.password clave del usuario de base de datos (optativo)

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

hibernate.connection.datasource = java:/comp/env/jdbc/test
hibernate.transaction.factory_class = org.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = org.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect

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

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

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

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 sistema sólo se les puede asignar valores via java -Dproperty=value o hibernate.properties. No se les puede asignar valores usando las técnicas descritas anteriormente.

Tabla 3.3. Porpiedades de Configuration de Hibernate

Nombre de la propiedad Propósito
hibernate.dialect El nombre de clase de un org.hibernate.dialect.Dialect que le permita a Hibernate generar SQL optimizado para una BD relacional en particular.

por ejemplo classname.completo.del.dialecto

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

hibernate.show_sql Escribir todos los comandos SQL en la consola. Ésta es una alternativa a asignar el valor "debug" a la categoría de logueo org.hibernate.SQL.

valores posibles true | false

hibernate.format_sql Darle un formato lindo al SQL generado en los logs o consola.

valores posibles true | false

hibernate.default_schema Al generar SQL, calificar los nombres de tabla con el esquema o "tablespace" dado.

por ejemplo NOMBRE_DEL_ESQUEMA

hibernate.default_catalog En el SQL generado, calificar los nombres de tablas con el catálogo dado.

por ejemplo. NOMBRE_DEL_CATÁLOGO

hibernate.session_factory_name La org.hibernate.SessionFactory será vinculada automáticamente a este nombre en JNDI luego de que haya sido creada.

por ejemplo. jndi/nombre/compuesto

hibernate.max_fetch_depth Les asigna una "profundidad máxima" a los outer joins que sean generados por aosciaciones "de extremo único" (single-ended). Un 0 anula la captura por defecto en los outer joins.

valores posibles valor recomendado: entre 0 y 3

hibernate.default_batch_fetch_size Le asigna un valor por defecto a la captura por lotes (batch fectching) en asociaciones.

valores posibles valores recomendados: 4, 8, 16

hibernate.default_entity_mode Asigna un método por defecto para la representación de entidades en todas las sesiones que sean abiertas por esta SessionFactory

valores posibles dynamic-map, dom4j, pojo

hibernate.order_updates Fuerza a Hibernate a ordenar los UPDATEs SQL por la clave primaria de los items que sean actualizados. Esto resultará en menos deadlocks de transacciones en los sistemas altamente concurrentes.

valores posibles true | false

hibernate.generate_statistics Hibernate irá juntando estadísticas útiles para el ajuste de performance.

valores posibles true | false

hibernate.use_identifier_rollback Si se habilita, los identificadores generados serán reinicializados a valores por defecto una vez que los objetos sean borrados.

valores posibles true | false

hibernate.use_sql_comments Si se activa, Hibernate generará comentatrios dentro del SQL, para facilitar el debug. Por defecto, este valor es false.

valores posibles true | false


Tabla 3.4. Propiedades Hibernate de JDBC y Conexión

Nombre de la propiedad Propósito
hibernate.jdbc.fetch_size Un valor distinto de 0 determina el tamaño de la captura o "fetch" JDBC (llama a Statement.setFetchSize()).
hibernate.jdbc.batch_size Un valor distinto de 0 habilita el uso de actualizacionesn en lotes (batch updates) de JDBC2 por parte de Hibernate.

valores posibles se recomienda entre 5 y 30

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

valores posibles true | false

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

por ejemplo classname.of.BatcherFactory

hibernate.jdbc.use_scrollable_resultset Habilita el uso de resultados JDBC2 navegables (scrollable resultsets) por parte de Hibernate. Esta propiedad sólo es necesaria cuando se emplean conexiones JDBC provistas por el usuario. De otro modo, Hibernate usa los metadatos de la conexión.

valores posibles true | false

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

valores posibles true | false

hibernate.jdbc.use_get_generated_keys Habilita el uso del PreparedStatement.getGeneratedKeys() de JDBC3 para capturar claves generadas en forma nativa luego de insertar. Requiere que a Requires JDBC3+ driver y a JRE1.4+, le sea asignado "false" si el driver que usted está usando tiene problemas con los generadores de identificadores. Por defecto, intenta determinar las capacidades del driver usando los metadatos de conexión.

valores posibles true|false

hibernate.connection.provider_class El nombre de clase de un org.hibernate.connection.ConnectionProvider hecho a medida que le provea conexiones JDBC a Hibernate.

por ejemplo classname.of.ConnectionProvider

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

por ejemplo 1, 2, 4, 8

hibernate.connection.autocommit Habilita la autocomisión (autocommit) para las conexiones JDBC en pool (no se recomienda).

valors posibles true | false

hibernate.connection.release_mode Especifica cuándo Hibernate debería liberar conexiones. Por defecto, una conexión JDBC es retenida hasta que la sesión es cerrada explícitamente o desconectada. Para fuentes de datos JTA en un servidor de aplicaciones, se debería usar after_statement para liberar conexiones agresivamente luego de cada llamado a JDBC. Para conexiones que no sean JTA, a menudo tiene sentido liberar las conexiones al final de cada transacción, usando after_transaction. auto elegirá after_statement para las estrategias JTA y CMT (manejadas por el contenedor), y after_transaction para las estrategias transaccionales JDBC.

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

Note que este valor sólo afecta sesiones devueltas por SessionFactory.openSession. Para las que se hayan obtenido usando SessionFactory.getCurrentSession, lo que importa para determinar el modo de liberación de conexiones, es la implementación que se haya configurado de CurrentSessionContext. Vea la Sección 2.5, “Sesiones Contextuales”

hibernate.connection.<nombreDeLaPropiedad> Le pasa el valor de propiedad JDBC <propertyName> a DriverManager.getConnection().
hibernate.jndi.<nombreDeLaPropiedad> Le pasa la propiedad <nombreDeLaPropiedad> a la InitialContextFactory de JNDI.

Tabla 3.5. Propiedades del caché de Hibernate

Nombre de la propiedad Propósito
hibernate.cache.provider_class El nombre de clase de un CacheProvider hecha a medida.

por ejemplo classname.of.CacheProvider

hibernate.cache.use_minimal_puts Optimiza el caché de 2do nivel para minimizar escrituras, al costo de realizar lecturas más frecuentemente. Esta configuración es más útil con cachés en "cluster", y en Hibernate3 se habilita por defecto con cachés en cluster.

valores posibles true|false

hibernate.cache.use_query_cache Habilita el caché de consultas. Cada consulta (query) debe ser individualmente 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 un mapeo <cache>

valores posibles true|false

hibernate.cache.query_cache_factory El nombre de una interfaz QueryCache personalizada, que por defecto es StandardQueryCache, la cual ya viene de fábrica.

por ejemplo classname.of.QueryCache

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 un formato 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 con Hibernate (por defecto, JDBCTransactionFactory).

por ejemplo classname.of.TransactionFactory

jta.UserTransaction El nombre JNDI usado por JTATransactionFactory para obtener una UserTransaction JTA del servidor de aplicaciones.

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 usa el 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. Es preferible usar el manejo de transacciones que ya viene incorporado, y el manejo automático de contexto de sesiones; ver laSección 2.5, “Sesiones contextuales”.

valores posibles true | false

hibernate.transaction.auto_close_session Si está habilitado, la sesión será automáticamente cerrada durante la fase de compleción de la transacción. Es preferible usar el manejo de transacciones que ya viene incorporado, y el manejo automático de contexto de sesiones; ver la Sección 2.5, “Sesiones contextuales”.

valores posibles 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 las opciones que ya vienen incorporadas.

valores posibles jta | thread | managed | la clase personalizada

hibernate.query.factory_class Elige la implementación del "revisor" (parser) HQL.

valores posibles org.hibernate.hql.ast.ASTQueryTranslatorFactory o org.hibernate.hql.classic.ClassicQueryTranslatorFactory

hibernate.query.substitutions Mapeo (sustitución) de símbolos en las consultas de Hibernate a símbolos de SQL. (Dichos símbolos pueden ser funciones o nombres literales, por ejemplo).

por ejemplo hqlLiteral=SQL_LITERAL, hqlFunction=SQLFUNC

hibernate.hbm2ddl.auto Exporta o valida automáticamente el esquema DDL a la base de datos cuando la SessionFactory es creada. Con create-drop, el esquema de base de datos será borrado cuando la SessionFactory se cierre explícitamente.

valores posibles validate | update | create | create-drop

hibernate.cglib.use_reflection_optimizer Habilita el uso de CGLIB en lugar de reflexión en tiempo de ejecución. Es una propiedad "a nivel de sistema". La reflexión también puede ser usada para el análisis y resoulción de problemas (troubleshooting). Note que Hibernate siempre requiere CGLIB, incluso si el optimizador ha sido inhabilitado. Esta propiedad no se puede asignar en el archivo hibernate.cfg.xml.

valores posibles true | false


3.4.1. Dialectos de SQL

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

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

Base de datos relacional Dialecto
DB2 org.hibernate.dialect.DB2Dialect
DB2 AS/400 org.hibernate.dialect.DB2400Dialect
DB2 OS390 org.hibernate.dialect.DB2390Dialect
PostgreSQL org.hibernate.dialect.PostgreSQLDialect
MySQL org.hibernate.dialect.MySQLDialect
MySQL with InnoDB org.hibernate.dialect.MySQLInnoDBDialect
MySQL with MyISAM org.hibernate.dialect.MySQLMyISAMDialect
Oracle (any version) org.hibernate.dialect.OracleDialect
Oracle 9i/10g org.hibernate.dialect.Oracle9Dialect
Sybase org.hibernate.dialect.SybaseDialect
Sybase Anywhere org.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Server org.hibernate.dialect.SQLServerDialect
SAP DB org.hibernate.dialect.SAPDBDialect
Informix org.hibernate.dialect.InformixDialect
HypersonicSQL org.hibernate.dialect.HSQLDialect
Ingres org.hibernate.dialect.IngresDialect
Progress org.hibernate.dialect.ProgressDialect
Mckoi SQL org.hibernate.dialect.MckoiDialect
Interbase org.hibernate.dialect.InterbaseDialect
Pointbase org.hibernate.dialect.PointbaseDialect
FrontBase org.hibernate.dialect.FrontbaseDialect
Firebird org.hibernate.dialect.FirebirdDialect

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á la performance, limitando la cantidad de viajes de ida y vuelta a la BD (probablemente a costa de un mayor trabajo efectuado por la base de datos misma). La captura por outer joins permite que todo un árbol (graph) de ojetos conectados por asociaciones de-uno-a-muchos, de-muchos-a-uno, de-muchos-a-muchos, y de-uno-a-uno sea capturado de una sola vez, con un solo comando SQL SELECT.

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

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

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 desea usar instancias grandes de tipos binary o serializable, debería habilitar hibernate.jdbc.use_streams_for_binary. Ésta es una propiedad configurable a nivel de sistema solamente.

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) de proceso 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. Por ejemplo:

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

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

hibernate.query.substitutions toLowercase=LOWER

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

3.4.6. Estadísticas de Hibernate

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

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 por sus siglas en inglés), para loguear varios eventos de sistema. SLF4J puede dirigir la salida de su actividad de logueo a varios "frameworks" de logueo: NOP, Simple, log4j 1.2, JDK 1.4, JCL o logback), dependiendo de la vinculación elegida. Para configurar el logueo adecuadamente, necesitará slf4j-api.jar en su classpath, así como el archivo de jar de su preferencia (en el caso de log4j, slf4j-log4j12.jar). Vea la documentación de SLF4J para más detalles. Para usar log4j también necesitará colocar un archivo log4j.properties en su classpath. Un archivo de propiedades a modo de ejemplo viene distribuido con Hibernate en el directorio src/.

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

Table 3.9. Hibernate Log Categories

Category (categoría) Función
org.hibernate.SQL Loguea todo el "lenguaje de modificación de datos" (DML) de SQL a medida que éste es ejecutado.
org.hibernate.type Loguea todos los parámetros JDBC
org.hibernate.tool.hbm2ddl Loguea todos los comandos SQL de definición de datos (DDL) que hayan sido ejecutados
org.hibernate.pretty Loguea el estado de todas las entidades (con un máximo de 20) asociadas con una sesión al momento de aplicarle "flush".
org.hibernate.cache Loguea toda la actividad del caché de 2do nivel
org.hibernate.transaction Loguea toda actividad relacionada con transacciones
org.hibernate.jdbc Loguea toda adquisición de recursos JDBC
org.hibernate.hql.ast.AST Loguea todo AST de HQL y SQL durante la revisión (parsing) de las consultas
org.hibernate.secure Loguea todas las autorizaciones para requests JAAS
org.hibernate Loguea todo (un montón de información, pero muy útil a para detectar y solucionar problemas).

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

3.6. Implementar una NamingStrategy

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

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

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

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

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

3.7. Archivo de configuración XML

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

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

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

<hibernate-configuration>

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

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

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

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

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

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

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

    </session-factory>

</hibernate-configuration>

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

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

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

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

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

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 el contenedor (container-mamaged), provistas a través de JNDI. Usualmente, un TransactionManager compatible con JTA y un ResourceManager se hacen cargo del manejo de transacciones (CMT), especialmente transcciones distribuidas a lo largo de varias fuentes de datos. Por supuesto, usted también puede demarcar los límites de sus transacciones programáticamente (BMT) o usar la API de transacciones de Hibernate (Transaction) para mantener su código portable.

  • Enlace JNDI automático: Hibernate puede ligar su SessionFactory a JNDI luego del arranque.

  • Enlace de sesión JTA: La sesión de Hibernate puede ser automáticamente ligada al alcance o "scope" de las transacciones JTA. Simplemente obtenga la SessionFactory de JNID (haciendo un "lookup"), y de ahí obtenga la sesión actual. Deje que Hibernate se haga cargo de aplicarle "flush" y de cerrar la sesión cuando la transacción JTA se haya terminado. La demarcación de transacciones es o bien declarativa (CMT) o bien programática (BMT/UserTransaction).

  • Despliegue JMX: Si usted cuenta con un servidor de aplicaciones habilitado para JMX, (por ejemplo, JBoss), puede elegir desplegar Hibernate como un "Managed MBean" Esto le ahorra el códgo de arranque de una línea que construye la SessionFactory a partir de Configuration. El container hará arrancar su HibernateService, e, idealmente, también se hará cargo de las dependencias de servicios (la fuente de datos tiene que estar disponible antes de que Hibernate arranque, etc).

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

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 arquitectura de su sistema. Si usted deja que Hibernate use JDBC directamente a través de un "pool" de conexiones, usted puede comenzar y terminar sus transacciones llamando a la API de JDBC. Si usted ejecuta su aplicación en un servidor de aplicaciones J2EE, es posible que prefiera usar transaccioes manejadas por bean (bean-managed) e invocar la API de JTA y UserTransaction según haga falta.

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

Éstas son las tres opciones estándar, que ya vienen incluidas "de fábrica":

org.hibernate.transaction.JDBCTransactionFactory

delega a las transacciones JDBC de la base de datos (es el valor por defecto)

org.hibernate.transaction.JTATransactionFactory

delega a la transacción manejada por el contenedor (container-managed) si hay una tranascción en proceso en este contexto (por ejemplo, un método de un Session EJB), en caso contrario, crea una nueva transción, y se utilizan transacciones manejadas por bean.

org.hibernate.transaction.CMTTransactionFactory

delega a transacciones JTA manejadas por el contenedor

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

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

Tabla 3.10. TransactionManagers de JTA

Fábrica de transacciones Servidor de aplicaciones
org.hibernate.transaction.JBossTransactionManagerLookup JBoss
org.hibernate.transaction.WeblogicTransactionManagerLookup Weblogic
org.hibernate.transaction.WebSphereTransactionManagerLookup WebSphere
org.hibernate.transaction.WebSphereExtendedJTATransactionLookup WebSphere 6
org.hibernate.transaction.OrionTransactionManagerLookup Orion
org.hibernate.transaction.ResinTransactionManagerLookup Resin
org.hibernate.transaction.JOTMTransactionManagerLookup JOTM
org.hibernate.transaction.JOnASTransactionManagerLookup JOnAS
org.hibernate.transaction.JRun4TransactionManagerLookup JRun4
org.hibernate.transaction.BESTransactionManagerLookup Borland ES

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 nuevas sesiones. Note que esto no se relaciona con las Datasource, también ligadas a JNDI, ¡ambas simplemente comparten el mismo repositorio de registro (registry)!

Si usted desea mantener la fábrica de sesiones ligada a un "espacio de nombre" o "namespace" JNDI, especifique un nombre (por ejemplo, java:hibernate/SessionFactory) usando la propiedad hibernate.session_factory_name. Si esta propiedad es omitida, la fábrica de sesiones no quedará ligada a JNDI. (Esto es especialmente útil en entornos que por defecto tienen una JNDI de sólo lectura, como por ejemplo Tomcat).

Cuando de liga una fábrica de sesiones a JNDI, Hibernate empleará los valores de hibernate.jndi.url y hibernate.jndi.class para instanciar un contexto inicial. Si no son especificadas, se usa el InitialContext por defecto.

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

Si usted usa una fábrica de sesiones JNDI, un EJB o cualquier otra clase puede obtenerla usando el JNDI lookup.

Le recomendamos que, en un entorno administrado, use JNDI para obtener sus fábricas de sesiones, y en cualquier otro entorno, un singleton estático. Para escudar su aplicación de estos detalles, también le recomendamos que oculte el código que efectivamente realiza el "lookup" dentro de una clase utilitaria, (como por ejemplo, HibernateUtil.getSessionFactory()). Note que dicha clase es también una buena manera de hacer arrancar Hibernate (capítulo 1).

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ón actual". Vea la discusión en Sesiones actuales. Usando el contexto de sesión "jta", si no hay ninguna sesión de Hibernate asociada con la transacción JTA actual, se creará una y se la asociará con la transacción la primera vez que sessionFactory.getCurrentSession() sea invocada. A las sesiones que hayan sido creadas de esta manera, les será automáticamente efectuado el "flush" antes de que la transacción finalice, serán cerradas después de que la transacción finalice, y las conexiones JDBC serán agresivamente liberadas después de cada comando. Esto permite que las sesiones sean manejadas por el ciclo de vida de la transacción JTA al cual están asociadas, manteniendo al código libre de ese tipo de preocupaciones administrativas. Su código puede utilizar o bien JTA programáticamente a través de UserTransaction, o (lo que más recomendamos para producir código portable) usar la API de Transaction de Hibernate para establecer los límites de la transacción. Si su aplicación se está ejecutando en un contenedor de EJB, se prefiere utilizar demarcación declarativa de transacciones con CMT (siglas en inglés de "transacciones manejadas por el contenedor").

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 en JNDI. Esto se puede lograr o bien con un bloque inicializador estático en una clase utilitaria (como el que está en HibernateUtil) o bien desplegando Hibernate como un "servicio administrado" (managed service),

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

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

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

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

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

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

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

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

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

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

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

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

    <!-- archivos de mapeo -->
    <attribute name="MapResources">auction/Item.hbm.xml,auction/Category.hbm.xml</attribute>

</mbean>

</server>

Este archivo es desplegado en un directorio llamado META-INF, y empaquetado en un archivo JAR con la extensión .sar (service archive o "archivo de servicio). Sus EJB (usualmente session beans) pueden ser conservados en su propio archivo JAR, pero este archivo JAR de EJB se puede incluir en el archivo principal de servicio para obtener una única desplegable "en caliente" (hot deployment). Consulte la documentación del servidor de aplicaciones JBoss para más información acerca del servicio JMX y el despliegue de EJB.

Capítulo 4. Clases Persistentes

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

Hibernate trabaja mejor si estas clases siguen un formato muy simple, conocido como el modelo de programación de "objeto Java liso y llano" o POJO (Plain Old Java Object) por sus siglas en inglés. Sin embargo, ninguna de estas reglas es estrictamente obligatoria. Mas aún, Hibernate3 presupone muy poco acerca de la naturaleza de sus objetos persistentes. Un modelo de dominio (domain model) se puede expresar de otras maneras: mediante árboles de instancias de Map, por ejemplo.

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) {
        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 puede no ser público) de manera qu Hibernate pueda instanciarlas usando Constructor.newInstance(). Recomendamos fuertemente un constructor por defecto, que tenga al menos visibilidad package para la generación del "proxy" en Hibernate.

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. La propiedad puede llamarse como sea, y su tipo puede ser cualquiera de los tipos de dato primitivos, cualquier tipo "envoltorio" (wrapper), java.lang.String, o java.util.Date. Si su base de datos anticuada tiene claves compuestas, usted puede usar inclusive una clase definida por el usuario con propiedades de los tipos mencionados (vea la sección sobre claves compuestas más adelante).

La propiedad indentificadora es estrictamente optativa. La puede omitir e Hibernate aún podrá seguirle la pista al objeto, internamente. De todos modos, no le recomendamos que haga esto.

De hecho, algunas de las funcoionalidades están disponibles sólo para aquellas clases que sí tienen identificador.

Le recomendamos que declare propiedades indentificador nombradas de una manera consistente, en sus clases persistentes. Mas aún, le recomendamos que use un tipo anulable (es decir, no primitivo).

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 de que sea la implementación de una interfaz con todos sus métodos públicos.

Se puede persistir clases finales que no implementen una interfaz con Hibernate, pero usted no será capaz de usar proxies para la captura por asociaciones perezosas (lazy association fetching), lo cual limitará sus opciones de ajuste de performance.

También se debería evitar declarar métodos public final en las clases no finales. Si se quiere usar clases con métodos públicos finales, se debe inhabilitar el "proxying" explícitamente, especificando lazy="false".

4.1.4. Declare métodos de acceso y "mutadores" (accessors, mutators) para los campos persistentes.

Cat declara métodos de acceso para todos sus campos persistentes. Muchos otras herramientas de ORM persisten directamente sus variables de instancia. Nosotros creemos que es mejor proveer un nivel de aislamiento entre el esquema relacional y la estructura interna de datos de la clase. Por defecto, Hibernate persiste propiedades del tipo JavaBean, y reconoce nombres de método de la forma getAlgo, isAlgo y setAlgo. Si es necesario, usted puede revertir esto, y permitir el acceso directo, para propiedades específicas,

No se necesita que las propiedaes sean declaradas como públicas. Hibernate puede persistir una propiedad con un par get/set que tenga acceso por defecto (package) o privado.

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 cualquier asociación con un lado "muchos"), y

  • planea usar recuperación (reattachment) de instancias desprendidas

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

La manera más obvia es implementar equals()/hashCode() comparando el valor identificador de ambos objetos. Si el valor es el mismo, ambos deben ser el mismo registro de la base de datos, siendo por lo tanto iguales (si ambos son agregados al Set., sólo tendremos un elemento en el Set.). ¡Desafortunadamente, no podemos utilizar ese enfoque con los identificadores generados! Hibernate sólo asignará identificadores los objetos que hayan sido creados, y una instancia que haya sido recientemente creada ¡aún no tiene ningún identificador! Más aún, si una de estas instancias, sin grabar, está dentro de un Set., grabarla le va a asignar un identificador, y sus valores de equals() y hashCode() (que estarían basados en el identificador) cambiarían, rompiendo el contrato del Set. Vea el sitio de web de Hibernate para una discusión completa de este problema. Note que esto no es un problema de Hibernate, sino parte de la semántica normal de Java en lo que respecta a la igualdad e identidad de los objetos.

Nosotro recomendamos implementar equals() y hashCode() utilizando una igualdad "por clave de negocio". Esto quiere decir, que estos métodos comparen solamente propiedades que formen la clave de negocio, una clave que identificaría a nuestro objeto en el mundo real (una clave candidata natural).

public class Cat {

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

        final Cat cat = (Cat) other;

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

        return true;
    }

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

}

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

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 u objetos JavaBean. Hibernate también soporta modelos dinámicos (usando Maps de Maps en tiempo de ejecución), y la representación de entidades como árboles DOM4J. Con este enfoque, no se escriben clases, simplemente archivos de mapeo.

Por defecto, Hibernate trabaja en el modo POJO normal. Se puede configurar un modo de representación de entidad por defecto para una fábrica de sesiones en particular, usando la opción de configuración default_entity_mode (vea Tabla 3.3, “Propiedades de Configuración de Hibernate”.

Los ejemplos siguientes demuestran la representación usando mapas. Primero, en el archicvo de mapeo, se debe declara un entity-name en lugar de (o además de) el nombre de la clase:

<hibernate-mapping>

    <class entity-name="Customer">

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

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

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

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

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

    </class>


</hibernate-mapping>

Note que, incluso si las asociaciones son declaradas usando nombres de clases de destino, el tipo de destino de una asociación también puede ser una entidad dinámica en lugar de un POJO.

Después de configurar el modo de entidad por defecto a dynamic-map para esta SessionFactory, podemos trabajar con "mapas de mapas" en tiempo de ejecución.

Session s = openSession();
Transaction tx = s.beginTransaction();
Session s = openSession();

// Crear un cliente
Map david = new HashMap();
david.put("name", "David");

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

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

// Grabar a ambos
s.save("Customer", david);
s.save("Organization", foobar);

tx.commit();
s.close();

La ventaja de un mapeo dinámico es acelerar el tiempo de entrega, para prototipos, sin la necesidad de implementar clases de entidades. De todos modos, se pierde el chequeo de tipos en tiempo de compilación, y esto muy probablemente causará varias excepciones en tiempo de ejecución. Gracias al mapeo de Hibernate, el esquema de base de datos puede ser fácilmente normalizado y saneado, permitiendo agregar una implementación apropiada de modelo de dominio encima, más adelante.

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

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

// Crea un cliente
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continúa con la sesión "POJO"

Dese cuenta por favor de que el llamado a getSession() usando un EntityMode determinado, es cosa de la API de Session API, no de SessionFactory. De esta manera, la nueva sesión comparte la misma conexión JDBC subyacente, y otra información de contexto. Esto vuelve innecesario invocar flush() y close() en la sesión secundaria, y le deja el manejo de transacciones y conexiones a unidad de trabajo primaria.

Se puede encontrar más información sobre las capacidades de representación con XML en Capítulo 18, Mapeo XML.

4.5. T-uplizadores

org.hibernate.tuple.Tuplizer, y sus sub-interfaces, son los encargados de manejar una representación en particular de un fragmento de datos, dado el org.hibernate.EntityMode de dicha representación. Si un determinado fragmento de datos se concibe como una estructura de datos, entonces un t-uplizador es lo que sabe cómo crear dicha estructura. Por ejemplo, para el modo de entidad "POJO", el t-uplizador correspondiente sabe cómo crear el POJO mediante su constructor, y cómo acceder a las propiedades de POJO usando los métodos de acceso. Hay dos t-uplizadores de alto nivel, representados por las interfaces org.hibernate.tuple.entity.EntityTuplizer y org.hibernate.tuple.component.ComponentTuplizer. Los EntityTuplizers see encargan de manejar los "contratos" recién descritos para las entidades, mientras que los ComponentTuplizers hacen lo propio para los componentes.

El usuario puede, asimismo, insertar sus propios t-uplizadores. Tal vez usted desee que par el modo de entidad "dynamic-map" se utilice una implementación de java.util.Map que no sea java.util.HashMap, o tal vez usted necesita que se utilice una estrategia de generación de proxies distinta de la que viene de fábrica. Las definiciones de t-uplizer van adjuntas a la entidad o mapeo de componentes que están destinadas a manejar. Volviendo al ejemplo de la entidad "cliente" (customer).

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

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

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

        <!-- otras propiedades-->

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


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

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

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

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 sido diseñado para ser legible y editable a mano. El lenguaje de mapeo es "Javacéntrico", lo cual significa que los mapeos se construyen en torno a declaraciones de clases persistentes, no de tablas.

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

Comencemos con un mapeo de ejemplo:

<?xml version="1.0"?>

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

<hibernate-mapping package="eg">

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

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

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

                <property name="weight"/>

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

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

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

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

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

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

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

        </class>

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

</hibernate-mapping>


(N.del T): "eg" son las siglas de "exempli gratia", una locución latina que en inglés hace las veces de "por ejemplo". A lo largo de esta documentación, "eg" se usa como el paquete por defecto para los ejemplos de código.
Ahora discutiremos el contenido del documente de mapeo. Sólo describiremos los elementos y atributos del documento que son usados por Hibernate en tiempo de ejecución. El documento de mapeo también contiene algunos atributos optativos adicionales, y elementos que afectan los esquemas de BD exportados por herramientas de exportación de esquemas (por ejemplo, el atributo not-null).

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 URL mencionada, en el directorio hibernate-x.x.x/src/org/hibernate o en hibernate3.jar. Hibernate siempre buscará la DTD primero en el classpath. Si experimenta problemas al buscar la DTD debido a su conexión de Internet, compare su declaración de DTD contra el contenido de su classpath.

5.1.1.1. EntityResolver

Como se mencionó anteriormente, Hibernate primero intentará resolver la DTD en su classpath. La manera en que lo hace, es registrando una implementación personalizada de org.xml.sax.EntityResolver con el SAXReader que usa para leer los archivos xml. Este EntityResolver perosnalizado reconoce dos espacios de nombre de systemId diferentes.

  • un "espacio de nombre" (namespace) de Hibernate es reconocido siempre que el resolver encuentra un systemId qie comience con http://hibernate.sourceforge.net/; el resolver intenta resolver estas entidades mediante el classloader que haya cargado las clases de Hibernate.

  • un espacia de nombre de usuario se reconoce siempre que el resolver encuentra un systemId que use un protocolo de URL classpath://; el resolver intentará resolver esas entidades a través de (1) el classloader del contexto de thread actual, y (2), el classloader que haya cargado las clases de Hibernate.

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

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [
    <!ENTITY types SYSTEM "classpath://your/domain/types.xml">

]>

<hibernate-mapping package="your.domain">
    <class name="MyEntity">
        <id name="id" type="my-custom-id-type">
            ...
        </id>
    <class>

    &types;
</hibernate-mapping>

En donde types.xml es un recurso en el paquete your.domain y contiene una definición de tipo a medida typedef.

5.1.2. hibernate-mapping

Este elemento tiene varios atributos optativos. Los atributos schema y catalog especifican que las tablas a las que este mapeo se refiere pertenecen al esquema o catálogo indicado. Si se especifican, los nombres de tabla serán calificados con el esquema y/o catálogo dados. Si no están presentes, los nombres de tabla no serán calificados. El atributo default-cascade especifica qué estilo de propagación en cascada debería asumirse para las propiedades y colecciones que no especifiquen un atributo cascade. El atributo auto-import nos permite, por defecto, usar un nombre de clase no calificado en el lenguaje de consultas.

<hibernate-mapping
         schema="schemaName"                          (1)
         catalog="catalogName"                        (2)
         default-cascade="cascade_style"              (3)
         default-access="field|property|ClassName"    (4)
         default-lazy="true|false"                    (5)

         auto-import="true|false"                     (6)
         package="package.name"                       (7)
 />
(1)

schema (optativo): El nombre de un esquema de BD

(2)

catalog (optativo): El nombre de un catálogo de BD

(3)

default-cascade (optativo - por defecto, none): El estilo por defecto de propagación en cascada

(4)

default-access (optativo - por defecto, property): La estrategia que Hibernate debería usar para acceder a todas las propiedades. Puede ser una implementacíón personalizada de PropertyAccessor.

(5)

default-lazy (optativo - por defecto, true): El valor por defecto para los atributos lazy no especificados de clases y colecciones.

(6)

auto-import (optativo - por defecto true): Especifica si se puede usar nombres de clase no calificados (de clases en el mapeo) el en lenguaje de consultas.

(7)

package (optativo): Especifica un prefijo de paquete a ser asumido, para las clases no calificadas en el documento de mapeo.

Si usted tiene dos clases persistentes cuyo nombre (no calificado) es el mismo, debería configurar auto-import="false". Hibernate lanzará una excepción si usted le intenta asignar dos clases al mismo nombre "imported".

Note que el elemento hibernate-mapping le permite anidar los mapeos de varias clases persistentes, como se muestra anteriormente. Sin embargo, mapear sólo una clase persistente (o jerarquía de clases) por archivo de mapeo es una costumbre más establecida (y lo que algunas herramientas esperan). Por ejemplo, Cat.hbm.xml, Dog.hbm.xml, o, si se usa herencia, Animal.hbm.xml.

5.1.3. class

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 subclases individuales, usado para comportamiento polimórfico. null y not null también son valores aceptables.

(4)

mutable (optativo, por defecto, true): Especifica si las instancias de esta clase son o no mutables.

(5)

schema (optativo): Suplanta al nombre de esquema especificado por el elemento <hibernate-mapping> raíz.

(6)

catalog (optativo): Suplanta al nombre de catálogo especificado por el elemento <hibernate-mapping> raíz.

(7)

proxy (optativo): Especifica una interfaz a utilizar para los proxies de inicialización perezosa. Se puede especificar el nombre de la clase misma.

(8)

dynamic-update (optativo, por defecto false): Especifica que los comandos SQL UPDATE deberían ser generados en tiempo de ejecución, y contener sólo aquéllas comlumnas cuyos valores hayan cambiado.

(9)

dynamic-insert (optativo, por defecto, false): Especifica que se deberían generar comandos SQL INSERT en tiempo de ejecución, conteniendo sólo las columnas cuyos valores son no nulos.

(10)

select-before-update (optativo, por defecto false): Especifica que Hibernate nunca debería ejecutar un UPDATE SQL a menos que esté seguro de que en realidad se está modificando algún objeto. En algunos casos (en realidad, sólo cuando un objeto transitorio ha sido asociado con una nueva sesión usando update()), esto significa que Hibernate ejecutará un comando SQLSELECT adicional, para determinar si un en realidad un UPDATE es necesario.

(11)

polymorphism (optativo, por defecto, implicit): Determina si se usa polimorfismo explícito o implícito.

(12)

where (optativo) especifica una condición SQL WHERE arbitraria a ser usada cuando se seleccionen objetos de esta clase.

(13)

persister (optativo): Especifica un ClassPersister hecho a medida.

(14)

batch-size (optativo, por defecto, 1) especifica un "tamaño de lote" para capturar instancias de esta clase por identificador.

(15)

optimistic-lock (optativo, por defecto, version): Determina la estrategia de "locking" optimista.

(16)

lazy (optativo): La captura haragana puede ser completamente inhabilitada espefificando lazy="false".

(17)

entity-name (optativo, por defecto, el nombre de la clase): Hibernate3 permite que una clase sea mapeada muchas veces (potencialmente, a distintas tablas), y permite mapeos de entidades qie estén representados por Maps a nivel de Java, En estos casos, se debería proveer un nombre arbitrario explícito para la entidad. Vea Sección 4.4, “Modelos dinámicos” y Capítulo 18, Mapeo XML for more information.

(18)

check (optativo): Una expresión SQL arbitraria usada para generar una constraint tipo check multifila a usar durante la generación automática del esquema.

(19)

rowid (optativo): Hibernate puede usar los vulgarmente llamados ROWIDs, en aquellas DB que lo soporten. Por ejemplo, en Oracle, Hibernate puede usar la columna adicional rowid para actualizar más rapidamente, si usted configura esta opción a rowid. Un ROWID es un detalle de implementación, y representa la ubicación física de una t-upla alamacenada.

(20)

subselect (optativo): Mapea una entidad inmutable y de sólo-lectura a un subselect de la base de datos. Es útil si se quiere tener una vista en lugar de una tabla, pero no se cuenta con una vista en la base de datos. Vea más adelante para más información.

(21)

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

Es perfectamente aceptable que la clase persistente nombrada sea une interfaz. En ese caso, se debe declarar la clase implementadora usando el elemento <subclass>. Se puede persistir cualquier clase anidada (inner class) estática. Se debería especificar el nombre de este tipo de clases usando la nomenclatura estándar, es decir com.Mi$ClaseInterna.

Las clases inmutables (mutable="false") no pueden ser actualizadas o borradas por la aplicación. Esto le permite a Hibernate realizar algunas optimizaciones menores.

El atributo optativo proxy permite la inicialización perezosa de instancias persistentes de la clase. Inicialmente, Hibernate devolverá proxies CGLIB, que implementan la interfaz mencionada. El objeto persistente propiamente dicjo será cargado cuando se invoque un método del proxy. Vea "Inicializar colecciones y proxies" más adelante.

Polimorfismo implícito significa que cualquier consulta que nombre a una superclas o interfaz devolverá instancias de la clase misma. Polimorfismo explícito significa que instancias de esta clase sólo serán devueltas por consulta que nombren a esta clase, y que las consultas que nombren a esta clase devolverán sólo instancias de subclases que hayan sido mapeados como <subclass> o <joined-subclass>. Para la mayoría de los casos, el valor por defecto polymorphism="implicit", es lo más apropiado. El polimorfismo explícito es útil cuando hay dos clases diferentes mapeadas a la misma tabla (lo cual permite tener una clase "de peso ligero" que contenga un subconjunto de las columnas de la tabla).

El atributo persister le permite personalizar la estrategia de persistencia usada para la clase. Por ejemplo, usted puede especificar su propia subclase de org.hibernate.persister.EntityPersister, o hasta puede proveer una implementación completamente nueva de la interfaz org.hibernate.persister.ClassPersister que persista, por ejemplo, valiéndose de una llamada a un procedimiento almacenado de base de datos, o usando serialización a archivos planos, o LDAP. Vea org.hibernate.test.CustomPersister para un ejemplo simple (de "persistencia" a una Hashtable).

Note que las asignaciones de valores dynamic-update y dynamic-insert no son heredadas por la subclases, así que pueden ser especificadas en los elementos <subclass> o <joined-subclass>. Estos valores pueden incrementar la performance en algunos casos, pero reducirla en otros. Úselos con cuidado.

El uso de select-before-update normalmente bajará la performance. Es muy útil para evitar que un "update trigger" de la base de datos sea invocado innecesariamente si usted está revinculando todo un árbol de instancias desprendidas a una sesión.

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

  • version verifica las columnas de versión/timestamp

  • all verifica todas las columnas

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

  • none no usa "locking" optimista

Recomendamos muy fuertemente que se utilicen las columnas de versión/timestamp para locking optimista con Hibernate. Ésta es la estrategia óptima en lo que a performace se refiere, y es la única estrategia que maneja correctamente las modificaciones que se hagan a instancias desprendidas (es decir, cuando se use Session.merge() ).

Para un mapeo Hibernate, no hay diferencia entre una vista y una tabla de la base de datos. Como es de esperarse, esto es transparente a nivel de base de datos (note que algunas base de datos no soportan vistas correctamente, especialmente actualizaciones de éstas). A veces usted quiere usar una vista, pero no puede crear una en la base de datos (por ejemplo, con un esquema heredado anticuado). En este caso, usted puede mapear una entidad inmutable y de sólo-lectura a una expresión "subselect" de SQL.

<class name="Summary">

    <subselect>
        select item.name, max(bid.amount), count(*)
        from item
        join bid on bid.item_id = item.id
        group by item.name
    </subselect>
    <synchronize table="item"/>
    <synchronize table="bid"/>
    <id name="name"/>
    ...

</class>

Declara las tablas contra las cuales se ha de sincronizar esta entidad, asegurándose de que el auto-flush ocurra correctamente. y que las consultas efectuadas contra la entidad derivada no devuelvan datos vencidos. En el mapeo, <subselect> está disponible como atributo y como elemento anidado.

5.1.4. id

Las clases mapeadas deben declarar la columna de clave primaria de la tabla en la base de datos. La mayoría de las clases también tendrán una propiedad del estilo JabaBeans conteniendo el didentificador único de una instancia determinada. El elemento <id> define el mapeo de esa propiedad a la columna de clave primaria.

<id
        name="propertyName"                                          (1)
        type="typename"                                              (2)
        column="column_name"                                         (3)
        unsaved-value="null|any|none|undefined|id_value"             (4)
        access="field|property|ClassName">                           (5)

        node="element-name|@attribute-name|element/@attribute|."

        <generator class="generatorClass"/>
</id>
(1)

name (optativo): El nombre de la propiedad indentificadora.

(2)

type (optativo): Un nombre que indica el tipo de Hibernate

(3)

column (optativo, por defecto, el nombre de la propiedad): El nombre de la columna de la clave primaria

(4)

unsaved-value (optativo, por defecto, un valor "adecuado"): Una propiedad identficadora que indica que una instancia ha sido recientemente instanciada (no grabada), distinguiéndola de las instancias desprendidas que fueron grabadas o cargadas en una sesión previa.

(5)

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

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

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

Hay una declaración alternativa de <composite-id>, para permitur el acceso a datos de formato anticuado, con clave compuesta. Desaconsejamos su uso en cualquier otro caso.

5.1.4.1. Generator

El elemento opcional hijo <generator> nombra a la clase de Java que se usa para generar los identificadores únicos de la clase persistente. Si hace falta algún parámetro para inicializar la instancia del generador, se pasa usando el elemento <param>.

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

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

Todos los generadores implementan la interfaz org.hibernate.id.IdentifierGenerator. Es una interfaz muy simple; algunas aplicaciones podrían elegir proveer su propia implementación a medida. De todos modos, Hibernate provee una variedad de implementaciones que ya vienen incluidas. Los siguientes son los apodos mnemónicos para estos generadores ya incluidos:

increment

genera identificadores de tipo long, short o int que son únicos solamente cuando ningún otro proceso está insertando datos en la misma tabla. No usar en un cluster.

identity

soporta "columnas de identidad" (identity) en DB2, MySQL, MS SQL Server, Sybase e HypersonicSQL. El identificador que se devuelve es de tipo long, short o int.

sequence

usa una secuencia (sequence) en DB2, PostgreSQL, Oracle, SAP DB, McKoi o un generador en Interbase. El identificador que se devuelve es de tipo long, short o int

hilo

usa un algoritmo hi/lo para generar identificadores de los tipos long, short o int eficientemente, dada una tabla y una columna (por defecto, hibernate_unique_key y next_hi respectivamente) como fuente de los valores "hi". El algorithmo hi/lo genera identificadores que son únicos sólo para una base de datos en particular.

seqhilo

usa un algoritmo hi/lo para generar identificadores de los tipos long, short o int eficientemente, dada una secuencia (sequence) nombrada de base de datos.

uuid

usa un algoritmo UUID de 128 bits para generar identificadores de tipo string, únicos para toda una red (se usa la dirección de IP). El UUID es codificado como una cadena de 32 dígitos hexadecimales.

guid

usa una cadena GUID generada por la base de datos, en MS SQL Server y MySQL.

native

elije identity, sequence o hilo, dependiendo de las capacidades de la base de datos subyancente.

assigned

deja que sea la aplicación la que asigne el identificador al objeto antes de que save() sea llamado. Ésta es la estrategia por defecto si no se especifica un elemento <generator>.

select

obtiene una clave primaria asignada por un trigger de la base de datos, seleccionando la fila por alguna clave única y obteniendo el valor de clave primaria.

foreign

usa el identificador de algún otro objeto asociado. Normalmente se usa en conjunción con una asocoación <one-to-one> por clave primaria.

sequence-identity

una generación de secuencias especializada que utiliza una sequencia de base de datos para la generación del valor en sí, pero lo combina con el método de JDBC3 getGeneratedKeys para devolver el valor final, como parte del comando INSERT. Esta estrategia sólo es soportada, que sepamos, por los drivers de Oracle 10g diseñados para JDK1.4. Note que los comentarios en estos comandos están inhabilitados, debido a un error en los drivers de Oracle.

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ún para generar identificadores. La primera implementación requiere una tabla de base de datos especial que almacene el siguiente valor "hi" disponible. La segunda, usa una secuencia al estilo de Oracle (si esto se soporta).

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

                <param name="column">next_value</param>
                <param name="max_lo">100</param>
        </generator>
</id>
<id name="id" type="long" column="cat_id">
        <generator class="seqhilo">

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

Desafortunadamente, usted no puede usar hilo cuando le provee su propia conexión (Connection) a Hibernate. Cuando Hibernate use una fuente de datos de un servidor de aplicaciones para obtener conexiones inscriptas en JTA, usted deberá configurar adecuadamente la hibernate.transaction.manager_lookup_class.

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

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 claves identity. Con las que soportan secuencias (DB2, Oracle, PostgreSQL, Interbase, McKoi, SAP DB) se puede usar el valor sequence. Ambas estrategias requieren el uso de 2 comandos SQL para insertar un objeto nuevo.

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

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

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

        <generator class="identity"/>

</id>

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

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 del objeto. Este generador se usa cuando la clave primaria es una clave natural en lugar de una clave sustituta (surrogate key). Éste es el comportamiento por defecto, si no se especifica el elemento <generator>.

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

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, como clave natural, y una clave primaria en la tabla llamada person_id, cuyo valor es generado por un trigger que seleciona dicho número.

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 la generación de identificadores. El primer aspecto, es la portabilida de base de datos, el segundo es la optimización (no tener que consultar a la base de datos cad vez que se requiera un valor de identificador nuevo). Estos dos nuevos generadores (a partir de la versión 3.3.x) han sido concebidos con el objetivo de reemplazar a algunos de los generadores nombrados anteriormente De todos modos, siguen incluyéndose en los lanzamientos actuales, y se puede hacer referencia a ellos via FQN.

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

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

  • initial_value (optativo, por defecto, 1): El valor inicial a ser devuelto por la tabla/secuencia. En términos de la creación de la secuencia, esto es análogo a la cláusula típica "STARTS WITH".

  • increment_size (optativo, por defecto, 1): El incremento usdo en llamadas ulteriores a la tabla/secuencia. En términos de la creación de la secuencia, esto es análogo a la cláusula típica "INCREMENT BY".

  • force_table_use (optativo, por defecto, false): Deberíamos forzar el uso de una tabla como estructura de respaldo, incluso si el dialecto soporta secuencias?

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

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

El segundo de estos nuevos generadores es org.hibernate.id.enhanced.TableGenerator, el cual ha sido primariamente concebido en reemplazo del generador table, (aunque, en realidad, funciona mucho más como un org.hibernate.id.MultipleHiLoPerTableGenerator). Secundariamente, ha sido concebido como una reimplementación de org.hibernate.id.MultipleHiLoPerTableGenerator que utiliza la noción de optimizadores "enchufables" (pluggable). En esencia, este generador define una tabla que es capaz de contener múltiples valores de incremento distintos, usando múltiples registros con claves diferentes. Este generador tiene varios parámetros de configuración:

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

  • value_column_name (optativo, por defecto, next_val): El nombre de la columna en la tabla, que será usada para contener el valor.

  • segment_column_name (optativo, por defecto, sequence_name): El nombre de la columna en la tabla que setá usada para contener la "clave de segmento". Este es el valor que identifica de manera distintiva qué valor de incremento usar.

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

  • segment_value_length (optativo, por defecto, 255): Usado para la generaciónde esquemas; el tamaño del campo de esta "clave de segmento".

  • initial_value (optativo, por defecto, 1): El valor inicial a ser devuelto por la tabla.

  • increment_size (optativo, por defecto, 1): El valor por el cual las llamdas subsiguientes a la tabla deberían diferir.

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

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 las veces en que se necesita generar un nuevo valor de identificador. En lugar de ello, lo ideal es agrupar un buen número de ellos en la memoria, y solamente acudir a la base de datos cuando se ese grupo de valores se haya agotado. Ésta es la función de los "optimizadores enchufables". Por el momento sólo los dos generadoeres mejorados (Sección 5.1.5, “Generadores mejorados de identificadores ”) soportan esta noción.

  • none (éste es, generalmente, el valor por defecto si no se especificó ningún optimizador): Esto indica que no se efectúe ninguna optimización, y que se acuda a la base de datos para todos y cada uno de los pedidos.

  • hilo: aplica un algoritmo hi/lo en torno a los valores devueltos por la base de datos. Se espera que los valores de base de datos para este optmizador sean secuenciales. Los valores devueltos desde la estructura de base de datos para este optmizador indican el "número de grupo"; el increment_size (tamaño del incremento) se multiplica por el valor en memoria para definir un grupo "hi value".

  • pooled: como fue discutido para hilo, estos optimizadores intentan minimizar el número de viajes a la base de datos. Aquí, sin embargo, simplemente almacenamos en valor inicial para el "próximo grupo" en la estructura de base de datos, más que un valor secuencial en combinación con un algoritmo de agrupamiento en memoria.Aquí, increment_size se refiere a los valores que vienen de la base de datos.

5.1.7. composite-id

<composite-id
        name="propertyName"
        class="ClassName"
        mapped="true|false"
        access="field|property|ClassName">

        node="element-name|."

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

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

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

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

Desafortunadamente, este abordaje de los identificadores significa que el objeto persistente es su propio identificador. No hay otro "puntero" al objeto que no sea el objeto mismo. Se debe instanciar el objeto persistente mismo, y poblarle sus propiedades identificadores antes de poder cargarlo en estado persistente asociado con una clave primaria. A este enfoque lo llamamos "identificador compuesto incrustado (embedded)", y lo desaconsejamos para cualquier aplicación seria.

El segundo abordaje es lo que llamamos "identificador compuesto mapeado", en donde las propiedades identificador usadas dentro del <composite-id> son duplicadas tanto en la clase persistente como en una clase identificadora separada.

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

(N.del.T): "Medicare" es el nombre en inglés del seguro estatal de salud en EE.UU. y otros países.
En este ejemplo, tanto la clase del identificador compuesto, MedicareId, como la clase de la entidad misma tienen propiedades llamadas medicareNumber y dependent. La clase identificadora debe reemplazar equals() y hashCode() e implementar Serializable. La desventaja de este abordaje es obvia: duplicación de código.

Los siguientes atributos son usados para especificar un identificador compuesto mapeado:

  • mapped (optativo, por defecto, false): indica que se usa un identificador mapeado compuesto, y que los mapeos contenidos de propiedades se refieren tanto a la clase entidad como a la clase del identificador compuesto.

  • class (optativo, pero requerido para un identificador compuesto mapeado): La clase usada como identificador compuesto.

Vamos a describir una tercera manera, aún más conveniente de enfocar el tema de los modificadores compuestos, en donde el identificador compuesto es implementado como una clase componente en Sección 8.4, “Componentes como identificadores compuestos”. Los atributos descritos a continuación se aplican sólo a este enfoque alternativo:

  • name (optativo, pero obligatorio para este abordaje): Una propiedad de tipo "componente" que contiene el identificador compuesto (ver capítulo 9).

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

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

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

5.1.8. discriminator (discriminador)

El elemento <discriminator> se requiere para la persistencia polimórfica que use la estrategia de mapeo de "una tabla por cada jerarquía de clases" y declara una columna como el "discriminador" de la tabla. La columna "discriminador" contiene un valor rótulo, que le dice a la capa de persistencia qué clase debe instanciar para cada registro en particular. Sólo un número restringido de tipos puede ser usado: string, character, integer, byte, short, boolean, yes_no, true_false.

<discriminator
        column="discriminator_column"                      (1)
        type="discriminator_type"                          (2)
        force="true|false"                                 (3)
        insert="true|false"                                (4)
        formula="arbitrary sql expression"                 (5)

/>
(1)

column (optativo, por defecto, class) el nombre de la columna del discriminador

(2)

type (optativo, por defecto, string) un nombre que indica el tipo de Hibernate

(3)

force (optativo, por defecto, false) "fuerza" a Hibernate a especificar valores permitidos de discriminador cuando captura todas las instancias de la clase raíz.

(4)

insert (optativo, por defecto, true) asígnele false si su columna de discriminador tambíén forma parte de un identificador compuesto mapeado (le dice a Hibernate que no incluya a esta columna en el INSERT).

(5)

formula (optativo) una expresión SQL arbitraria que se ejecuta cuando un tipo tiene que ser evaluado. Permite discriminación basada en contenido.

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

El atributo force solamente es útil si la tabla contiene valores de discriminador "adicionales" que no están mapeados a una clase persistente. Éste no es casi nunca el caso.

Usando el atributo formula, se puede declarar una expresión SQL arbitraria que puede ser usada para evaluar el tipo de una fila.

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

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 si se planea usar transacciones largas (véase más abajo).

<version
        column="version_column"                                      (1)
        name="propertyName"                                          (2)

        type="typename"                                              (3)
        access="field|property|ClassName"                            (4)
        unsaved-value="null|negative|undefined"                      (5)
        generated="never|always"                                     (6)
        insert="true|false"                                          (7)

        node="element-name|@attribute-name|element/@attribute|."
/>
(1)

column (optativo, por defecto, el nombre de la propiedad): El nombre de la columna que contiene el número de versión.

(2)

name: El nombre de una propiedad de la clase persistente.

(3)

type (optativo, por defecto, integer): El tipo del número de versión.

(4)

access (optativo, por defecto, property): La estrategia que Hibernate debería usar para obtener el valor de la propiedad.

(5)

unsaved-value (optativo, por defecto, undefined): Una propiedad de versión, que indica que una instancia ha sido recientemente instanciada (y aún no grabada, "transitoria"), distinguiéndola de una instancia desprendida, que ua hubiere sido cargada o grabada por una sesión previa (undefined especifica que debería usarse el valor de la propiedad indentificadora).

(6)

generated (optativo, por defecto, never): Especifica que esta propiedad de versión es en realidad generada por la base de datos. Véase la discusión de propiedades generadas.

(7)

insert (optativo, por defecto, true): Especifica si la columna versión debería ser incluida en los comandos INSERT. Puede adoptar el valor false sólo si la columna de base de datos es definida con un valor por defecto de 0.

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

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

5.1.10. timestamp (optativo)

El elemento optativo <timestamp> indica que la tabla contiene datos marcados con fecha y hora. Esto apunta a ser una alternativa a asignar números de versión. Las timestamps son, por naturaleza, una implementación menos segura de "locking" optimista. De todos modos, la aplicación podría usar timestamps de otras formas.

<timestamp
        column="timestamp_column"                                    (1)
        name="propertyName"                                          (2)
        access="field|property|ClassName"                            (3)
        unsaved-value="null|undefined"                               (4)
        source="vm|db"                                               (5)

        generated="never|always"                                     (6)
        node="element-name|@attribute-name|element/@attribute|."
/>
(1)

column (optativo, por defecto, the property name): el nombre de una columna que contiene la timestamp.

(2)

name: El nombre de una propiedad estilo JavaBeans del tipo Date o Timestamp de la clase persistente.

(3)

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

(4)

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

(5)

source (optativo, por defecto, vm): ¿De dónde debería Hibernate obtener el valor de la timestamp? ¿De la base de datos, o de la JVM actual? Las timestamps originadas en la base de datos son costosas, porque Hibernate debe consultar la base de datos para determinar el "próximo" valor, pero serán más seguras en entornos de cluster. Note también, que no se sabe si todos los dialectos soportan esta obtención de timestamps de la base de datos, mientras que otros pueden ser inseguros de usar en "locking", dada su falta de precisión (Oracle 8, por ejemplo).

(6)

generated (optativo, por defecto, never): Especifica que esta propiedad timestamp en realidad es generada por la base de datos. Vea la discusión de propiedades generadas.

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

5.1.11. property

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

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

        type="typename"                                              (3)
        update="true|false"                                          (4)
        insert="true|false"                                          (4)
        formula="arbitrary SQL expression"                           (5)
        access="field|property|ClassName"                            (6)

        lazy="true|false"                                            (7)
        unique="true|false"                                          (8)
        not-null="true|false"                                        (9)
        optimistic-lock="true|false"                                 (10)
        generated="never|insert|always"                              (11)

        node="element-name|@attribute-name|element/@attribute|."
        index="index_name"
        unique_key="unique_key_id"
        length="L"
        precision="P"
        scale="S"
/>
(1)

name: el nombre de la propiedad, com una letra minúscula inicial.

(2)

column (optativo, por defecto, the property name): el nombre de la columna de la tabla en la base de datos mapeada. También puede especificarse con elemento(s) <column> anidados.

(3)

type (optativo): un nombre que indica el tipo de Hibernate.

(4)

update, insert (optativo, por defecto, true) : indica que las columnas mapeadas deben ser incluidas en los comandos SQL UPDATE y/o INSERT. Asignarles false a las dos permite una propiedad puramente "derivada", cuyo valor es inicializado solamente por alguna otra propieadad que se mapea a la(s) misma(s) columna(s), o por un trigger, u otra aplicación.

(5)

formula (optativo): una expresión SQL que define el valor de una propiedad computada. Las propiedades computadas no tienen un mapeo de columna propio.

(6)

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

(7)

lazy (optativo, por defecto, false): Especifica que esta propiedad debería ser obtenida de manera haragana, cuando se acceda por primera vez a la variable de instancia (requiere implementación bytecode en tiempo de ejecución).

(8)

unique (optativo): Habilita la generación del lenguaje de definición de datos (DDL) para una constraint única para las columnas. También permite que esto sea apuntado por una property-ref.

(9)

not-null (optativo): Habilita la generación del lenguaje de definición de datos (DDL) para una constraint de nulabilidad para las columnas.

(10)

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

(11)

generated (optativo, por defecto, never): Especifica que el valor de esta propiedad es en realidad generado por la base de datos. Vea la discusión de propiedades generadas.

typename puede ser:

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

  2. 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).

  3. El nombre de una clase de Java serializable.

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

Si no se especifica un tipo, Hibernate usará reflexión en la propiedad nombrada, para adivinar el tipo Hibernate correcto. Hibernate intentará interpretar el nombre de la clase devuelta por el método "getter" de la propiedad, usando las reglas 2, 3 y 4, en ese orden. De todos modos, esto no siempre es suficiente. En algunos casos, aún se necesita el atributo type (por ejemplo, para distinguir entre Hibernate.DATE e Hibernate.TIMESTAMP, o para especificar un tipo hecho a medida).

El atributo access le permite controlar cómo Hibernate accederá a la propiedad en tiempo de ejecución. Por defecto, Hibernate invocará al par get/set. Si usted especifica access="field", Hibernate salteará el par get/set y accederá al campo directamente, usando reflexión. Usted puede especificar su propia estrategia de acceso, nombrando una clase que implemente la interfaz org.hibernate.property.PropertyAccessor.

Las propiedades derivadas son una característica especialmente poderosa. Estas propiedades son de sólo lectura, por definición. Son computadas en el momento en que se cargan. Se declara dicha computación como un comando SQL, que se traduce en una subconsulta SELECT en el código SQL que carga la instancia.

<property name="totalPrice"
    formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
                WHERE li.productId = p.productId
                AND li.customerId = customerId
                AND li.orderNumber = orderNumber )"/>

Note que usted se puede referir a la tabla misma de la entidad, evitando usar el alias para una columna en particular (en el ejemplo, customerId). También note que puede usar elementos <formula> anidados si no le gusta especificar el valor como un atributo.

5.1.12. many-to-one

Una asociación común con otra clase persistente se declara usando un elemento many-to-one element. El modelo relacional es una asociación de-muchos-a-uno: la clave foránea en una tabla se refiera a la(s) columna(s) clave de la tabla de destino.

<many-to-one
        name="propertyName"                                          (1)
        column="column_name"                                         (2)

        class="ClassName"                                            (3)
        cascade="cascade_style"                                      (4)
        fetch="join|select"                                          (5)
        update="true|false"                                          (6)
        insert="true|false"                                          (6)

        property-ref="propertyNameFromAssociatedClass"               (7)
        access="field|property|ClassName"                            (8)
        unique="true|false"                                          (9)
        not-null="true|false"                                        (10)
        optimistic-lock="true|false"                                 (11)

        lazy="proxy|no-proxy|false"                                  (12)
        not-found="ignore|exception"                                 (13)
        entity-name="EntityName"                                     (14)
        formula="arbitrary SQL expression"                           (15)
        node="element-name|@attribute-name|element/@attribute|."
        embed-xml="true|false"
        index="index_name"
        unique_key="unique_key_id"
        foreign-key="foreign_key_name"
/>
(1)

name: El nombre de la propiedad.

(2)

column (optativo): El nombre de la columna de clave foránea. También puede ser especificada por elementos <column> anidados.

(3)

class (optativo, por defecto, el tipo de la propiedad, determinado por reflexión): El nombre de la clase asociada

(4)

cascade (optativo): Especifica qué operaciones serán propagadas en cascada desde el objeto padre hacia los objetos asociados.

(5)

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

(6)

update, insert (optativo, por defecto, true) especifica que las columnas mapeadas deben incluirse en el los comandos SQL UPDATE y/o INSERT. Asignarles false permite una propiedad puramente "derivada", cuyo valor es inicializado desde alguna otra propiedad, o desde un trigger, u otra aplicación.

(7)

property-ref: (optativo) El nombre de una propiedad de una clase asociada que está ligada a esta clave foránea. Si no se especifica, se usa la clave primaria de la clase asociada.

(8)

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

(9)

unique (optativo): Habilita la generación de lenguaje de definición de datos (DDL) para la creación de una constraint única para la columna de clave foránea. También permite que ésta sea el blanco referido por una property-ref. Esto hace que la "multiplicidad" de la asociación sea, efectivamente, de-uno-a-uno.

(10)

not-null (optativo): Habilita la generación de lenguaje de definición de datos (DDL) para la creación de una constraint de nulabilidad para las columnas de la clave foránea.

(11)

optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones de esta propiedad requieren o no la adquisición de un "lock" optimista. En otras palabras, determina si debería ocurrir un incremento de versión cuando esta propiedad esté "sucia".

(12)

lazy (optativo, por defecto, proxy): Las asociaciones de extremo único están "proxied" por defecto. lazy="no-proxy" especifica que el valor de la propiedad debe ser capturado en forma haragana, cuando se acceda a la variable de instancia por primera vez (requiere instrumentación bytecode en tiempo de ejecución). lazy="false" especifica que la asociación siempre será capturada de manera ansiosa ("eager" fetching).

(13)

not-found (optativo, por defecto, exception): Especifica cómo serán manejadas las claves foráneas que se refieran a filas ausentes. ignore tratará dicha fila ausente como si fuera una asociación nula.

(14)

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

(15)

formula (optativo): una expresión SQL que define el valor de una clave foránea computada.

Asignarle cualquier valor que tenga sentido al atributo cascade que no sea none propagará ciertas operaciones al objeto asociado. Los valores que tienen sentido son las operaciones básicas de Hibernate, persist, merge, delete, save-update, evict, replicate, lock, refresh, así como los valores especiales delete-orphan y all, y combinaciones de nombres de operacion separados por comas, por ejemploc ascade="persist,merge,evict" o cascade="all,delete-orphan". Vea la Sección 10.11, “Persistencia transitiva” para una explicación completa. Note que las asociaciones a un solo valor (de-muchos-a-uno y de-uno-a-uno) no soportan el borrado de huérfanos.

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

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

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

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

Entonces, el mapeo para OrderItem podría usar:

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

De todos modos, esto se desaconseja.

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

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

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

5.1.13. one-to-one

Una asociación de-uno-a-uno a otra clase persistente se declara usando el elemento one-to-one.

<one-to-one
        name="propertyName"                                          (1)

        class="ClassName"                                            (2)
        cascade="cascade_style"                                      (3)
        constrained="true|false"                                     (4)
        fetch="join|select"                                          (5)
        property-ref="propertyNameFromAssociatedClass"               (6)

        access="field|property|ClassName"                            (7)
        formula="any SQL expression"                                 (8)
        lazy="proxy|no-proxy|false"                                  (9)
        entity-name="EntityName"                                     (10)
        node="element-name|@attribute-name|element/@attribute|."
        embed-xml="true|false"
        foreign-key="foreign_key_name"
/>
(1)

name: El nombre de la propiedad.

(2)

class (optativo, por defecto, el tipo de la propiedad, determinado por reflexión): El nombre de la clase asociada.

(3)

cascade (optativo) especifica qué operaciones deben ser propagadas en cascada desde el objeto padre hacia el objeto asociado.

(4)

constrained (optativo) especifica que una constraint de clave foránea a la clave primaria de la tabla mapeada apunta a la tabla de la clase asociada. Esta opción afecta el orden en que save() y delete() son propagadas en cascada, y determina si la asociación puede generar "proxies" (también es usada por la herramienta de exportación de esquema de base de datos).

(5)

fetch (optativo, por defecto, select): Elige entre captura por outer-join y captura por selección secuencial.

(6)

property-ref: (optativo) El nombre de una propiedad de la clase asociada que está asociada a la clave primaria de esta clase. Si no se especifica, se usa la clave primaria de la clase asociada.

(7)

access (optativo, por defecto, property): La estrategia que Hibernate debe usar para acceder al valor de la propiedad.

(8)

formula (optativo): Casi todas las asociaciones de-uno-a-uno se mapean a la clave primaria de la entidad a la que pertenecen, En el raro caso de que no sea así, se puede especificar alguna otra columna o expresión con la cual asociarla usando una fórmula SQL. (vea org.hibernate.test.onetooneformula por un ejemplo).

(9)

lazy (optativo, por defecto, proxy): Por defecto, las asociaciones de extremo único usan proxies. lazy="no-proxy" especififica que la propuedad debe ser capturada en forma haragana cuando se accede a la variable de instancia por primera vez (requiere instrumentación bytecode de tiempo de ejecución). lazy="false" especifica que á asociación será siempre capturada de forma "ansiosa" (eager fetching). ¡Note que si se usa constrained="false", usar proxies es imposible e Hibernate siempre capturará en forma ansiosa!

(10)

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

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

  • asociaciones por clave primaria.

  • asociaciones por clave foránea única.

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

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

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

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

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

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

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

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

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

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

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

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

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 para todas las entidades. Una clave natural es una propiedad o combinación de propiedades que sea única y no nula. Si también es inmutable, mejor todavía. Mapee las propiedades de la clave natural dentro del elemento <natural-id>, e Hibernate generará las constraints necesarias de unicidad y nulabilidad, y su mapeo quedará más auto-documentado.

Recomentamos fuertemente que implemente equals() y hashCode() para comparar las propiedades de la clave natural de la entidad.

Este mapeo no debería usarse con entidades para las cuales la clave primaria es la clave natural.

  • mutable (optativo, por defecto, false): Por defecto, propiedades identificador naturales que asume son inmutables (constantes).

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 componenes a su vez pueden declarar sus propias propiedades, componentes o colecciones. Vea "Componentes" a continuación.

<component
        name="propertyName"                 (1)
        class="className"                   (2)

        insert="true|false"                 (3)
        update="true|false"                 (4)
        access="field|property|ClassName"   (5)
        lazy="true|false"                   (6)
        optimistic-lock="true|false"        (7)

        unique="true|false"                 (8)
        node="element-name|."
>

        <property ...../>
        <many-to-one .... />
        ........
</component>
(1)

name: El nombre de la propiedad

(2)

class (optativo, por defecto, el tipo de la propiedad, determinado por reflexión): El nombre de la clase componente (hija).

(3)

insert: ¿Aparecen las columnas mapeadas en los SQL INSERTs?

(4)

update: ¿Aparence las columnas mapeadas en los SQL UPDATEs?

(5)

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

(6)

lazy (optativo, por defecto, false): Especifica que este componente debería ser capturado en forma haragana (lazy fetching) cuando se acceda a la variable de instancia por primera vez (requiere implementación bytecode en tiempo de ejecución).

(7)

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

(8)

unique (optativo, por defecto, false): Especifica que existe una constraint de unicidad sobre todas las columnas mapeadas del componente.

Las propiedades <property> hijas mapean propiedades de la clase hija a columnas de la tabla.

El elemento <component> acepta un subelemento <parent> que mapea una propiedad del componente como una referencia que apunta de vuelta a la entidad contenedora.

El elemento <dynamic-component> permite que un Map sea mapeado como componente, en donde el nombre de la propiedad se refiere a claves del mapa, véase Sección 8.5, “Componentes dinámicos”.

5.1.16. properties

El elemento <properties> permite la definición de un agrupamiento lógico, con un nombre, de algunas propiedades de una clase. La utilidad más importante de este tipo de construcción, es que permite que una combinación de propiedades sea el blanco de un property-ref. También es una manera conveniente de definir una constraint de unicidad.

<properties
        name="logicalName"                  (1)
        insert="true|false"                 (2)
        update="true|false"                 (3)
        optimistic-lock="true|false"        (4)
        unique="true|false"                 (5)

>

        <property ...../>
        <many-to-one .... />
        ........
</properties>
(1)

name: El nombre lógico del agrupamiento ( no es un nombre de propiedad real).

(2)

insert: ¿Aparecen las propiedades mapeadas en los comandos INSERT?

(3)

update: ¿Aparecen las propiedades mapeadas en los comandos UPDATE?

(4)

optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones de esta propiedad requieren o no la adquisición de un "lock" optimista. En otras palabras, determina si debería ocurrir un incremento de versión cuando esta propiedad esté "sucia".

(5)

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

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

<class name="Person">

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

</class>

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

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

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

</many-to-one>

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

5.1.17. subclass

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

<subclass
        name="ClassName"                              (1)

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

        <property .... />

        .....
</subclass>
(1)

name: El nombre (enteramente calificado) de la subclase.

(2)

discriminator-value (optativo, por defecto, el nombre de la clase): A un valor que distingue entre subclases individuales.

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

Cada subclase debería declara sus propias propiedades persistentes y subclases. Se asume que las propiedades <version> e <id> serán heredadas de la clase raíz. Cada subclase en la jerarquía debe definir un valor único de discriminator-value. Si no es especificado ninguno, se usa el nomre enteramente calificado de la clase de Java.

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

5.1.18. joined-subclass

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

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

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

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

<hibernate-mapping package="eg">

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

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

        </class>

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

</hibernate-mapping>

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

5.1.19. union-subclass

Una tercera opción es mapear a tablas sólo las clases concretas de la jerarquía de herencias. (la estrategia de "una-tabla-por-clase-concreta"), en la cual una tabla define todo el estado persustente de la clase, incluido el estado persistente. En Hibernate, no es absolutamente necesario mapear dichas jerarquías de herencia. Se puede, simplemente, mapear cada clase con una declaracíón separada de <class>. De todos modos, si desea usar asociaciones polimórficas (por ejemplo, una asociación a la superclase de su jeraquía), es necesario usar un mapeo con <union-subclass>.

<union-subclass
        name="ClassName"                    (1)

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

        <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 (lazy fetching).

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 relacion de-uno-a-uno entre dichas tablas.

<join
        table="tablename"                        (1)
        schema="owner"                           (2)
        catalog="catalog"                        (3)
        fetch="join|select"                      (4)
        inverse="true|false"                     (5)

        optional="true|false">                   (6)

        <key ... />

        <property ... />
        ...
</join>
(1)

table: El nombre de la tabla asociada.

(2)

schema (optativo): Suplanta al nombre del esquema de base de datos especificado en el elemento raíz <hibernate-mapping>.

(3)

catalog (optativo): Suplanta al nombre del catálogo de base de datos especificado en el elemento raíz <hibernate-mapping>.

(4)

fetch (optativo, por defecto, join): Si se le asigna el valor pr defecto join, Hibernate usará un INNER JOIN para capturar una asociación definida por una clase en su superclase, y utilizará un OUTER JOIN para una asociación definida por una subclase. Si se le asigna el valor select, entonces Hibernate usará una asociación secuencial definida en una subclase, la cual sólo será emitida cuando ocurra que una fila represente una instancia de la subclase. Los INNER JOINS aún serán usados para capturar asociaciones definidas por la clase y sus superclases.

(5)

inverse (optativo, por defecto, false): Si se habilita, Hibernate no intentará insertar o acualizar las propiedades definidas por esta asociación.

(6)

optional (optativo, por defecto, false): Si se habilita, Hibernate insertará una nueva fila sólo si las propiedades definidas por esta asociación son no nulas, y siempre usará un OUTER JOIN para capturar las propiedades.

Por ejemplo, la información de la dirección de una perosna puede ser mapeada en una tabla separada, al tiempo que se preserva la semántica para todas las propiedades.

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

    <id name="id" column="PERSON_ID">...</id>

    <join table="ADDRESS">
        <key column="ADDRESS_ID"/>
        <property name="address"/>
        <property name="zip"/>
        <property name="country"/>
    </join>

    ...

Esta característica es a menudo sólo útil para modelos de datos anticuados. Recomendamos que haya menos tablas que clases, y un modelo de dominio bien detallado.
N.del T.: se les suele llamar "detallados" (en inglés "fine-grained") a los modelos con clases que contiengan otras clases como miembro, por ejemplo, una clase Dirección dentro de una clase Persona, en lugar de incluir los campos de la direccíón (calle, número, etc) directamente en Persona.
Aunque los campos de Dirección probablemente se terminen persistiendo en la misma tabla que los de Persona, se considera que una representación jerárquica que use las clases Persona y Dirección es más "detallada" que la simple tabla PERSONA subyacente


De todos modos, "join-table" es útil para alternar entre distintas estrategias de mapeo en una misma jerarquía, como se explica más adelante.

5.1.21. key

Hemos visto surgir el elemento <key> varias veces ya. Aparece cada vez que el mapeo de un elemento padre define una asociación a una nueva tabla, que haga referencia a la clave primaria de la tabla original.

<key
        column="columnname"                      (1)

        on-delete="noaction|cascade"             (2)
        property-ref="propertyName"              (3)
        not-null="true|false"                    (4)
        update="true|false"                      (5)
        unique="true|false"                      (6)

/>
(1)

column (optativo): El nombre de la columna de clave foránea. Esto también puede ser especficado por elementos <column> anidados.

(2)

on-delete (optativo, por defecto, noaction): Especifica si la constraint de clave foránea tiene habilidtado el borrado en cascada a niver de la base de datos.

(3)

property-ref (optativo): Especifica que la clave foránea se refiere a columnas que no son la clave primaria de la tabla original. (provisto para datos anticuados/heredados solamente).

(4)

not-null (optativo): Especifica que las columnas de la clave foránea no son anulables (lo cual se sobreentiende cuando la clave foránea es también parte de la clave primaria)

(5)

update (optativo): Especifica que la clave foránea nunca debería ser actualizada (lo cual se sobreentiende cuando la clave foránea es también parte de la clave primaria)

(6)

unique (optativo): Especifica que la clave foránea debería tener una constraint de unicidad (lo cual se sobreentiende cuando la clave foránea es también la clave primaria).

Recomendamos que, para sistemas en los cuales la performance de delete sea importante, toda las claves sean definidas con on-delete="cascade", e Hibernate generará una constraint ON CASCADE DELETE a nivel de la base de datos, en lugar de varios comandos DELETE individuales. Tenga presente que esta característica saltea la estrategia usual de locking optimista para datos versionados.

Los atributos not-null y update son útiles cuando se mapea una asociación de-uno-a-muchos unidireccional. Si la mapea a una clave foránea no anulable, hay que declarar la columa clave usando <key not-null="true">.

5.1.22. elementos column y formula

Cualquier elemento de mapeo que acepte un atributo column aceptará, alternativamente, un subelemento <column>. De la misma manera, el elemento <formula> es una alternativa al atributo formula.

<column
        name="column_name"
        length="N"
        precision="N"
        scale="N"
        not-null="true|false"
        unique="true|false"
        unique-key="multicolumn_unique_key_name"
        index="index_name"
        sql-type="sql_type_name"
        check="SQL expression"
        default="SQL expression"/>
<formula>SQL expression</formula>

Los atributos column y formula pueden incluso ser combinados dentro de la mismo mapeo de propiedad o asociación, para expresar, por ejemplo, codiciones de asociación exóticas.

<many-to-one name="homeAddress" class="Address"
        insert="false" update="false">
    <column name="person_id" not-null="true" length="10"/>
    <formula>'MAILING'</formula>

</many-to-one>

5.1.23. import

Supongamos que su aplicación tiene dos clases persistenes con el mismo nombre, y usted no quiere especificar un nombre de clase enteramente calificado (con el paquete) en las consutas de Hibernate. Las clases pueden ser "importadas" explícitamente en lugar de tener que confiar en el auto-import="true". Usted puede incluso importar clases e interfaces que no estén mapeadas explícitamente.

<import class="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 de múltiples tablas. Este tipo de mapeo siempre requiere más de una columna. La primera columna contiene el tipo de la entidad asociada. Es imposible especificar una constraint de clave foránea para este tipo de asociaciones. Esto se debería usar sólo en casos muy especiales (por ejemplo, logs de auditoría, datos de sesión de usuario, etc).

El atributo meta-type le permite a la aplicación especificar un tipo hecho a medida, que mapeará valores de columna en la base de datos a clases persistentes, las cuales tendrán propiedades indentificadoras del tipo identificado por id-type. Se debe especificar el mapeo desde los valores valores del meta-tipo a los nombres de las clases.

<any name="being" id-type="long" meta-type="string">
    <meta-value value="TBL_ANIMAL" class="Animal"/>
    <meta-value value="TBL_HUMAN" class="Human"/>
    <meta-value value="TBL_ALIEN" class="Alien"/>
    <column name="table_name"/>
    <column name="id"/>
</any>
<any
        name="propertyName"                      (1)

        id-type="idtypename"                     (2)
        meta-type="metatypename"                 (3)
        cascade="cascade_style"                  (4)
        access="field|property|ClassName"        (5)
        optimistic-lock="true|false"             (6)

>
        <meta-value ... />
        <meta-value ... />
        .....
        <column .... />
        <column .... />
        .....
</any>
(1)

name: el nombre de la propiedad.

(2)

id-type: el tipo del identificador.

(3)

meta-type (optativo, por defecto, string): Cualquier tipo que sea permisible para mapear un discriminador.

(4)

cascade (optativo, por defecto none): el estilo de propagación en cascada

(5)

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

(6)

optimistic-lock (optativo, por defecto, true): Especifica si las actualizaciones de esta propiedad requieren o no la adquisición de un "lock" optimista. En otras palabras, determina si debería ocurrir un incremento de versión cuando esta propiedad esté "sucia".

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 se transmiten de padre a hijos). Éste es un tipo distinto de manejo de objetos de datos (ODMG, por sus siglas en inglés), que consiste en la persistencia de objetos basada en la capacidad de acceder a ellos, lo cual corresponde más íntimamente con cómo los objetos de una aplicación son usados en sistemas grandes. Las entidades soportan referencias compartidas y circulares, y pueden ser versionadas.

El estado persistente de una entidad consiste en referencias a otras entidades, y a instancias de lo que en Hibernate se denomina "value types" (tipos "valor"), Los "value types" son los tipos primitivos, las colecciones (el componente colección en sí, no lo que está dentro de ellas), y ciertos objetos inmutables. Los "value types" no pueden ser grabados ni versionados independientemente, sino que son persistidos junto con la entidad que los contiene. No tienen "identidad" independiente, y por lo tanto no pueden ser compartidos entre dos o más entidades, ni entre colecciones.

Hasta ahora hemos usado el término "clase persistente" para referirnos a entidades. Lo seguiremos haciendo, pero, estrictamente hablando, sin embargo, un "componente" (en inglés, component) también es una clase, definida por el usuario, que es persistente. La diferencia es que estos "componentes" tienen la semántica de un "value type", no la de una entidad. Un "value type" puede ser, entonces, un tipo primitivo de la JDK, o una String, o un tipo definido por el usuario. Una clase definida por el usuario puede ser, a criterio del programador de la aplicación, un "value type" o una entidad. En un modelo de dominio, ser una entidad en general implica que varios otros objetos compartirán referencias a ella, mientras que los "value types" simplemente forman parte de una única clase, vía composición o agrupación (en inglés, "composition" y "aggregation").

Retomaremos estos conceptos todo a lo largo de la documentación.

El desafío es mapear el sistema de tipos de Java (junto con la definición del desarrollador de las entidades y "value types") a un sistema del tipo SQL/BD. El puente entre ambos sistemas es provisto por Hibernate. Para las entidades proveemos <class>, <subclass> y así sucesivamente. Para los "value types" usamos <property>, <component>, etc., normalmente con un atributo type. El valor de este atributo es uno de los tipos para mapeo de Hibernate. Hibernate trae varios mapeos (para los tipos estándar de la JDK) ya incluidos. Pero usted puede escribir sus propios tipos para mapeo e implementar sus propias estrategias de conversión, como veremos más adelante.

Todos los tipos que vienen ya incluidos en Hibernate, excepto las colecciones, soportan la semántica de nulo.

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 la marca de la BD): boolean, yes_no y true_false son todas codificaciones alternativas de un boolean o un java.lang.Boolean de Java.

string

Un mapeo de tipo de java.lang.String a VARCHAR (o el VARCHAR2 de Oracle).

date, time, timestamp

Mapeos de tipo de java.util.Date y sus subclases a tipos SQL DATE, TIME y TIMESTAMP (o equivalentes).

calendar, calendar_date

Mapeos de tipo de java.util.Calendar a tipos SQL TIMESTAMP y DATE (o equivalente).

big_decimal, big_integer

Mapeos de tipo de java.math.BigDecimal y java.math.BigInteger a NUMERIC (e el NUMBER de Oracle).

locale, timezone, currency

Mapeos de tipo de java.util.Locale, java.util.TimeZone y java.util.Currency a VARCHAR (o el VARCHAR2 de Oracle). Las instancias de Locale y Currency son mapeadas a sus códigos ISO. Las instancias de TimeZone son mapeadas a su ID.

class

Un mapeo de tipo java.lang.Class a VARCHAR (o la VARCHAR2 de Oracle). Una Class es mapeada con su nombre enteramente calificado.

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 Hibernate serializable con el nombre de una clase o interfaz serializable de Java, que no sea por defecto un tipo básico.

clob, blob

Mapeos de tipo para las clases JDBC java.sql.Clob y java.sql.Blob. Estos tipos pueden ser inconvenientes para algunas aplicaciones, dado que los objetos clob y blob no pueden ser reusados fuera de una transacción. (Más aún, el soporte de drivers es esporádico e inconsistente).

imm_date, imm_time, imm_timestamp, imm_calendar, imm_calendar_date, imm_serializable, imm_binary

Mapeos de tipo para lo que normalmente se considera "tipos mutables de Java", en los que Hibernate adopta ciertas optimizaciones que son sólo apropiadas para tipos inmutables de Java, y la aplicación trata al objeto como inmutable. Por ejemplo: para una instancia mapeada como imm_timestamp, no se debería invocar Date.setTime(). Para cambiar el valor de la propiedad (y hacer que ese valor cambiado sea persistido), la aplicación tiene que asignarle un nuevo objeto (no idéntico) a la propiedad.

Los identificadores únicos de las entidades y colecciones pueden ser de cualquier tipo básico excepto binary, blob y clob. (Los identiicadores compuestos también están permitidos, ver más adelante).

Los "value types" básicos tienen constantes Type definidas en org.hibernate.Hibernate. Por ejemplo, Hibernate.STRING representa el tipo string.

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 persistir propiedades del tipo java.lang.BigInteger a columnas VARCHAR. Hibernate no trae un tipo ya incluido para esto. Pero los tipos a medida no solamente sirven para mapear una propiedad de Java (o un elemento colección) a una sola columna de una tabla. Por ejemplo, usted puede tener una propiedad Java con métodos getNombre()/setNombre() de tipo java.lang.String que sea persistida varias columnas, como PRIMER_NOMBRE, INICIAL_DEL_SEGUNDO, APELLIDO.

Para crear un tipo a medida, implemente org.hibernate.UserType o org.hibernate.CompositeUserType y declare propiedades usando el nombre enteramente calificado del tipo. Revise org.hibernate.test.DoubleStringType para comprobar el tipo de cosas que es posible hacer.

<property name="twoStrings" type="org.hibernate.test.DoubleStringType">

    <column name="first_string"/>
    <column name="second_string"/>

</property>

Note el uso de los elementos <column> para mapear una sola propiedad a múltiples columnas.

Las interfaces CompositeUserType, EnhancedUserType, UserCollectionType, y UserVersionType poveen soporte para usuarios más especializados.

Incluso se le pueden proveer parámetros a un UserType en el archivo de mapeo. Para lograr esto, su UserType debe implementar la interfaz org.hibernate.usertype.ParameterizedType. Para proveerle parámetros a su tipo a medida, usted puede usar el elemento <type> en sus archivos de mapeo.

<property name="priority">

    <type name="com.mycompany.usertypes.DefaultValueIntegerType">
        <param name="default">0</param>
    </type>

</property>

Ahora el UserType puede aceptar valrores para el parámetro llamado default con el objeto Properties que le fue pasado.

Si se usa un objeto UserType muy a menudo, sería útil definirle un nombre corto. Esto se puede hacer usando el elemento <typedef>. Los "typedefs" le asignan un nombre a un tipo a medida, y pueden contener también una lista de parámetros por defecto si el tipo es parametrizado.

<typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero">

    <param name="default">0</param>

</typedef>
<property name="priority" type="default_zero"/>

También es posible sustituir (override) los parámetros provistos en un typedef, caso por caso, usando parámetros de tipo en el mapeo de propiedades.

Aunque la rica variedad de tipos ya incluidos en Hibernate hace que sólo en contadas ocasiones realmente se necesite usar un tipo a medida, se considera aconsejable crear tipos a medida para clases (no entidades) que ocurran frecuentemente en su aplicación. Por ejemplo, una clase MonetaryAmount (suma de dinero) sería una buena candidata para un CompositeUserType, incluso si pudiere ser fácilmente mapeada como componente. Uno de los motivos para esto es abstracción. Con un tipo a medida como éste, sus documentos de mapeo serán a prueba de posibles cambios en la forma en que los valores monetarios se representaren en el futuro.

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 de entidad para desambigüar entre instancias de las dos entidades mapeadas. (Por defecto, el nombre de la entidad es igual al nombre de la clase). Hibernate permite especificar el nombre de entidad al trabajar con objetos persistentes, al escribir consultas, o al mapear asociaciones a dicha entidad.

<class name="Contract" table="Contracts" entity-name="CurrentContract">
    ...
    <set name="history" inverse="true" order-by="effectiveEndDate desc">
        <key column="currentContractId"/>
        <one-to-many entity-name="HistoricalContract"/>
    </set>
</class>

<class name="Contract" table="ContractHistory" entity-name="HistoricalContract">
    ...
    <many-to-one name="currentContract" column="currentContractId" entity-name="CurrentContract"/>

</class>

Note cómo ahora las asociaciones son especificadas usando entity-name en lugar de class.

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 tabla o de columna en signos de acento grave (`), en inglés, "backticks". Hibernate usará el entrecomillado adecuado para el dialecto SQL correspondiente (lo cual es usualmente comillas, pero para SQL Server son corchetes, y para MySQL, estos "backticks").

<class name="LineItem" table="`Line Item`">

    <id name="id" column="`Item Id`"/><generator class="assigned"/></id>
    <property name="itemNumber" column="`Item #`"/>
    ...
</class>

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 las tags @hibernate.tags de XDoclet. No cubriremos esta estrategia aquí, dado que es estrictamente parte de XDoclet. De todos modos, incluimos el siguiente ejemplo de la clase Cat con mapeo XDoclet.

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

/**
 * @hibernate.class
 *  table="CATS"
 */
public class Cat {
    private Long id; // identifier
    private Date birthdate;
    private Cat mother;
    private Set kittens
    private Color color;
    private char sex;
    private float weight;

    /*
     * @hibernate.id
     *  generator-class="native"
     *  column="CAT_ID"
     */
    public Long getId() {
        return id;
    }
    private void setId(Long id) {
        this.id=id;
    }

    /**
     * @hibernate.many-to-one
     *  column="PARENT_ID"
     */
    public Cat getMother() {
        return mother;
    }
    void setMother(Cat mother) {
        this.mother = mother;
    }

    /**
     * @hibernate.property
     *  column="BIRTH_DATE"
     */
    public Date getBirthdate() {
        return birthdate;
    }
    void setBirthdate(Date date) {
        birthdate = date;
    }
    /**
     * @hibernate.property
     *  column="WEIGHT"
     */
    public float getWeight() {
        return weight;
    }
    void setWeight(float weight) {
        this.weight = weight;
    }

    /**
     * @hibernate.property
     *  column="COLOR"
     *  not-null="true"
     */
    public Color getColor() {
        return color;
    }
    void setColor(Color color) {
        this.color = color;
    }
    /**
     * @hibernate.set
     *  inverse="true"
     *  order-by="BIRTH_DATE"
     * @hibernate.collection-key
     *  column="PARENT_ID"
     * @hibernate.collection-one-to-many
     */
    public Set getKittens() {
        return kittens;
    }
    void setKittens(Set kittens) {
        this.kittens = kittens;
    }
    // addKitten not needed by Hibernate
    public void addKitten(Cat kitten) {
        kittens.add(kitten);
    }

    /**
     * @hibernate.property
     *  column="SEX"
     *  not-null="true"
     *  update="false"
     */
    public char getSex() {
        return sex;
    }
    void setSex(char sex) {
        this.sex=sex;
    }
}

Vea el sitio de web de Hibernate para más ejemplos de XDoclet con Hibernate.

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 verifican en tiempo de compilación. Este mecanismo es más poderoso que las anotaciones XDoclet, y mejor soportado por las herramientas gráficas (IDE) como IntelluJ IDEA, muchas de las cuales proveen autocompleción y resaltado de sintaxis para anotaciones de JDK 5.0. La nueva revisión de los EJB, (JSR-220), usa anotaciones como su principal mecanismo de metadatos para los Entity Beans. Hibernate3 implementa el EntityManager JSR-220 (la API de persistencia), hay soporte disponible para el mapeo de metadatos en el paquete de Annotataciones de Hibernate (Hibernate Annotations), como una descarga separada. Los metadatos que se soportan son tanto Hibernate3 como EJB3 (JSR-220).

El siguiente es un ejemplo de un POJO anotado como Entity Bean EJB.

@Entity(access = AccessType.FIELD)
public class Customer implements Serializable {

    @Id;
    Long id;

    String firstName;
    String lastName;
    Date birthday;

    @Transient
    Integer age;

    @Embedded
    private Address homeAddress;

    @OneToMany(cascade=CascadeType.ALL)
    @JoinColumn(name="CUSTOMER_ID")
    Set<Order> orders;

    // métodos Getter/setter, y de negocio ...
}

Note que el soporte para anotaciones JDK 5.0 (y JSR-220) todavía es un trabajo en curso, incompleto. Por favor remítase al módulo de Anotaciones de Hobernate para más detales.

5.6. Propiedades generadas

Las propiedades generadas son propiedades cuyos valores son generados por la base de datos. En general, las aplicaciones Hibernate tienen que refrescar los objetos que contengan cualquier propiedad para la cual la base de datos esté generando valores. Sin embargo, al marcar propiedades como "generadas" se deja que la aplicación delegue esta responsabilidad en Hibernate. Esencialmente, cada vez que Hibernate emita un INSERT o UPDATE para una entidad que tenga propiedades generadas definidas, inmediatamente generará un SELECT para capturar los valores generados.

Por añadidura, las propiedades marcadas como generadas debe ser no insertables y e inmodifocables. Sólo las versiones, las timestamps, y las propiedades simples pueden ser marcadas como generadas.

never (el valor por defecto) - significa que el valor de la propiedad dada nunca es generada por la base de datos.

insert - declara que el valor de la propiedad dada es generado al ocurrir un INSERT, pero no es regenerado en los UPDATEs subsiguientes. Cosas como "fecha de creación" entrarían en esta categoría. Note que, aunque las propiedades version y timestamp pueden ser marcadas como generadas, esta opción no está disponible para ellas.

always - declara que el valor de esta propiedad es generado tanto al insertar como al modificar.

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 las herramientas de evolución de esquema de base de datos con las que cuenta Hibernate, para poveer la capacidad de definir un esquema totalmente dentro de los archivos de mapeo de Hibernate. Aunque fue designada específicamente para eliminar (DROP) cosas como triggers o procedimientos almacenados, en realidad cualquier comando SQL que pueda ser ejecutado mediante un java.sql.Statement.execute() es válido aquí: ALTERs, INSERTs, etc). Esencialmente, hay dos maneras de definir objetos de base de datos auxiliares:

La primera es listar explícitamente los comandos CREATE y DROP en el archivo de mapeo:

<hibernate-mapping>

    ...
    <database-object>
        <create>CREATE TRIGGER my_trigger ...</create>
        <drop>DROP TRIGGER my_trigger</drop>
    </database-object>

</hibernate-mapping>

La segunda es proveer una clase a medida, la cual sepa cómo construir los comandos CREATE y DROP. Esta clase a medida debe implementar la interfaz org.hibernate.mapping.AuxiliaryDatabaseObject.

<hibernate-mapping>
    ...
    <database-object>
        <definition class="MyTriggerDefinition"/>
    </database-object>

</hibernate-mapping>

Adicionalmente, a estos objetos de base de datos se les puede definir un alcance, de manera que se apliquen sólo cuando se usen ciertos dialectos determninados.

<hibernate-mapping>
    ...
    <database-object>
        <definition class="MyTriggerDefinition"/>
        <dialect-scope name="org.hibernate.dialect.Oracle9Dialect"/>
        <dialect-scope name="org.hibernate.dialect.OracleDialect"/>

    </database-object>
</hibernate-mapping>

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; }
    public String getSerialNumber() { return serialNumber; }
    void setSerialNumber(String sn) { serialNumber = sn; }
}

La interfaz puede ser, efectivamente, un java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap o cualquier otra cosa que implemente la interfaz org.hibernate.usertype.UserCollectionType.)

Dese cuenta de cómo inicializamos la variable de instancia con una instancia de HashSet. Ésta es la mejor manera de inicializar propiedades con valor de colección, o instancias recientemente inicializadas (no persistentes). Cuando una clase se vuelve persistente (al llamar persist(), por ejemplo) Hibernate en realidad reemplazará el HashSet con una implementación de Set propia de Hibernate mismo. Esté atento a errores como éstos:

Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();  //kittens=gatitos
kittens.add(kitten);
cat.setKittens(kittens);
session.persist(cat);
kittens = cat.getKittens(); // Correcto, porque la colección kittens es un Set
(HashSet) cat.getKittens(); // ¡Error!

Las colecciones persistentes que son inyectadas por Hibernate se comportan como: HashMap, HashSet, TreeMap, TreeSet or ArrayList, dependiendo del tipo de interfaz.

Las instancias de coleccioones tienen el comportamiento usual de los "value tupes". Son automáticamente persistidas cuando son referidas por un objeto persistente, y aumáticamente borradas cuando son "des-referidas". Si una colección se pasa de un objeto persistente a otro, sus elementos pueden ser movidos de una tabla a otra. Dos entidades no pueden compartir una referencia a la misma instancia de una colección. Dado el modelo relacional subyacente, las propiedades con valor de colección no soportan la semántica de nulo. Hibernate no distingue entre una colección nula y una colección vacía.

No vale la pena preocuparse mucho por nada de esto. Use las colecciones persistentes del mismo modo en que usaría las colecciones comunes de Java. Sólo asegúrese de comprender la semántica de las asociaciones bidireccionales (la cual se discute más adelante).

6.2. Mapeo de colecciones

Consejo

Hay una gran variedad de mapeos que puede ser generada para colecciones, abarcando muchos modelos relacionales de uso común. Le sugerimos que experimente con la herramienta de generación de esquemas de DB para tener una idea de cómo las declaraciones de mapeo se traducen en tablas de una base de datos.

El elemento de mapeo de Hibernate que se use para mapear una colección depende del tipo de interfaz. Por ejemplo, un elemento <set> se usa para mapear propiedades del tipo Set.

<class name="Product">

    <id name="serialNumber" column="productSerialNumber"/>
    <set name="parts">
        <key column="productSerialNumber" not-null="true"/>
        <one-to-many class="Part"/>
    </set>

</class>

Además de <set>, también están los elementos de mapeo <list>, <map>, <bag>, <array> y <primitive-array> . El elemento <map> es representativo:

<map
    name="propertyName"                                         (1)
    table="table_name"                                          (2)
    schema="schema_name"                                        (3)
    lazy="true|extra|false"                                     (4)
    inverse="true|false"                                        (5)

    cascade="all|none|save-update|delete|all-delete-orphan|delet(6)e-orphan"
    sort="unsorted|natural|comparatorClass"                     (7)
    order-by="column_name asc|desc"                             (8)
    where="arbitrary sql where condition"                       (9)
    fetch="join|select|subselect"                               (10)

    batch-size="N"                                              (11)
    access="field|property|ClassName"                           (12)
    optimistic-lock="true|false"                                (13)
    mutable="true|false"                                        (14)
    node="element-name|."
    embed-xml="true|false"

>

    <key .... />
    <map-key .... />
    <element .... />
</map>
(1)

name el nombre de la propiedad colección

(2)

table (optativo, por defecto, el nombre de la propiedad) el nombre de la tabla de la colección (no se usa en asociaciones de-muchos-a-muchos).

(3)

schema (optativo) el nombre de un esquema de base de datos que suplanta al esquema declarado en el elemento raíz.

(4)

lazy (optativo, por defecto, true) puede usarse para inhabilitar la "captura haragana" (lazy fetching) y especificar que la asociación es siempre capturada en forma "ansiosa" (eager fetching), o para especificar que la asociación es "súper-haragana", en donde la mayoría de las operaciones evitan inicializar la colección (prescrito para colecciones muy grandes).

(5)

inverse (optativo, por defecto, false) marca esta colección como el extremo "inverso" de una asociación bidireccional.

(6)

cascade (optativo, por defecto, none) les permite a las operaciones propagarse en cascada a las entidades hijas.

(7)

sort (optativo) especifica una colección ordenada con un orden natural, o una clase "comparator" dada.

(8)

order-by (optativo, a partir de JDK1.4 solamente) especifica una columna o columnas de tabla que define el orden de iteración del Map, Set o bag, junto con un asc o desc opcional (ascendente/descendente).

(9)

where (optativo) especifica una condición SQL WHERE arbitraria, a ser usada ciando se capture o borre la colección (útil cuando la colección sólo debe contener un subconjunto de los datos disponibles).

(10)

fetch (optativo, por defecto, select) Elije entre la captura por outer-join, select secuencial, y subselect secuencial.

(11)

batch-size (optativo, por defecto, 1) especifica el "tamaño de lote" al capturar instancias de esta colección en forma haragana.

(12)

access (optativo, por defecto, property): La estrategia que Hibernate debería suar para acceder al valor colección.

(13)

optimistic-lock (optativo, por defecto, true): Especifica que cambios en el estado de la colección resultarán en el incremento de la versión de la entidada poseedora. (Para las asociaciones "de-uno-a-muchos", es a menudo rasonable inhabilitar este valor).

(14)

mutable (optativo, por defecto, true): Un valor false especifica que los elementos de esta colección nunca deberían cambiar (lo cual le permite a Hibernate efectuar ciertas optimizaciones menores).

6.2.1. Claves foráneas de las colecciones

Las instancias de colecciones se distinguen en la BD por la clave foránea que posee a la colección. Esta clave foránea se denomina columna (o columnas) clave de la colección en la tabla de la colección. La columna clave de la colección es mapeada por el elemento <key>.

Puede haber constraints de nulabilidad en la columna de la clave foránea. Para la mayoría de las colecciones, esto está implícito. Para las asociaciones de-uno-a-muchos unidireccionales, la columna de la clave primaria es anulable por defecto, así que a veces es necesario especificar not-null="true".

<key column="productSerialNumber" not-null="true"/>

la constraint de clave foránea puede usar ON DELETE CASCADE.

<key column="productSerialNumber" on-delete="cascade"/>

Vea el capítulo correspondiente para una definición completa del elemento <key>.

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 a medida, componentes, y, por supuesto, referencias a otras entidades. Esta es una distinción importante: un objeto en una colección puede ser manipulado con la semántica de "valor" (que todo su ciclo de vida dependa del dueño de la colección), o puede ser una referencia a otra entidad, con su propio ciclo de vida. En este último caso, sólo el "vínculo" entre ambos objetos es lo que se considera como estado almacenado por la colección.

Al tipo contenido se lo refiere como al tipo del elemento de la colección. Los elementos de la colección son mapeados por <element> o <composite-element>, o, en el caso de referencias a entidades, con <one-to-many> o <many-to-many>. Los primeros dos, mapean elementos con la semántica de valor. Los otros dos, se usan para mapear asociaciones de entidades.

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 la tabla de la colección (una columna que mapea a un índice de una array), o un índice de List, o una clave de un Map. El índice de un Map puede ser de cualqueira de los tipos básico, mapeado con <map-key>, puede ser una entidad mapeada con <map-key-many-to-many>, o puede ser un tipo compuesto, mapeado con <composite-map-key>. El índice de un array o lista es siempre del tipo integer y se mapea usando el elemento <list-index> element. La columna mapeada contiene enteros secuenciales (comenzando por cero, por defecto).

<list-index
        column="column_name"                (1)
        base="0|1|..."/>
(1)

column_name (obligatorio): El nombre de la columna que contiene el valor del índice de la colección.

(1)

base (optativo, por defecto, 0): El valor de columna índice correspondiente al primer elemento de la lista o array.

<map-key
        column="column_name"                (1)
        formula="any SQL expression"        (2)
        type="type_name"                    (3)

        node="@attribute-name"
        length="N"/>
(1)

column (optativo): El nombre de la columna que contiene los valores de índice de la colección.

(2)

formula (optativo): Una fórmula SQL usada para evaluar la clave del mapa

(3)

type (reguired): El tipo de las claves del mapa

<map-key-many-to-many
        column="column_name"                (1)
        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ía mapear la propiedad como una <bag> de Hibernate. Una bag (bolsa) no retiene su orden cuando es obtenida de la base de datos, pero puede ser opcionalmente ordenada.

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 una columna o columnas de clave foránea, una columna o columnas para el elemento de la colección, y, posiblemente, una columna o columnas índice.

Para una colección de valores, usamos la tag <element>.

<element
        column="column_name"                     (1)
        formula="any SQL expression"             (2)
        type="typename"                          (3)

        length="L"
        precision="P"
        scale="S"
        not-null="true|false"
        unique="true|false"
        node="element-name"
/>
(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"
    />
(1)

column (optativo): el nombre de columna del elemento clave foránea.

(2)

formula (optativo): una fórmula SQL usada para evaluar el valor del elemento clave foránea

(3)

class (obligatorio): el nombre de la clase asociada.

(4)

fetch (optativo, por defecto, join): permite capturas para esta asociación mediante un outer-join o un select secuencial. Éste es un caso especial: para una captura total y "ansiosa" (usando un solo SELECT) de una entidad y sus relaciones de-muchos-a-muchos con otras entidades, se debería habilitar la captura (fetch) de tipo join no solamente en la colección misma, sino también en este atributo del elemento anidado <many-to-many>.

(5)

unique (optativo): habilita la generación de lenguaje de definición de datos (DDL) para una constraint de unicidad en la columna de clave foránea. Esto vuelve efectivamente "de-uno-a-muchos" la multiplicidad de la asociación.

(6)

not-found (optativo, por defecto, exception): Especifica cómo se actuará cuando le falten filas a la clave foránea. ignore tratará la fila faltante como una asociación nula

(7)

entity-name (optativo): el nombre de entidad de la clase asociada, como una alternativa a class.

(8)

property-ref: (optativo) el nombre de una propiedad, en la clase asociada que está ligada a esta clave foránea. Si no se especifica, se usa la clave primaria de la clase asociada.

A continuación, algunos ejemplos. Primero, un conjunto de strings.

<set name="names" table="person_names">
    <key column="person_id"/>
    <element column="person_name" type="string"/>
</set>

Una bag conteniendo enteros, con un orden de iteración determinado por el atributo order-by:

<bag name="sizes" table="item_sizes"  order-by="size asc">
    <key column="item_id"/>
    <element column="size" type="integer"/>
</bag>

Un array de entidades (en este caso, una asociación de-uno-a-muchos):

<array name="addresses" table="PersonAddress" cascade="persist">
    <key column="personId"/>
    <list-index column="sortOrder"/>
    <many-to-many column="addressId" class="Address"/>
</array>

Un mapa de índices string a fechas:

<map name="holidays" table="holidays" schema="dbo" order-by="hol_name asc">
    <key column="id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map>

Una lista de componentes (se discute más adelante):

<list name="carComponents" table="CarComponents">
    <key column="carId"/>
    <list-index column="sortOrder"/>
    <composite-element class="CarComponent">
        <property name="price"/>
        <property name="type"/>
        <property name="serialNumber" column="serialNum"/>
    </composite-element>
</list>

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 una columna índice en la tabla Part. Una tag <one-to-many> indica que ésta es una asociación de-uno-a-muchos.

<one-to-many
        class="ClassName"                                  (1)
        not-found="ignore|exception"                       (2)
        entity-name="EntityName"                           (3)
        node="element-name"
        embed-xml="true|false"
    />
(1)

class (obligatorio): El nombre de la clase asociada.

(2)

not-found (optativo, por defecto, exception): especifica cómo se manejarán los identificadores en caché que estén refiriéndose a columnas perdidas. ignore tratará la fila ausente como una asociación nula.

(3)

entity-name (optativo): El nombre de la entidad asociada, como alternativa a class.

Note que el elemento <one-to-many> no necesita declarar ninguna columna. Tampoco es necesatio especificar un nombre de tabla en ningún lado.

Nota muy importante: Si la columna de clave foránea de una asociación <one-to-many> se declara NOT NULL, se debe declarar el mapeo de <key> not-null="true" o usar una asociación bidireccional con el mapeo de la colección marcado como inverse="true". Vea la discusíón sobre asociaciones bidireccionales más adelante en este capítulo.

Este ejemplo muestra un mapa de entidades Part por nombre (donde el nombre de la parte, partName, es una propiedad persistente de Part). Note el uso de un índice basado en una fórmula.

<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 especificar un comparador en el archivo de mapeo.

<set name="aliases" table="person_aliases" sort="natural">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" sort="my.custom.HolidayComparator">
    <key column="year_id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map>

Valores permitidos del atributo sort son unsorted, natural y el nombre de una clase que implemente java.util.Comparator.

Las colecciones ordenadas en realidad se comportan como un java.util.TreeSet o java.util.TreeMap.

Si quiere que la base de datos misma ordene los elementos de la colección, use el atributo order-by del mapeo de los mapeos de set, bag o map. Esta solución sólo se encuentra disponible bajo la JDK 1.4 o superior (se implementa usando LinkedHashSet o LinkedHashMap). Esto efectúa el ordenamiento en la consulta SQL, no en memoria.

<set name="aliases" table="person_aliases" order-by="lower(name) asc">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" order-by="hol_date, hol_name">
    <key column="year_id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date type="date"/>
</map>

¡Note que el valor del atributo order-by es un ordenamiento SQL, no HQL!

Las asociaciones pueden incluso ser ordenadas por algún criterio arbitrario en tiempo de ejecución, usandp un filtro (filter()) de colección.

sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();

6.3.2. Asociaciones bidireccionales

Una asociación bidireccional (bidirectional association) permite navegar desde ambos "extremos" de la asociación. Se soportan dos tipos de asociación bidireccional:

one-to-many

(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 la misma tabla de la base de datos, y declarando uno de los extremos como inverse (cuál, es a elección, pero no puede ser una colección indexada).

He aquí un ejemplo de una asociación bidireccional de-muchos-a-muchos; cada categoría puede tener muchos items y cada ítem puede tener muchas categorías:

<class name="Category">
    <id name="id" column="CATEGORY_ID"/>
    ...

    <bag name="items" table="CATEGORY_ITEM">
        <key column="CATEGORY_ID"/>
        <many-to-many class="Item" column="ITEM_ID"/>
    </bag>
</class>

<class name="Item">
    <id name="id" column="ITEM_ID"/>
    ...

    <!-- lado inverso -->
    <bag name="categories" table="CATEGORY_ITEM" inverse="true">
        <key column="ITEM_ID"/>
        <many-to-many class="Category" column="CATEGORY_ID"/>
    </bag>
</class>

Los cambios que se le hacen sólo al extremo inverso de la asociación, no son persistidos. Esto significa que Hibernate tiene dos representaciones en memoria por cada asociación bidireccional.: un vínculo de A a B, y otro vínculo de B a A. Esto es más fácil de entender si se piensa en el modelo de objetos Java y cómo se crea una relación de-muchos-a-muchos en Java.

category.getItems().add(item);          // La categoría ahora "sabe" acerca de la relación
item.getCategories().add(category);     // El ítem ahora "sabe" acerca de la relación

session.persist(item);                   // ¡La relación no será grabada!
session.persist(category);               // La relación sí será grabada

El lado no-inverso se usa para grabar en la base de datos la representación en memoria.

Se puede definir una asociación bidireccional de-muchos-a-muchos mapeando un a asocíación de-uno-a-muchos a la(s) misma(s) columna(s) de la tabla que una asociación de-muchos-a-uno, y poniendo inverse="true" en el el extremo 'muchos'.

<class name="Parent">

    <id name="id" column="parent_id"/>
    ....
    <set name="children" inverse="true">
        <key column="parent_id"/>
        <one-to-many class="Child"/>
    </set>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
</class>

Mapear uno de los extremos de la asociación con inverse="true" no afecta las propagaciones en cascada. ¡Son conceptos ortogonales!

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 algunas consideraciones especiales. Si existe una propiedad de la clase hija que mapee a la columna de índice, no hay problema, podemos continuar usando inverse="true" en el mapeo de la colección.

<class name="Parent">

    <id name="id" column="parent_id"/>
    ....
    <map name="children" inverse="true">
        <key column="parent_id"/>
        <map-key column="name" type="string"/>
        <one-to-many class="Child"/>
    </map>

</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <property name="name" not-null="true"/>
    <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
</class>

Pero si tal propiedad no existe en la clase hoja, no podemos concebir la asociación como verdaderamete bidireccional (hay información disponible en un extremo que no lo está en el otro extremo). En este caso, no podemos mapear la colección con inverse="true". En lugar de eso, podemos usar el siguiente mapeo.

<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children">
        <key column="parent_id" not-null="true"/>
        <map-key column="name" type="string"/>

        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <many-to-one name="parent" class="Parent" column="parent_id" insert="false" update="false" not-null="true"/>

</class>

Note que en este mapeo, el extremo con la colección es el responsable de las actualizaciones de la clave foránea. A HACER: ¿esto resulta realmente en la ejecución de UPDATES innecesarios?

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 usa má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 tener identidicadores "sintéticos" (claves sustitutas), entonces encontrará un tanto raro que las asociaciones de-muchos-a-muchos y las colecciones que le hemos mostrado hasta ahora ¡se mapean todas a tablas con claves compuestas! Este punto es debatible. Una tabla que sea puramente "de asociación" no se beneficia mucho con la existencia de una clave sustituta (aunque una colección de valores compuestos sí podría). De todos modos, Hibernate provee un mecanismo que permite mapear asociaciones de-muchos-a-muchos y colecciones de valores a una tabla con clave sustituta.

El elemento <idbag> le permite mapear una List (o Collection) con semántica de bag.

<idbag name="lovers" table="LOVERS">

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

    <key column="PERSON1"/>
    <many-to-many column="PERSON2" class="Person" fetch="join"/>

</idbag>

Como se puede ver, una <idbag> tiene un generador de id "sintético", ¡igual que una clase de entidad! A cada fila de la colección se le asigna una clave sustituta diferente. Si embargo, Hibernate no provee ningún mecanismo para descubrir el valor de la clave sustituta de una fila en particular.

Note que la performance al actualizar una <idbag> ¡es mucho mejor que la de una <bag> común! Hibernate puede localizar filas individuales eficientemente, y actualizarlas o borraras individualmente, como si fuera una list, un map o un set.

En la implementación actual, el identificador native como estrategia de generación de identificadores no se soporta para las <idbag>s.

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 una asociación de-uno-a-muchos:

<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children">
            <key column="parent_id"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping>

Esto se mapea a las siguientes definiciones de tabla:

create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )
alter table child add constraint childfk0 (parent_id) references parent

If the parent is required, use a bidirectional one-to-many association:

<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" inverse="true">
            <key column="parent_id"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
        <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
    </class>

</hibernate-mapping>

Note la constraint NOT NULL:

create table parent ( id bigint not null primary key )
create table child ( id bigint not null
                     primary key,
                     name varchar(255),
                     parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent

Alternativamente, si usted insiste absolutamente en que la asociación debe ser unidireccional, debe declarar la constraint NOT NULL en el mapeo de la <key>.

<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children">
            <key column="parent_id" not-null="true"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping>

Por otra parte, si un hijo tiene múltiles padres, lo apropiado es una relación de-muchos-a-muchos.

<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" table="childset">
            <key column="parent_id"/>
            <many-to-many class="Child" column="child_id"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping>

Definiciones de tablas:

create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null, child_id bigint not null, primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
alter table childset add constraint childsetfk1 (child_id) references child

Para más ejemplos, y una explicación paso a paso del mapeo de una relación padre-hijo, vea Capítulo 21, Ejemplo: Padre/Hijo.

Son posibles mapeos de asociaciones aún más exóticas. Catalogaremos todas las posibilidades en el capítulo siguiente.

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 casos canónicos uno por uno, empezando con los mapeos unidireccionales, y siguiendo con los bidireccionales. Usaremos Person y Address ("persona" y "dirección") en todos los ejemplos.

Clasificaremos las asociaciones según usen o no una tabla de asociación, y según su multiplicidad.

Las claves foráneas anulables no son consideradas una práctica recomendable, en la modelización de datos tradicional, así que todos nuestros ejemplos usarán claves foráneas no nulas. Esto no es obligatorio, los mapeos aún funcionarían si se eliminara la constraint de nulabilidad.

7.2. 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 la constraint de unicidad.

<class name="Person">
    <id name="id" column="personId">
        <generator class="native"/>
    </id>
    <many-to-one name="address"
        column="addressId"
        unique="true"
        not-null="true"/>
</class>

<class name="Address">
    <id name="id" column="addressId">
        <generator class="native"/>
    </id>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
        

Una asociación unidirectional de-uno-a-uno por clave primaria normalmente usa un generador de id especial (note que hemos revertido la dirección de la asociación en este ejemplo).

<class name="Person">
    <id name="id" column="personId">
        <generator class="native"/>
    </id>
</class>

<class name="Address">
    <id name="id" column="personId">
        <generator class="foreign">
            <param name="property">person</param>
        </generator>
    </id>
    <one-to-one name="person" constrained="true"/>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
        

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 al especificar unique="true", hemos cambiado la multiplicidad de "de-muchos-a-muchos" a "de-uno-a-muchos".

<class name="Person">

    <id name="id" column="personId">
        <generator class="native"/>
    </id>
    <set name="addresses" table="PersonAddress">
        <key column="personId"/>
        <many-to-many column="addressId" unique="true" class="Address"/>
    </set>
</class>

<class name="Address">
    <id name="id" column="addressId">
        <generator class="native"/>
    </id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
        

7.3.2. de-muchos-a-uno

Una asociación A unidireccional de-muchos-a-uno por tabla de asociación es muy común, cuando la asociación es optativa

<class name="Person">
    <id name="id" column="personId">
        <generator class="native"/>

    </id>
    <join table="PersonAddress" optional="true">
        <key column="personId" unique="true"/>
        <many-to-one name="address" column="addressId" not-null="true"/>
    </join>
</class>

<class name="Address">
    <id name="id" column="addressId">
        <generator class="native"/>
    </id>
</class>
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 unique )
create table Address ( addressId bigint not null primary key )
        

7.3.4. de-muchos-a-muchos

Finalmente, tenemos una asociación unidireccional de-muchos-a-muchos.

<class name="Person">
    <id name="id" column="personId">
        <generator class="native"/>
    </id>
    <set name="addresses" table="PersonAddress">
        <key column="personId"/>
        <many-to-many column="addressId" class="Address"/>
    </set>
</class>

<class name="Address">
    <id name="id" column="addressId">
        <generator class="native"/>
    </id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
        

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, y dejar que Hibernate maneje la asociación desde el extremo "muchos" para mantener el índice de cada elemento (convirtiendo el otro lado virtualmente en inverso, al especificar update="false" y insert="false"):

<class name="Person">

   <id name="id"/>
   ...
   <many-to-one name="address" column="addressId" not-null="true" insert="false" update="false"/>
</class>

<class name="Address">
   <id name="id"/>
   ...
   <list name="people">
      <key column="addressId" not-null="true"/>
      <list-index column="peopleIdx"/>
      <one-to-many class="Person"/>
   </list>
</class>

Es importante que se defina not-null="true" en el elemento <key> del mapeo de la colección, si la columna de clave foránea sunyacente es NOT NULL. No debe declararse solamente not-null="true" en un posible elemento <column> anidado, sino en el elemento <key>.

7.4.2. de-uno-a-uno

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 an cualquier extremo de la asociación, en la colección, o en el join.

<class name="Person">

    <id name="id" column="personId">
        <generator class="native"/>
    </id>
    <set name="addresses" table="PersonAddress">
        <key column="personId"/>
        <many-to-many column="addressId" unique="true" class="Address"/>
    </set>
</class>

<class name="Address">
    <id name="id" column="addressId">
        <generator class="native"/>
    </id>
    <join table="PersonAddress" inverse="true" optional="true">
        <key column="addressId"/>
        <many-to-one name="person" column="personId" not-null="true"/>
    </join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
        

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 unique )
create 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 (personId, addressId) )
create table Address ( addressId bigint not null primary key )
        

7.6.  Mapeos de asociaciones más complejas

Asociaciones más complejas son extremadamente raras. Hibernate posibilita manejar asociaciones más complejas usando fragmentos SQL incrustados en el documento de mapeo. Por ejemplo, si una tabla con información contable histórica definiere las columnas accountNumber, effectiveEndDate and effectiveStartDate, mapeadas de esta manera:

<properties name="currentAccountKey">
    <property name="accountNumber" type="string" not-null="true"/>
    <property name="currentAccount" type="boolean">
        <formula>case when effectiveEndDate is null then 1 else 0 end</formula>
    </property>
</properties>

<property name="effectiveEndDate" type="date"/>

<property name="effectiveStateDate" type="date" not-null="true"/>

entonces podemos mapear una asociación a la instancia actual (la que tiene effectiveEndDate nula) usando:

<many-to-one name="currentAccountInfo" property-ref="currentAccountKey" class="AccountInfo">
    <column name="accountNumber"/>
    <formula>'1'</formula>
</many-to-one>

En un ejemplo más complejo, imagine que la asociación entre Employee y Organization estuviere mantenida por una tabla Employment, llena de datos históricos de empleo. Entonces, una asociación al empleador más reciente de un empleado (el que tuviere la startDate más reciente) podría ser mapeada de esta manera:

<join>
    <key column="employeeId"/>
    <subselect>
        select employeeId, orgId
        from Employments
        group by orgId
        having startDate = max(startDate)
    </subselect>
    <many-to-one name="mostRecentEmployer" class="Organization" column="orgId"/>
</join>

Uno se puede poner bastante creativo con esta funcionalidad, pero normalmente es más práctico manejar estos casos unando HQL o consultas Criteria.

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, para distintos 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() {
        return name;
    }
    public void setName(Name name) {
        this.name = name;
    }
    ......
    ......
}
public class Name {
    char initial;
    String first;
    String last;
    public String getFirst() {
        return first;
    }
    void setFirst(String first) {
        this.first = first;
    }
    public String getLast() {
        return last;
    }
    void setLast(String last) {
        this.last = last;
    }
    public char getInitial() {
        return initial;
    }
    void setInitial(char initial) {
        this.initial = initial;
    }
}

Ahora, Name puede ser persistida como un componente de Person (en inglés, "nombre" y "persona", respectivamente). Note que Name define métodos getter y setter para sus propiedades persistentes, pero no necesita declarar ninguna interfaz ni propiedades identiicadoras.

Nuestro mapeo Hibernate se vería así:

<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name"> <!-- el atributo 'class" es optativo -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class>

La tabla PERSON tendría las columnas pid, birthday, initial, first y last.

Como todos los "value types", los componentes no soportan referencias compartidas. Dos personas pueden tener el mismo nombre, pero los dos objetos Person correspondientes contendrán dos objetos nombre independientes, sólo que con "el mismo" valor. La semántica de nulo de un componente es ad hoc. Cuando se recargue el objeto contenedor, Hibernate assumirá que el componente entero es nulo. Esto debería alcanzar para la mayoría de los casos.

Las propiedades de un componente pueden ser de cualquier tipo Hibernate (colecciones, asociaciones de-muchos-a-uno, otros componentes, etc). Los componentes anidados no deben ser considerados una rareza. Se espera que Hibernate soporte un modelo de objetos muy jerárquico y detallado en este sentido.

El elemento <component> acepta un subelemento <parent> que se mapee a una propiedad desde la clase del componente como referencia de vuelta a la entidad contenedora.

<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name" unique="true">
        <parent name="namedPerson"/> <!-- referencia de vuelta a Person -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class>

8.2. Colecciones de objetos dependientes

Hay soporte para colecciones de componentes (por ejemplo, un array de tipos Name). Declare su colección de componentes reemplazando la tag del elemento <element> con una tag <composite-element>.

<set name="someNames" table="some_names" lazy="true">
    <key column="id"/>
    <composite-element class="eg.Name"> <!-- el atributo class es obligatorio -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </composite-element>
</set>

Nota: si se define un Set de elementos compuestos, es importante implementar equals() y hashCode() correctamente.

Los elementos compuestos pueden contener otros componentes, pero no colecciones. Si su componente compuesto a su vez contiene componentes, use la tag <nested-composite-element>. Éste es un caso batante exótico: una colección de componentes que a su vez tengan componentes. A este punto usted debería preguntarse si no sería más apropiada una asociación de-uno-a-muchos. Trate de remodelar el elemento compuesto como una entidad, pero dése cuenta de que, aunque el modelo de Java es el mismo, le modelo relacional y la semántica de persistencia son aún ligeramente diferentes.

Por favor, note que un mapeo de elemento compuesto no soporta propiedades anulables si se está usando un <set>. Hibernate tiene que usar el valor de cada columna para identificar un registro cuando se borra objetos (no hay una clave primaria separada en la tabla de elementos compuestos), lo cual no es posible con valores nulos. Se deberá o bien usar sólo valores no nulos en un <composite-element>, o bien elegir una <list>, <map>, <bag> o <idbag>.

Un caso especial de elemento compuesto ese el elemento compuesto que tiene un elemento <many-to-one> anidado. Un mapeo como éste permite mapear columnas extra de una tabla de asociación de-muchos-a-muchos a la clase del elemento compuesto. La siguiente es una asociación de-muchos-a-muchos de from Order a Item (de orden a ítem) en donde la fecha de compra ( purchaseDate), el precio (price) y la cantidad (quantity) son propiedades de la asociación.

<class name="eg.Order" .... >
    ....

    <set name="purchasedItems" table="purchase_items" lazy="true">
        <key column="order_id">
        <composite-element class="eg.Purchase">
            <property name="purchaseDate"/>
            <property name="price"/>
            <property name="quantity"/>

            <many-to-one name="item" class="eg.Item"/> <!-- el atributo "clase" es optativo -->

        </composite-element>
    </set>
</class>

Por supuesto, no puede haber una referencia a la compra del otro lado, para proveer navegación bidireccional de la asociación. Recuerde que los componentes son "value types", y no aceptan referencias compartidas. Una simple compra (Purchase) puede estar en el set de una Order, pero no puede ser referida por el Item al mismo tiempo.

Incluso son posibles asociaciones ternarias o cuaternarias:

<class name="eg.Order" .... >
    ....

    <set name="purchasedItems" table="purchase_items" lazy="true">
        <key column="order_id">
        <composite-element class="eg.OrderLine">
            <many-to-one name="purchaseDetails class="eg.Purchase"/>
            <many-to-one name="item" class="eg.Item"/>
        </composite-element>
    </set>
</class>

Los elementos compuestos pueden aparecer en consultas, usando la misma sintaxis que las entidades.

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 de un 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 ciertos requisitos:

  • Debe implementar java.io.Serializable.

  • Debe reimplementar equals() y hashCode(), de una manera consistente con la noción de igualdad para la clave compuesta en la DB.

Nota: en Hibernate3, el segundo requisito