Referencia a Hibernate

RECU-0178 (Recurso Referencia)
Tabla de contenidos
  1. 1. Descripción
    1. 1.1. Consideraciones generales de funcionamiento
      1. 1.1.1. ¿Por qué necesitamos Hibernate?
      2. 1.1.2. La arquitectura de Hibernate
      3. 1.1.3. Configuración de Hibernate
      4. 1.1.4. Navegación en Hibernate
      5. 1.1.5. Anotaciones en Hibernate
    2. 1.2. Clases Persistentes
      1. 1.2.1. Estados de una instancia de una clase persistente
    3. 1.3. Conceptos y estrategias en el mapeo de datos
      1. 1.3.1. Comprendiendo las entidades y los tipos de valores
      2. 1.3.2. "Value types" básicos
      3. 1.3.3. "Value types" hechos a medida
      4. 1.3.4. Mapeando clases con identidad
    4. 1.4. Mapeando con JPA
      1. 1.4.1. Marcar un POJO como entidad persistente
        1. 1.4.1.1. La definición de la tabla
      2. 1.4.2. Mapeando propiedades simples
        1. 1.4.2.1. Declarando propiedades simples
        2. 1.4.2.2. Tipo de acceso
        3. 1.4.2.3. Mapeando columnas
        4. 1.4.2.4. Declaración de componentes
    5. 1.5. Mapeando propiedades de identidad
        1. 1.5.0.1. Generar el identificador de la propiedad
        2. 1.5.0.2. Identificador compuesto
      1. 1.5.1. Mapeando la herencia
      2. 1.5.2. EJB3 soporta tres tipos de herencia
      3. 1.5.3. Tabla por clase
      4. 1.5.4. Mapeando relaciones en las entidades
        1. 1.5.4.1. Mapeo de relaciones uno a uno
        2. 1.5.4.2. Mapeo de muchos a uno
    6. 1.6. Transacciones y concurrencia
      1. 1.6.1. La sesión y el alcance (scope) de las transacciones
        1. 1.6.1.1. Unidad de trabajo
        2. 1.6.1.2. Conversaciones largas
        3. 1.6.1.3. Considerar la identidad de los objetos
        4. 1.6.1.4. Problemas comunes
      2. 1.6.2. Demarcación de transacciones de base de datos
        1. 1.6.2.1. Entornos no administrados
        2. 1.6.2.2. Usar JTA
        3. 1.6.2.3. Manejo de excepciones
        4. 1.6.2.4. Expiración de transacciones
    7. 1.7. Caché
      1. 1.7.1. El caché de segundo nivel
      2. 1.7.2. Administrar los cachés
      3. 1.7.3. El caché de consultas (query cache)
  2. 2. Ejemplos
  3. 3. Enlaces externos
  4. 4. Contenidos relacionados

Descripción

La característica principal de Hibernate es su soporte para el modelado basado en objetos, lo que le permite ofrecer un mecanismo transparente para la persistencia. Se utiliza XML para mapear una base de datos a una aplicación y soporta objetos de grano fino. La versión actual de Hibernate es compatible con anotaciones de Java y, por tanto, satisface la especificación de EJB.

Hibernate incluye un lenguaje de consulta muy poderoso llamado Hibernate Query Language o HQL. HQL es muy similar a SQL, y también define algunos convenios adicionales. HQL es completamente orientado a objetos, lo que le permite aprovechar la fuerza completa de la orientación a objetos y aprovecharse de la herencia, polimorfismo y asociación. Las consultas HQL se realizan con mayúsculas y minúsculas, a excepción de los nombres de las clases Java y las propiedades que se utilizan. HQL devuelve resultados de la consulta como objetos que se puede acceder directamente y ser manipulados por el programador. HQL también soporta muchas características avanzadas de la paginación y los perfiles dinámico que SQL nunca ha soportado.

Consideraciones generales de funcionamiento

Para almacenar y recuperar objetos de la base de datos, el desarrollador debe mantener una conversación con el motor de Hibernate mediante un objeto especial, quizás el concepto clave más importante dentro de Hibernate, llamado Sesion (clase Session). Se puede equiparar, a grandes rasgos, al concepto de conexión de JDBC y cumple un papel muy parecido, es decir, sirve para delimitar una o varias operaciones relacionadas dentro de un proceso de negocio, demarcar una transacción y aporta algunos servicios adicionales, como manejo de caché, para evitar interacciones innecesarias contra la BD. En este sentido veremos que la clase Session ofrece métodos como save (Object object), createQuery (String queryString), beginTransaction(), close(), entre otros.

El diseño esta pensado para interactuar con la base de datos tal como estamos acostumbrados a hacerlo con una conexión JDBC, pero con una diferencia: mayor simplicidad, es decir, guardar un objeto, por ejemplo, consiste en algo así como session.save(miObjeto), sin necesidad de especificar una sentencia SQL, y esto es sólo un ejemplo muy trivial en el que ganamos relativamente poco utilizando Hibernate.

Hibernate distingue entre objetos tipo transient y tipo persistent: los primeros son objetos que sólo existen en memoria y no en un almacén de datos, en algunos casos no serán almacenados jamás en la base de datos y en otros es un estado en el que se encuentran hasta ser almacenados en ella. Los segundos se caracterizan por haber sido ya almacenados y ser, por tanto, objetos persistentes. Dicho de otra manera, los objetos transient han sido instanciados por el desarrollador sin haberlos almacenado mediante una sesión, los objetos persistentes han sido creados y almacenados en una sesión o bien devueltos en una consulta realizada con la sesión.

Igual que con las conexiones JDBC, tenemos que crear y cerrar sesiones, aunque no hay una relación 1:1 entre sesiones y conexiones, es decir, no tenemos que abrir y cerrar simultáneamente sesiones y conexiones JDBC, la política a seguir dependerá del contexto del proceso de negocio de cada situación dándonos Hibernate amplias posibilidades para la implementación de nuestras políticas (conexiones JDBC gestionadas por la aplicación, por Hibernate, por un posible servidor de aplicaciones, etc), siendo solamente necesario en la práctica crear y cerrar explícitamente las sesiones de Hibernate.

¿Por qué necesitamos Hibernate?

Los beans de entidad, que tradicionalmente han sido utilizados para mapeo objeto-relacional, son muy difíciles de entender y difícil de mantener. Hibernate hace mapeo objeto-relacional simple mediante la asignación de los metadatos en un archivo XML, que definen la tabla en la base de datos, que deben ser asignados a una clase particular. En otros frameworks de persistencia es necesario modificar la clase de aplicación para lograr mapeo objeto-relacional, lo que no es necesario en Hibernate.

Con Hibernate, no es necesario preocuparse por cambios de base de datos, como cambios manuales en los archivos de secuencia de comandos SQL que se evitan. Si alguna vez necesita cambiar la base de datos que utiliza su aplicación, puede ser fácilmente cambiado mediante la modificación de la propiedad dialect en el archivo de configuración. Hibernate te da el poder completo de SQL, algo que nunca ofrecieron los primeros marcos ORM comerciales.

Hibernate genera código JDBC basado en la base de datos subyacente elegida, por lo que le ahorra la molestia de escribir código JDBC. También es compatible con la agrupación de conexiones. Las APIs que son usadas por Hibernate son muy simples y fáciles de aprender. Los desarrolladores con muy poco conocimiento de SQL puede hacer uso de Hibernate, ya que disminuye la carga de la escritura de consultas SQL.

La arquitectura de Hibernate

Internamente, Hibernate utiliza JDBC, que proporciona una capa de abstracción de la base de datos, mientras que emplea la API de transacciones Java (JTA) y JNDI para integrar con otras aplicaciones. La información de conexión que Hibernate necesita para interactuar con la base de datos es proporcionada por la agrupación de conexiones JDBC, que tiene que ser configurada.

La arquitectura de Hibernate se compone principalmente de dos interfaces (Session y Transaction) junto con la interfaz Query, que se encuentra en la capa de persistencia de la aplicación. Las clases que se definen en la capa de negocio de la solicitud, interactúan a través de metadatos independientes de la capa de persistencia, que a su vez mantiene conversaciones con la capa de datos con determinadas API de JDBC. Además, Hibernate usa otras interfaces para la configuración, sobre todo la clase Configuration. Hibernate también hace uso de las interfaces de devolución de llamada y algunas interfaces opcionales para extender la funcionalidad de asignación.

Las interfaces principales de programación que forman parte de Hibernate son los siguientes:

org.hibernate.SessionFactory: se utiliza básicamente para obtener una instancia de Session, y puede ser visto como una analogía con el mecanismo de agrupación de conexiones. Se trata de hilos de seguridad, como todos los hilos de aplicación puede utilizar un SessionFactory único (siempre que Hibernate utilice una sola base de datos). Esta interfaz se configura mediante el archivo de configuración, que determina la asignación de archivos al ser cargado.

org.hibernate.Session: proporciona un único hilo que determina la conversación entre la aplicación y la base de datos. Esto es análogo a una conexión específica. Una instancia de Session es "poco pesada" y su creación y destrucción es muy "barata". Esto es importante, ya que nuestra aplicación necesitará crear y destruir sesiones todo el tiempo, quizá en cada petición. Puede ser útil pensar en una sesión como en una caché o colección de objetos cargados (a o desde una base de datos) relacionados con una única unidad de trabajo.

org.hibernate.Transaction: proporciona un único objeto hilo que se extiende a través de la solicitud y determina una unidad atómica de trabajo. Básicamente, los resúmenes de JDBC, JTA, y las operaciones de CORBA.

org.hibernate.Query se utiliza para realizar una consulta, ya sea en HQL o en el dialecto SQL de la base de datos subyacente. Una instancia Query es ligera, y es importante señalar que no se puede utilizar fuera de la sesión a través de la cual se creó.

Configuración de Hibernate

El archivo de configuración ayuda en el establecimiento de una conexión a una base de datos relacional en particular. El archivo de configuración debe saber a qué archivos de mapeo necesita hacer referencia. En tiempo de ejecución, Hibernate lee el archivo de mapeo y lo utiliza para crear una clase Java dinámica correspondiente a la tabla de la base de datos. Un ejemplo de archivo de configuración:

<hibernate-configuration> <session-factory>
  <property name="hibernate.connection.url">
   jdbc:mysql://localhost/hibernateDemo
  </property>
  <property name="hibernate.connection.driver&#95;class">
   com.mysql.jdbc.Driver
  </property>
  <property  name="hibernate.connection.username">
   root
  </property>
  <property name="hibernate.connection.password">
   infosys
  </property>
  <property name="dialect">
   org.hibernate.dialect.MySQLDialect
  </property>
  <property name="hibernate.show&#95;sql">
   false
  </property>
  <property name="hibernate.transaction.factory&#95;class">
   org.hibernate.transaction.JDBCTransactionFactory
  </property>
  <mapping resource="Employee.hbm.xml" />
 </session-factory>
</hibernate-configuration>

Navegar por muchos datos es una de las principales tareas a las que deben hacer frente las aplicaciones. Saber encontrar lo que se quiere, y mostrar la información de una manera ordenada y rápida es una tarea que no por elemental suele estar bien resuelta.

Uno de los casos más comunes es tener miles de registros almacenados en una base de datos y querer mostrarlos a los usuarios de una manera tabulada y ordenados por algún criterio. En este caso, enseñar todos los registros no es recomendable, tanto desde el punto de vista de utilidad, es difícil encontrar una aguja en un pajar, como técnico, cargar miles de objetos que no se van a usar solo ralentiza la aplicación, y ocupa un espacio muy valioso cuando se está hablando de una aplicación multiusuario. La solución con la que se suele enfrentar a este problema una aplicación, es seguir el viejo axioma romano "divide y vencerás", mostrar solo una parte de los datos y dar la posibilidad y los recursos necesarios al usuario para que pueda acceder al resto de los datos mediante alguna interacción con la pantalla. Así se consiguen solucionar las dos pegas antes mencionadas, al usuario se le muestran unos pocos datos en los que puede centrarse y analizar la solución, y el sistema no se recarga de manera innecesaria. Bueno, esto es en teoría, porque la manera "tradicional" sigue siendo cargar todos los objetos en memoria y paginarlos usando la api de la interfaz List, con lo que el sistema se sigue recargando.

La solución correcta sería dividir los datos en origen, en la base de datos, y cargar únicamente en el entorno java los elementos que se necesitan mostrar, pero eso implica, conocer las extensiones que cada motor de base de datos aporta para estas misiones, al no ser una de las facilidades incorporadas al estándar SQL.

La solución que aporta hibernate es simple. Hibernate ofrece, principalmente, la posibilidad de realizar consultas a la base de datos de dos maneras distintas mediante sentencias HQL o mediante el api de criterios, en una caso se emplea la interfaz Query y en el otro la interfaz Criteria, pero ambas interfaces han sido dotadas de los mismos dos métodos:

  • setFirstResult: Nos permite posicionarnos en un registro determinado de la tabla. Empezando por cero.
  • setMaxResults: Establece cuantos registros, a partir del especificado con el método setFirstResult, recuperaremos de la base de datos.

La ventaja de la combinación de los anteriores métodos es que hibernate traduce nuestra petición y construye la sentencia sql correcta para cada motor, recuperando sólo los registros necesarios, sin que sepamos las particularidades de cada base de datos.

Anotaciones en Hibernate

Hibernate basa todo su funcionamiento en el uso de meta-información que contiene los datos necesarios para establecer las relaciones entre el modelo de datos relacional y el modelo de objetos. Tradicionalmente, esta información se le ha proporcionado a hibernate en la forma de ficheros de configuración xml, que se cargan en el entorno cuando hibernate se inicia por primera vez. Afortunadamente, a partir de ahora, disponemos de un sistema alternativo de configurar hibernate, basado en las anotaciones de la versión 5 de java. Gracias a estas anotaciones, la creación de herramientas para los IDE es mucho más fácil, al poder hacerse las correcciones en tiempo de compilación y al tener a nuestro alcance el uso de anotaciones que convierten estas tareas rutinarias y tediosas en más fáciles de completar y con menos posibilidad de error. Las anotaciones que hibernate nos pone sobre la mesa se pueden dividir en dos grandes grupos:

  • Compatibles con JPA. Son aquellas etiquetas que dicta la especificación JPA. Nada raro, si tenemos en cuenta que hibernate es un proveedor de JPA. Todas estas se encuentran en el paquete javax.persistence.
  • Anotaciones propias. Todas las anotaciones que hibernate proporciona para configurar características propias. Con estas anotaciones podemos hacer casi todo los que esta a nuestro alcance con los ficheros xml. No tienen nada que ver con las especificaciones de java.

Clases Persistentes

Las clases persistentes son clases de una aplicación que implementan 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 puede, 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. A la hora de implementar una clase persistente se debe considerar:

  • Implementar un constructor sin argumentos. Todas las clases persistentes deben tener un constuctor por defecto (el cual puede no ser público) de manera que 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
  • Provea una propiedad identificadora. 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
  • Evitar declarar las clases como finales. 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 rendmiento. 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".
  • Declare métodos de acceso y "mutadores" (accessors, mutators) para los campos persistentes. Es muy recomendable 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 propiedades sean declaradas como públicas. Hibernate puede persistir una propiedad con un par get/set que tenga acceso por defecto (package) o privado

Estados de una instancia de una clase persistente

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 identidad persistente e identidad Java.

Conceptos y estrategias en el mapeo de datos

Vamos a realizar un pequeño estudio sobre objeto/relación de clases y propiedades para convertirlas en tablas y columnas de la base de datos. El objetivo de este apartado es ayudar a obtener mapeos de datos de grado fino, comprendiendo las mejores estrategias disponibles en función de la situación. Prestaremos especial atención a la problemática que genera la herencia en los mapeos y como mapear colecciones y asociaciones.

Comprendiendo las entidades y los tipos de valores

Las entidades son tipos persistentes que representan objetos de negocio de primera clase. En otras palabras, algunas de las clases y tipos que tienen que tratar en una aplicación son más importantes, lo que naturalmente hace que otros sean menos importantes. ¿Qué hace con algo importante? Veamos la cuestión desde una perspectiva diferente

Un objetivo principal de Hibernate es el apoyo a modelos de dominio de grano fino, que hemos aislado como el requisito más importante para un modelo de dominio rico. Es una razón por la cual trabajamos con POJOs. En términos realistas, de grano fino significa más clases que tablas.

Hibernate hace hincapié en la utilidad de las clases de grano fino para la aplicación de la seguridad de tipos y el comportamiento. Por ejemplo, muchas personas tienen un modelo de dirección de correo electrónico como un cadena de valor de propiedad del usuario. Un enfoque más sofisticado consiste en definir una clase EmailAddress, lo que añade la semántica de alto nivel y el comportamiento, que puede ofrecer un método sendEmail().

Este problema de granularidad nos lleva a una distinción de importancia central en ORM. En Java, todas las clases son iguales, todos los objetos tienen su propia identidad y ciclo de vida. Nosotros pensamos un diseño de más clases que tablas. Una fila representa a múltiples instancias. Dado que la identidad de una base de datos es implementada mediante un valor de una clave primaria, algunos objetos persistentes no tienen su propia identidad

En efecto,el mecanismo de la persistencia implementa una semántica de paso por valor de algunas clases. Uno de los objetos representados en la fila tiene su propia identidad, y otros dependen de eso. Hibernate hace la distinción esencial siguiente:

  • Un objeto de tipo de entidad tiene una identidad propia en la base de datos (valor de clave principal). Una referencia de objeto a una instancia de entidad es persistido como una referencia en la base de datos (un valor de clave externa). La entidad tiene su propio ciclo de vida, y puede existir independientemente de cualquier otra entidad. 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").
  • Un objeto de tipo valor no tiene una identidad de base de datos, sino que pertenece a una instancia de una entidad y su estado persistente se incrusta en la fila de la tabla a la que pertenece. Los tipos de valor no tienen identificadores o propiedades identificadoras. La vida útil de una instancia de tipo de valor está limitada por la vida útil de la pertenencia a instancias de entidad. Un tipo de valor no es compatible con referencias compartidas.

A la hora de implementar POJOs para las entidades y los tipos de valor es necesario considerar:

  • Referencias compartidas: Escribe tus clases de manera que evite compartir referencias a las instancias de tipo valor.
  • Dependencias del ciclo de vida: Según lo discutido, el ciclo de vida de una instancia de tipo de valor esta ligado al de su instancia de la entidad propietaria. No hay una palabra clave para esto en Java, pero el flujo de trabajo de la aplicación y la interfaz de usuario deben estar diseñadas para respetar y esperar las dependencias del ciclo de vida. Los metadatos de persistencia incluyen las reglas en cascada para todas las dependencias.
  • Identidad de Entidades: Las clases necesitan una propiedad para identificar en casi todas los casos. Las clases de tipo valor definidas por el usuario no tienen la propiedad identificadora, debido a que estas instancias son identificadas mediante la entidad propietaria.

"Value types" básicos

Los tipos de mapeo básicos ya incluidos pueden ser 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 identificadores 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.

"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 en 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 valores para el parámetro llamado por defecto 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 . 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 pudiera ser fácilmente mapeada como componente. Uno de los motivos para esto es la 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 representasen en el futuro.

Mapeando clases con identidad

Con la persistencia objeto / relacional, un objeto persistente es una representación en memoria de una fila de una tabla de la base de datos. Junto con la identidad de Java (ubicación de la memoria) y la igualdad de objeto, lo recogerá la identidad de base de datos (que es la ubicación del almacén de datos persistentia). Ahora tiene tres métodos para la identificación de objetos:

  • Los objetos son idénticos si ocupan la misma posición de memoria en la JVM. Esto se puede comprobar mediante el operador ==. Este concepto se conoce como objeto de identidad.
  • Los objetos son iguales si tienen el mismo valor, tal como se define por el método equals (Object o) . Este concepto se conoce como la igualdad.
  • Los objetos almacenados en una base de datos relacionales son idénticos si representan la misma fila o, equivalentemente, si comparten la misma tabla y valor de la clave principal. Este concepto se conoce como la identidad de base de datos.

Ahora tenemos que ver cómo se relaciona con la identidad de base de datos objeto de identidad en Hibernate. Expone dos maneras para manejar la identidad de la base de datos en una aplicación:

  • El valor de la propiedad de identificación de la instancia persistente
  • El valor retornado por el método Session.getIdentifier(Object entity)

Mapeando con JPA

Las entidades JPA son POJOs. En realidad, son entidades persistentes en Hibernate. Sus asignaciones se definen mediante anotaciones JDK 5.0 en lugar de archivos hbm.xml. Las anotaciones se pueden dividir en dos categorías, las anotaciones de mapeo lógico (que describe el modelo de objetos, la asociación entre dos entidades, etc) y las anotaciones de mapeo físico (donde se describe el esquema físico, tablas, columnas, índices, etc.) Las anotaciones están en el paquete de javax.persistence .*.

Marcar un POJO como entidad persistente

Cada clase POJO persistente es una entidad y se declara con la anotación @ Entity (a nivel de clase):

@Entity
public class Flight implements Serializable {
    Long id;

    @Id
    public Long getId() { return id; }

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

La anotación @Entity declara la clase como una entidad (es decir, una clase POJO persistente), @Id declara la propiedad identificadora de esta entidad. Las otras declaraciones de mapeo son implícitas. El vuelo de clase se asigna a la tabla de vuelo, mediante la columna ID como su columna de clave principal.

Dependiendo de si se realizan anotaciones sobre campos o métodos, el tipo de acceso utilizado por Hibernate será por campo o propiedad. La especificación EJB3 requiere que usted declare anotaciones del tipo de elemento que se tendrá acceso, es decir, el método getter si utiliza acceso a la propiedad, el campo si se utiliza el acceso por campos. Mezclar anotaciones en los campos y métodos debe evitarse. Hibernate intentará adivinar el tipo de acceso mediante la posición de las anotaciones @Id o @EmbeddedId.

La definición de la tabla

La anotación @table impone el nivel de clase. Permite definir una tabla, el catálogo y el esquema de nombres para el mapeo de la entidad. Si no existe una anotación de este tipo se utilizan los valores por defecto: el nombre de la clase de la entidad

@Entity
@Table(name="tbl_sky")
public class Sky implements Serializable {
   ...
}

El elemento @table contiene un esquema y un catálogo de atributos, si necesitan ser definidos. Se pueden definir restricciones únicas para la tabla utilizando la anotación @UniqueConstraint en conjunto con la anotación @Table

@Table(name="tbl_sky",
    uniqueConstraints = {@UniqueConstraint(columnNames={"month", "day"})}
)

Mapeando propiedades simples

Declarando propiedades simples

Cada propiedad no estatica o transitoria (campo o método, dependiendo del tipo de acceso) de una entidad se considera persistente, a menos que se anoten como @transient. No tener una anotación de su propiedad es equivalente a la correspondiente anotación @basic . La anotación @basic le permite declarar la estrategia de búsqueda de una propiedad:

public transient int counter; //transient property

private String firstname; //persistent property

@Transient
String getLengthInMeter() { ... } //transient property

String getName() {... } // persistent property

@Basic
int getLength() { ... } // persistent property

@Basic(fetch = FetchType.LAZY)
String getDetailedComment() { ... } // persistent property

@Temporal(TemporalType.TIME)
java.util.Date getDepartureTime() { ... } // persistent property          

@Enumerated(EnumType.STRING)
Starred getNote() { ... } //enum persisted as String in database

JPA da soporte a la asignación de propiedades de todo tipo de base con el apoyo de Hibernate (todos los tipos básicos de Java, sus respectivos envoltorios y clases serializables).

En pocas APIs de Java, la precisión temporal de tiempo no está definida. Los datos temporales pueden tener la precisión según DATE, TIME, TIMESTAMP (es decir, la fecha real, sólo el tiempo, o ambos). Usar la anotación @Temporal para afinar eso.

La anotacion @Lob indica que la propiedad debe ser persistida en un Blob o Clob dependiendo del tipo de propiedad: java.sql.Clob, de caracteres [],char [] y java.lang.String se guarda en un Clob. java.sql.Blob, Byte [], byte[] y el tipo serializable se guarda en un Blob.

Tipo de acceso

Por defecto, el tipo de acceso de una jerarquía de clases se define por la posición de las anotaciones @Id o @ EmbeddedId . Si estas anotaciones se encuentran en un campo, entonces sólo se consideran los campos para la persistencia y al estado se accede a través del campo. Si hay anotaciones en un getter (método), entonces sólo los captadores son considerados para la persistencia y al estado se accede a través de la getter / setter. Esto funciona bien en la práctica y es el enfoque recomendado

Sin embargo, en algunas situaciones, es necesario:

  • forzar el tipo de acceso de la jerarquía de la entidad
  • reemplazar el tipo de acceso de una entidad específica en la jerarquía de clases
  • reemplazar el tipo de acceso de un tipo integrable

La mejor práctica es usar una clase incrustada utilizada por varias entidades que no podrían utilizar el mismo tipo de acceso. En este caso es mejor para forzar el tipo de acceso a nivel de clase incrustada.

Para forzar el tipo de acceso en una clase concreta, utilice la anotación @Access como se muestra a continuación

@Entity
public class Order {
   @Id private Long id;
   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }

   @Embedded private Address address;
   public Address getAddress() { return address; }
   public void setAddress() { this.address = address; }
}

@Entity
public class User {
   private Long id;
   @Id public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }

   private Address address;
   @Embedded public Address getAddress() { return address; }
   public void setAddress() { this.address = address; }
}

@Embeddable
@Access(AcessType.PROPERTY)
public class Address {
   private String street1;
   public String getStreet1() { return street1; }
   public void setStreet1() { this.street1 = street1; }

   private hashCode; //not persistent
}
Mapeando columnas

La columnas utilizadas para el mapeo de una propiedad se pueden definir mediante la anotación @Column. Se usa para reemplazar los valores por defecto. Puede utilizar esta anotación a nivel de propiedad para las propiedades que son:

  • No estan anotadas
  • anotado con @Basic
  • anotado con @Version
  • anotada con @Lob
  • anotado con @Temporal
@Entity
public class Flight implements Serializable {
...
@Column(updatable = false, name = "flight_name", nullable = false, length=50)
public String getName() { ... }
Declaración de componentes

Es posible declarar un componente integrado dentro de una entidad y sobrescribir su asignación de columna. Las clases de componentes pueden ser anotados en el nivel de clase con la anotación @Embeddable. Es posible anular la asignación de columna de un objeto incrustado de una entidad en particular mediante las anotaciones @Embedded y @AttributeOverride en la propiedad asociada.

@Embeddable
public class Country implements Serializable {
    private String iso2;
    @Column(name="countryName") private String name;

    public String getIso2() { return iso2; }
    public void setIso2(String iso2) { this.iso2 = iso2; }

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

Mapeando propiedades de identidad

La anotación @Id le permite definir que la propiedad es el identificador de su entidad. Esta propiedad se puede establecer por la propia solicitud o ser generado por Hibernate (recomendado). Puede definir la estrategia de generación de identificador gracias a la anotación @GeneratedValue.

Generar el identificador de la propiedad

JPA define cinco tipos de estrategias de generación de identificador:

  • AUTO - ya sea columna de identidad, la secuencia o la tabla en función de la base de datos
  • Table - Tabla que mantiene la ID
  • Identity - columna de identidad
  • Secuence - la secuencia
  • Identity copy - la identidad es copiada de otra entidad

El siguiente ejemplo muestra un generador de secuencias utilizando la configuración SEQ_STORE

@Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_STORE")
public Integer getId() { ... }

El siguiente ejemplo usa el generador de identidad:

@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
public Long getId() { ... }

El generador AUTO es el tipo preferido para aplicaciones portátiles (a través de varios proveedores de Base de datos). La configuración de generación de identificador puede ser compartida por varias asignaciones @Id con el atributo generador. Hay varias configuraciones disponibles a través de @SequenceGenerator y @TableGenerator. El ámbito de aplicación de un generador puede ser la aplicación o la clase. Los generadores definidos en el ámbito clase no son visibles fuera de la clase y pueden ser sobrescritos por los generadores a nivel de aplicación.

Identificador compuesto

Puede definir una clave principal compuesta por varias sintaxis:

  • Utilizar un tipo de componente para representar el identificador y el mapa como una propiedad de la entidad: a continuación, la propiedad anotado como @EmbeddedId. El tipo de componente tiene que ser Serializable.
  • Varias propiedades como un Map como @Id propiedades: el tipo de identificador es entonces la clase de entidad propia y debe ser Serializable. Este enfoque, por desgracia no viene de serie y sólo se admite por Hibernate.
  • Varias propiedades como un Map como @Id propiedades y declarar una clase externa para ser el tipo de identificador. Esta clase, que debe ser serializable, se declara a la entidad a través de la anotación @IdClass. El tipo de identificador debe contener las mismas propiedades que las propiedades identificadoras de la entidad: cada nombre de la propiedad debe ser el mismo, su tipo debe ser el mismo, así si la propiedad es de una entidad de tipo básico, su tipo debe ser el tipo de la clave principal de la entidad asociada si la propiedad entidad es una asociación (ya sea un @OneToOne o un @ManyToOne).

Mapeando la herencia

EJB3 soporta tres tipos de herencia

  • Estrategia de una tabla por clase
  • Una tabla simple por cada estrategia de jerarquía de clase
  • Una estrategia basada en la unión de subclases

La estrategia elegida se indica al declarar la clase mediante la anotación @Inheritance.

Tabla por clase

Esta estrategia tiene muchos inconvenientes (especialmente con las consultas polimórficas y asociaciones) explicado en la especificación JPA. Hibernate evita la mayoría de ellos implementando esta estrategia mediante consultas SQL UNION. Es de uso general para el nivel superior de una jerarquía de herencia

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Flight implements Serializable { ... }

Esta estrategia soporta las asociaciones one-to-many de forma bidireccional. Esta estrategia no soporta la generación de identidad. La identificación debe ser compartida a traves de las tablas. Por lo tanto no puede utilizarse AUTO o IDENTITY con esta estrategia.

Estrategia basada en la jerarquia de clases y tablas simples

Todas las propiedades de todas las clases padre y subclases se mapean en la misma tabla, los casos se distinguen por una columna discriminadora especial:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
    name="planetype",
    discriminatorType=DiscriminatorType.STRING
)
@DiscriminatorValue("Plane")
public class Plane { ... }

@Entity
@DiscriminatorValue("A320")
public class A320 extends Plane { ... }

@Inheritance y @DiscriminatorColumn solo deben de ser definidas al principio de la jeraraquía de la entidad.

Subclases unidas

Las anotaciones @PrimaryKeyJoinColumn y @PrimaryKeyJoinColumns definen la clave primaria de la tabla de la subclase a la que esta unida:

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Boat implements Serializable { ... }

@Entity
public class Ferry extends Boat { ... }

@Entity
@PrimaryKeyJoinColumn(name="BOAT_ID")
public class AmericaCupClass  extends Boat { ... }

Mapeando relaciones en las entidades

Mapeo de relaciones uno a uno

Puede asociar entidades a través de una relación uno a uno usando @OneToOne. Hay tres casos de asociaciones uno a uno :

  • las entidades asociadas comparten los mismos valores claves principales
  • una clave externa está en manos de una de las entidades
  • una tabla de asociación se utiliza para almacenar el vínculo entre las dos entidades (una restricción única tiene que ser definido en cada fk para garantizar la multiplicidad uno a uno).
@Entity
public class Body {
    @Id
    public Long getId() { return id; }

    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    public Heart getHeart() {
        return heart;
    }
    ...
}
Mapeo de muchos a uno

Las asociaciones Muchos a Uno se declaran a nivel de propiedad con la anotación @ManyToOne

@Entity()
public class Flight implements Serializable {
    @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
    @JoinColumn(name="COMP_ID")
    public Company getCompany() {
        return company;
    }
    ...
}

Transacciones y concurrencia

El punto más importante acerca de Hibernate y el control de concurrencia, es que es muy fácil de entender. Hibernate usa directamente las conexiones JDBC y los recursos de JTA sin agregar ningún mecanismo adicional de "locking".

Hibernate no efectúa "Bloqueos" de objetos en memoria. Si la aplicación puede esperar el comportamiento que esté definido por el nivel de aislamiento de sus transacciones de base de datos. Note que, gracias a la sesión, que es también una caché con alcance a la transacción, Hibernate provee lecturas repetibles para la búsqueda por identificadores, y consultas de entidad (no consultas "de reporte" que devuelvan valores escalares).

Además de versionar para lograr un control de concurrencia optimista, Hibernate también ofrece una API (mínima) para efectuar un "lock" de filas pesimista, usando la sintaxis SELECT FOR UPDATE. El control de concurrencia optimista.

La sesión y el alcance (scope) de las transacciones

La fábrica de sesiones (SessionFactory) es un objeto seguro en cuanto al acceso por hilos múltiples (thread-safe), y es costosa de crear. Se la concibió para ser compartida por todos los hilos de la aplicación. Se crea una sola vez, normalmente durante el arranque de la aplicación, a partir de una instancia de Configuration.

Una sesión individual (Session), en cambio, es barata de crear, y no es segura en cuanto al acceso por múltiples threads (no es "threadsafe"). Se espera que sea usada una sola vez, para una interacción simple o "request" (una sola solicitud, una sola "conversación" o "unidad de trabajo"), y luego sea descartada. Una sesión no intentará obtener una conexión de JDBC, ni una fuente de datos (Connection, Datasource) a menos que sea necesario. Por lo tanto, no consume recursos mientras no es usada.

Para completar este panorama se debe pensar también en las transacciones de base de datos. Una transacción de base de datos debe ser lo más corta posible, para reducir las posibilidades de conflictos de bloqueos en la base de datos. Las transacciones largas impiden que la aplicación sea "escalable" (es decir, que se adapte con facilidad a una mayor demanda y tamaño), por no poder soportar una mayor carga de demandas concurrentes. Por tal motivo, mantener una transacción abierta mientras el usuario "piensa" y hasta que la unidad de trabajo se complete, casi nunca es una buena idea.

¿Cuál es el alcance de una unidad de trabajo? ¿Una simple sesión de Hibernate puede abarcar varias transacciones de base de datos, o sus alcances son similares y con una relación uno a uno? ¿Cuándo se debe abrir y cerrar una sesión, y cómo se demarcan los límites de una transacción de base de datos?

Unidad de trabajo

Antes que nada, no emplee la práctica de una sesión por operación, es decir, no abra y cierre una sesión para cada llamada a la base de datos en un mismo hilo. En una aplicación, las llamadas a la base de datos son hechas en un orden planificado, y son agrupadas en unidades de trabajo atómicas. (Nótese que esto también significa que tener la conexión en auto-commit después de cada comando SQL es inútil en una aplicación, esta modalidad de trabajo es más acorde con trabajo ad-hoc en una consola SQL). Hibernate inhabilita el auto-commit inmediatamente, (o espera que el servidor de aplicaciones lo haga). Las transacciones de base de datos nunca son opcionales, toda comunicación con la base de datos debe ocurrir dentro de una transacción, ya sea para escribir o para leer datos. Como se dijo anteriormente, el comportamiento "auto-commit" debe evitarse, dado que es improbable que un conjunto de muchas pequeñas transacciones tenga mejor rendimiento que una unidad de trabajo claramente definida. Esta última es también más flexible y extensible.

El patrón más común, en una aplicación cliente/servidor multiusuario, es "una sesión por cada solicitud" (session-per-request) . Según este modelo, una solicitud o "request" del cliente se envía al servidor (en donde está ejecutándose la capa de persistencia Hibernate), se abre una nueva sesión, y todas las operaciones de base de datos son ejecutadas en esta unidad de trabajo. Una vez que el trabajo se haya completado (y la respuesta para el cliente se haya preparado), la sesión sufre un "flush" y se cierra. También se usaría una sola transacción de base de datos para atender la solicitud del cliente, comenzándola y efectuando "commit" cuando se abra y cierre la conexión, respectivamente. La relación entre sesión y transacción es una a una, y este modelo es perfectamente adecuado para muchas aplicaciones.

El desafío radica en la implementación. Hibernate ya trae un sistema incluido que se puede usar para simplificar el uso de este patrón, y manejar lo que se considera la "sesión actual". Todo lo que el programador debe hacer es comenzar una transacción cuando haya que procesar una solicitud (request) al servidor y finalizar la transacción antes de que la respuesta le sea enviada al cliente. Esto se puede lograr de varias maneras, las soluciones más comunes son:

  • un filtro (ServletFilter)
  • un interceptor basado en AOP, que tenga sus "pointcuts" en los métodos del servicio
  • un contenedor de proxies/intercepciones, etc.

Una manera estándar de implementar aspectos que conciernen de una misma forma a varios niveles y áreas de la aplicación (en inglés, "cross-cutting aspects"), es usar un contenedor de EJB, el cual puede implementar transacciones de una manera declarativa y manejada por el contenedor mismo (CMT). Si se decide usar la demarcación de transacciones programática, elija la API de Transaction de Hibernate, que es fácil y portátil.

El código de su aplicación puede acceder a la "sesión actual" para procesar una solicitud, simplemente invocando sessionFactory.getCurrentSession() en cualquier lugar, y tan a menudo como haga falta. Siempre se obtendrá una sesión que estará dentro del alcance o "scope" de la transacción de base de datos actual. Esto tiene que ser configurado, ya sea para entornos de recursos locales o para JTA (véase la Sección 2.5, “Sesiones contextuales”.

A veces es conveniente extender los alcances de la sesión y de la transacción de base de datos hasta que la vista o "view" le haya sido presentada al usuario. Esto es especialmente útil en aplicaciones basadas en servlets, las cuales utilizan una fase separada de presentacíón posterior al procesamiento de la solicitud o "request". Extender la transacción de base de datos hasta que la "vista" sea presentada es fácil si se implementa un interceptor propio. Sin embargo, no es fácil de hacer si uno se apoya en un EJBs con transacciones CMT, dado que la transacción se completará tras el return de los métodos de los EJBs, antes de que la presentación de ninguna vista haya comenzado. Visite el sitio de web de Hibernate para consejos y ejemplos acerca de este patrón Open Session in View.

Conversaciones largas

El patrón "una sesión por solicitud" (session-per-request) no es el único concepto útil que puede usarse para diseñar unidades de trabajo. Muchos procesos de negocios requieren toda una serie de interacciones con el usuario, entretejidas con accesos a la base de datos. En las aplicaciones web y corporativas no es aceptable que una transacción de base de datos dure a lo largo de toda la interacción con el usuario. Considere el siguiente ejemplo:

Aparece la primera ventana de diálogo, los datos que ve el usuario han sido cargados en una sesión y transacción de base de datos en particular. El usuario es libre de modificar los objetos.

El usuario pulsa "Grabar" después de 5 minutos, y espera que sus modificaciones sean hechas persistentes; también espera haber sido la única persona que haya editado esa información, y que no pueda haber ocurrido otras modificaciones conflictivas. A esto le llamamos una "unidad de trabajo"; desde el punto de vista del usuario, una conversación (o transacción de la aplicación). Hay varias maneras de implementar esto en su aplicación.

Una primera implementación ingenua sería mantener la sesión y la transacción de base de datos abiertas durante el tiempo que el usuario se tome para pensar, lo cual ejerce un "bloqueo" sobre la base de datos para impedir modificaciones concurrentes, y garantizar aislamiento y atomicidad. Esto es, por supuesto, una práctica a evitar o "anti-patrón" (anti-pattern), dado que los conflictos de bloqueo no le permitirán a nuestra aplicación adaptarse a una mayor demanda por usuarios concurrentes.

Claramente, debemos usar varias transacciones a la base de datos para implementar la conversación. En este caso, mantener un aislamiento entre los procesos de negocio se vuelve, en parte, responsabilidad de la capa de la aplicación. Una simple conversación normalmente abarca varias transacciones de base de datos. Será atómica si sólo una de dichas transacciones (la última) es la que almacena los datos modificados, todas las otras simplemente leen datos (por ejemplo, en una ventana de diálogo tipo "paso a paso" o "wizard", que abarque varios ciclos solicitud/respuesta). Esto es más fácil de implementar de lo que parece, especialmente si se utilizan las ventajas que provee Hibernate:

  • Versionado automático: Hibernate puede efectuar automáticamente un control de concurrencia optimista, puede detectar automáticamente si ocurrió una modificación durante el tiempo que el usuario se tomó para reaccionar. Usualmente, esto sólo se verifica al final de la conversación.
  • Objetos desprendidos: si se decide usar el patrón "una sesión por solicitud" (session-per-request), todas las instancias cargadas se convertirán en "desprendidas" (detached) durante el tiempo que el usario se tome para pensar. Hibernate le permite reasociar estos objetos y persistir las modificaciones; este patrón se llama "una sesión por solicitud con objetos desprendidos". Para aislar modificaciones concurrentes se usa el versionado automático.
  • Sesión larga (o "extendida"): la sesión de Hibernate puede desconectarse de la conexión JDBC subyacente después de que la transacción haya ejecutado su commit, y reconectada cuando ocurra una nueva solicitud del cliente. Este patrón se conoce como "una sesión por conversación" y hace que la reasociación de objetos sea innecesaria. Para aislar modificaciones concurrentes se usa versionado automático, y a la sesión no se le permite hacer "flush" automático, si no explícito.

Ambos patrones,  "una sesión por solicitud" y "una sesión por conversación" tienen ventajas y desventajas. Las discutiremos más adelante, en el contexto del control de concurrencia optimista.

Considerar la identidad de los objetos

Una aplicación puede aceder en forma concurrente al mismo estado persistente en dos sesiones diferentes. Pero una instancia de una clase persistente nunca se comparte entre dos instancias de Session. Por lo tanto, hay dos nociones diferentes de identidad:

Identidad de base de datos
foo.getId().equals(bar.getId())

Identidad de JVM
foo==bar

Entonces, para objetos asociados a una sesión en particular, (esto es, en el mismo alcance o "scope" de una sesión) las dos nociones son equivalentes, e Hibernate garantiza que la identidad JVM equivale a la identidad de Base de Datos. Sin embargo, si la aplicación accede en forma concurrente al mismo dato, puede ocurrir que la misma identidad persistente esté contenida en dos instancias de objeto en dos sesiones distintas. En este caso la identidad persistente o de base de datos existe, pero la identidad de JVM no, los objetos son "diferentes". Estos conflictos se resuelven usando versionado automático (cuando ocurren los "flush"/"commit"), usando el enfoque optmista.

Este enfoque deja que Hibernate se preocupe por la concurrencia. También provee la mejor "escalabilidad", dado que garantizar la identidad sólo a nivel de unidades de trabajo en un hilo simple no requiera un bloqueo costoso ni otros medios de sincronización. La aplicación no necesita sincronizar ningún objeto, siempre y cuando se atenga a que se usará un solo hilo por sesión. Dentro de una sesión, la aplicación puede usar == tranquilamente para comparar objetos.

Sin embargo, una aplicación que use == fuera de una sesión, se puede topar con resultados inesperados. Esto puede ocurrir incluso en lugares insólitos, por ejemplo, si se colocan dos instancias desprendidas en el mismo Set, existe la posibilidad de que ambas tengan la misma identidad de base de datos (es decir, que representen la misma fila) pero no la misma identidad JVM. El programador debe sustituir los métodos equals() y hashCode() en las clases persistentes, e implementar su propia noción de igualdad entre objetos. Sólo una advertencia: nunca use el identificador de base de datos para implementar igualdad; use una "clave de negocios", una combinación única y normalmente inmutable de atributos. Si la instancia transitoria es almacenada en un Set, cambiar el hashcode rompe el contrato del Set. Los atributos de las "claves de negocio" no necesitan ser tan estables como las claves primarias de una base de datos. Sólo necesitan poder establecer, de manera estable, diferencias o igualdad entre los objetos que estén en un Set. También note que éste no es un problema de Hibernate, sino de la manera en que los objetos de Java implementan identidad e igualdad.

Problemas comunes

Nunca use los anti-patrones" una sesión por cada interacción con el usuario" ni "una sesión para toda la aplicación" (por supuesto, puede haber raras excepciones a esta regla). Note que los problemas que listamos a continuación pueden aparecer incluso si se están usando los patrones que sí recomendamos. Asegúrese de que entiende las implicancias de la decisión de diseño que tome.

Una sesión (Session) no es segura en cuanto a acceso concurrente por múltiples hilos(no es "thread-safe"). Las cosas que se supone que funcionan en forma concurrente, como las HTTP requests, los session beans, o los workers de Swing workers, causarían condiciones de conflicto por recursos conocidas como "race conditions" si la instancia de una sesión de Hibernate fuese compartida. Si la sesión de Hibernate está contenida en una sesión de HTTP (HttpSession, la cual se discute más adelante), se debería considerar el sincronizar el acceso a la sesión HTTP. De otra manera, cualquier usuario que cliquee "reload" lo suficientemente rápido, es probable que termine usando la misma sesión (de Hibernate) en dos hilos ejecutándose en forma concurrente.

Si Hibernate produce una excepción, significa que hay que deshacer(rollback) la transacción de base de datos, y cerrar la sesión inmediatamente (como se discute luego en más detalle). Si la sesión está asociada a la aplicación, debe detenerse la aplicación. Efectuar un "rollback" de la transacción no restaura los objetos de negocio al estado en el que estaban antes de que la transacción ocurriese. Esto significa que el estado de la base de datos y el de los objetos de negocio sí quedan fuera de fase. Normalmente esto no es problema, porque las excepciones no son recuperables, y es necesario comenzar todo tras un "rollback".

La sesión almacena en su caché cada objeto que esté en estado persistente (habiendo vigilado y comprobado Hibernate si su estado es "sucio"). Esto significa que crecerá para siempre, hasta provocar un error de memoria (OutOfMemoryException) si se la deja abierta por mucho tiempo o simplemente se le cargan demasiados datos. Una solución para esto, es invocar clear() y evict() para manejar el caché de la sesión, pero lo que se debería considerar es un procedimiento de base de datos almacenado (stored procedure) si se necesitan operaciones masivas de datos. Mantener una sesión abierta durante toda la interacción con el usuario también aumenta la probabilidad de que los datos se vuelvan equivocos.

Demarcación de transacciones de base de datos

La demarcación de las transacciones de base de datos (o de sistema) siempre es necesaria. No puede ocurrir ninguna comunicación con la base de datos si no es dentro de una transacción. (Esto parece confundir a algunos programadores acostumbrados a trabajar siempre en modo "auto-commit"). Siempre deben emplearse límites de transacción claros, incluso para las operaciones de lectura. Dependiendo del nivel de aislamiento y de las posibilidades de la base de datos, esto puede ser opcional, pero no hay ninguna desventaja ni se puede dar ninguna razón en contra de demarcar explícitamente, siempre, los límites de una transacción. Una única transacción de base de datos, ciertamente tendrá mucho mejor rendimiento que múltiples pequeñas transacciones, incluso al leer datos.

Una aplicación de Hibernate puede ser ejecutada en un entorno "no manejado", "no administrado" (es decir, autosuficiente, una simple aplicación de web o Swing), y también en entornos J2EE "administrado" (managed environments). En un entorno "no manejado", Hibernate es responsable por su propio "pool" de conexiones. El programador tiene que establecer los limites de las transacciones manualmente (en otras palabras, invocar los métodos begin, commit y rollback de las transacciones él mismo). Un entorno "administrado", normalmente provee transacciones administradas por el contenedor ("container-managed transactions o CMT por sus siglas en inglés), con la disposición de las transacciones definida declarativamente en los archivos de descripción de despliegue o "deployment descriptors" de los session beans EJB, por ejemplo. En tal caso, la demarcación manual o "programática" de las transacciones no es necesaria.

Sin embargo, a veces es deseable mantener la capa de persistencia portátil entre entornos administrados y no administrados. Utilizar la demarcación programática se puede usar, en ambos casos. Hibernate ofrece una API llamada Transaction que se traduce en el sistema nativo de transacciones del entorno en el cual el programa haya sido desplegado. Esta API es opcional, pero recomendamos usarla siempre, a menos que el código esté en un session bean, dentro de un servidor con CMT.

Usualmente, finalizar una sesión involucra las siguientes fases:

  • invocar el "flush" de la sesión
  • invocar "commit" en la transacción de base de datos
  • cerrar la sesión
  • manejar las excepciones que hayan podido ocurrir
Entornos no administrados

Si la capa de persistencia e Hibernate se ejecutan en un entorno no-administrado, las conexiones a la base de datos usualmente son manejadas por un "pool" de conexiones simple (es decir, no una DataSource) del cual Hibernate obtiene las conexiones que necesite. El estilo de manejo de sesión/tranascción se ve así:

// Entorno no-administrado
Session sess = factory.openSession();
Transaction tx = null;
try {
    tx = sess.beginTransaction();

    // efectuar algo de trabajo
    ...

    tx.commit();
}
catch (RuntimeException e) {
    if (tx != null) tx.rollback();
    throw e; // o mostrar un mensaje de error
}
finally {
    sess.close();
}

No hace falta invocar el "flush" de la sesión explícitamente: la llamada a commit() dispara automáticamente la sincronización (dependiendo del Modo de "flush" para la sesión). Una llamada a close() marca el fin de la sesión. La implicación más importante de close() es que la conexión JDBC será cedidad por la sesión. Este código Java es portátil, y puede ejecutarse tanto en entornos administrados como no administrados.

Una solución mucho más flexible (que ya viene incorporada en Hibernate), es el manejo del contexto de "sesión actual", como se describió anteriormente.

// Estilo para un entorno no-administrado  con getCurrentSession()
try {
    factory.getCurrentSession().beginTransaction();

    // efectuar algo de trabajo
    ...

    factory.getCurrentSession().getTransaction().commit();
}
catch (RuntimeException e) {
    factory.getCurrentSession().getTransaction().rollback();
    throw e; // o mostrar un mensaje de error
}

Se podrán encontrar fragmentos de código como éstos en cualquier aplicación normal. Las excepciones fatales, de sistema, deberían ser capturadas en la capa superior. En otras palabras, el código que ejecuta las llamadas a Hibernate, en la capa de persistencia, y el código que maneja las RuntimeExceptions (y normalmente hace las tareas de limpeza y salida) están en capas diferentes. El "manejo de contexto actual" hecho por Hibernate puede simplificar este diseño considerablemente, todo lo que se necesita es acceso a la SessionFactory.

Note que debería seleccionarse org.hibernate.transaction.JDBCTransactionFactory (el valor pord efecto), y, para el segundo ejemplo, el valor "thread" para hibernate.current_session_context_class.

Usar JTA

Si la capa de persistencia se ejecuta en un servidor de aplicaciones (por ejemplo, detrás de EJB session beans), cada conexión de base de datos obtenida por Hibernate será automáticamente parte de la transacción global JTA. También se puede instalar una implementación JTA autónoma, y usarla sin EJB. Hibernate ofrece dos estrategias para integración con JTA:

Si se usan transacciones manejadas por beans (bean-managed transactions o BMT por sus siglas en inglés), Hibernate le dirá al servidor de aplicaciones que empiece y finalice una transaccción BMT si se usa la API de Transaction API. De este modo, el código e manejo de transacciones para un entorno no administrado es idéntico al de un entorno administrado.

// estilo BMT
Session sess = factory.openSession();
Transaction tx = null;
try {
    tx = sess.beginTransaction();

    // efectuar algo de trabajo
    ...

    tx.commit();
}
catch (RuntimeException e) {
    if (tx != null) tx.rollback();
    throw e; // o mostrar un mensaje de error
}
finally {
    sess.close();
}

Si se desea usar la sesión asociada a una transacción, es decir, la funcionalidad getCurrentSession() para una propagación más fácil del contexto, se debe usar la API de JTA UserTransaction directamente:

// estilo BMT con getCurrentSession()
try {
    UserTransaction tx = (UserTransaction)new InitialContext()
                            .lookup("java:comp/UserTransaction");

    tx.begin();

    //  efectuar algo de trabajo con la sesión asociada a la transacción
    factory.getCurrentSession().load(...);
    factory.getCurrentSession().persist(...);

    tx.commit();
}
catch (RuntimeException e) {
    tx.rollback();
    throw e; // o mostrar un mensaje de error
}

Con CMT, la demarcación de transacciones se hace en los descriptores de despliegue (deployment descriptors) del session bean, no se hace programáticamente. Así que el código queda reducido a:

// estilo CMT
 Session sess = factory.getCurrentSession();

 //  efectuar algo de trabajo
 ...

En un entorno CMT/EJB incluso el rollback ocurre automáticamente, puesto que una RuntimeException no capturada emitida por el método de un session bean le dice al contenedor que efectúe rollback en la transacción global. Esto significa que no hace falta usar la API de Transaction de Hibernate en absoluto con BMT or CMT, e igualmente se obtiene propagación automática de la sesión "actual" asociada a la transacción.

Cuando se elige la fábrica (factory) de transacciones, note que debería elegirse org.hibernate.transaction.JTATransactionFactory si se usa JTA directamente (BMT), y debería usarse org.hibernate.transaction.CMTTransactionFactory en un session bean CMT. Más aún, asegúrese de que su hibernate.current_session_context_class ha sido o bien eliminado (por compatibilidad hacia a trás o "backwards compatibility"), o bien puesto a "jta".

La operación getCurrentSession() tiene una desventaja en un entorno JTA: Hay una advertencia sobre el uso del modo de liberación de conexiones after_statement, el cual es el valor por defecto. Debido a una limitación de la especificación JTA, para Hibernate no es posible limpiar automáticamente instancias no cerradas de ScrollableResults o Iterator que hayan sido devueltas por scroll() o iterate(). Usted debe liberar el cursor de base de datos subyacente invocando explícitamente a ScrollableResults.close() o Hibernate.close(Iterator) en el bloque finally. (Por supuesto, la mayoría de las aplicaciones pueden fácilmente evitar usar scroll() o iterate() en el código JTA o CMT).

Manejo de excepciones

Si la sesión provoca una excepción (incluida cualquier SQLException), se debería efectuar un rollback de la transacción de base de datos inmediatamente, invocar Session.close() y descartar la instancia de la sesión. Algunos métodos de Session no dejarán a la sesión en un estado consistente. Ninguna excepción generada por Hibernate puede ser tratada como recuperable. Asegúrese de que la sesión será cerrada invocando close() en el bloque finally.

HibernateException, la cual envuelve la mayoría de los errores que ocurren en la capa de persistencia de Hibernate, es una excepción del tipo "unchecked" (esto es, que no requiere captura obligatoriamente en tiempo de compilación). No lo era en versiones anteriores de Hibernate. En nuestra opinión, no se debería forzar al programador a capturar excepciones de bajo nivel en la capa de persistencia. En la mayoría de los sistemas, las excepciones "unchecked" son manejadas en uno de los primeros "marcos" de la pila de invocaciones a métodos (es decir, en las capas más "altas" de la aplicación), y se le presenta un mensaje de error al usuario de la aplicación, o se adopta algún otro curso de acción apropiado. Note que Hibernate puede también emitir otras exceptiones, además de HibernateException. Éstas son, de nuevo, no recuperables, y se debe adoptar las medidas apropiadas para tratar con ellas.

Hibernate envuelve las excepciones SQLException generadas al interactuar con la base de datos en una JDBCException. De hecho, Hibernate intentará convertir la excepción en una subclase de JDBCException que tenga más sentido. La SQLException subyacente está siempre disponible via JDBCException.getCause(). Hibernate convierte las SQLException en subclases apropiadas de JDBCException usando el conversor SQLExceptionConverter que está asociado a la fábrica SessionFactory. Por defecto, el SQLExceptionConverter es definido de acuerdo al dialecto SQL elegido. Pero es posible enchufar una implementación a medida (ver los javadocs para la clase SQLExceptionConverterFactory por detalles). Los subtipos estándar de JDBCException son:

  • JDBCConnectionException: indica un error en la comunicación con la JDBC subyacente
  • SQLGrammarException: indica un error sintáctico o gramatical con el SQL emitido
  • ConstraintViolationException: indica alguna forma de violación de una constraint de integridad
  • LockAcquisitionException: indica un error al adquirir el nivel de bloqueo necesario para efectuar la operación solicitada
  • GenericJDBCException: una excepción genérica que no cae en ninguna de las otras categorías
Expiración de transacciones

Una característica extremadamente importante provista por un entorno aministrado, como EJB, la cual nunca es provista por un entorno no administrado, es la expiración de las transacciones, o "transaction timeout". Los "timeouts" de las transacciones hacen que ninguna transacción "rebelde" ocupe indefinidamente los recursos del sistema sin devolverle respuesta alguna al usuario. Fuera de un entorno administrado (JTA), Hibernate no puede proveer esta funcionalidad en forma completa. Sin embargo, puede al menos controlar las operaciones de acceso a datos, asegurándose de que los "puntos muertos" (deadlocks) y las consultas con resultados enormes estén limitadas a un tiempo definido. En un entorno administrado, Hibernate puede delegar el "timeout" de las transacciones en JTA. Esta funcionalidad es abstraída por el objeto Transaction de Hibernate.

Session sess = factory.openSession();
try {
    //set transaction timeout to 3 seconds
    sess.getTransaction().setTimeout(3);
    sess.getTransaction().begin();

    // do some work
    ...

    sess.getTransaction().commit()
}
catch (RuntimeException e) {
    sess.getTransaction().rollback();
    throw e; // or display error message
}
finally {
    sess.close();
}

Note que setTimeout() no puede ser llamada desde un bean en CMT, en donde los "timeouts" de las transacciones son definidos declarativamente

Caché

El caché de segundo nivel

Una sesión (Session) de Hibernate, es un caché de datos persistentes a nivel de la transacción. Es posible configurar un caché a nivel de cluster, o a nivel de la JVM (a nivel de la SessionFactory o fábrica de sesiones), que se aplique a una o más clases en particular, y/o a una o más colecciones en particular. Se puede incluso conectar un caché en cluster. Hay que tener cuidado, porque los cachés nunca están al tanto de los cambios que le hayan sido hechos al repositorio de datos persistente por otra aplicación (aunque sí pueden ser configurados para que sus datos cacheados expiren regularmente luego de un cierto tiempo).

Existe la opción de decirle a Hibernate qué implementación de cacheo usar, especificando el nombre de una clase que implemente org.hibernate.cache.CacheProvider, usando la propiedad hibernate.cache.provider_class. Hibernate trae incorporada una buena cantidad de integraciones con proveedores de cachés open-source. Además, se puede implementar un caché propio y conectarlo.

  • Estrategia de sólo-lectura: Si su aplicación necesita leer pero nunca modificar las instancias de una clase persistente, se puede usar un caché read-only. Esta es la estrategia más simple y la de mejor rendimiento. También es perfectamente segura de utilizar en un cluster.
  • Estrategia de lectura-escritura: Si la aplicación necesita actualizar datos, puede ser apropiado usar una caché de lectura-escritura (read-write). Esta estrategia de cacheo nunca debería ser usada si se requiere aislamiento de transacciones serializables. Si el caché se usa en un entorno JTA, se debe especificar la propiedad hibernate.transaction.manager_lookup_class, especificando una estrategia para obtener la propiedad TransactionManager de JTA. En otros entornos, hay que asegurarse de que la transacción esté completa para cuando ocurran Session.close() o Session.disconnect(). Si se quiere usar esta estrategia en un cluster, hay que asegurarse de que la implementación subyacente de caché soporta "locking". Los proveedores de caché que vienen ya incorporados no lo soportan.
  • Estrategia de lectura-escritura no estricta: Si la aplicación necesita actualizar datos, pero sólo ocasionalmente (es decir, si es improbable que dos transacciones traten de actualizar el mismo ítem simultáneamente), y no se requiere un aislamiento de transacciones estricto, puede ser apropiado un caché "de lectura-escritura no estricta" (nonstrict-read-write). Si el caché se usa en un entorno JTA, se debe especificar hibernate.transaction.manager_lookup_class. En otros entornos, hay que asegurarse de que la transacción esté completa para cuando ocurran Session.close() o Session.disconnect().
  • Estrategia transaccional: La estrategia transaccional (transactional) provee soporte para proveedores de caché enteramente transaccionales, como JBoss TreeCache. Tales cachés sólo pueden ser usados en un entorno JTA, y se debe especificar hibernate.transaction.manager_lookup_class.

Administrar los cachés

Siempre que un objeto se le pase a save(), update() o saveOrUpdate(), y cada vez que se obtenga un objeto usando load(), get(), list(), iterate() o scroll(), dicho objeto se agrega al caché interno de la sesión.

Cuando a continuación se invoca flush(), el estado de dicho objeto será sincronizado con la base de datos. Si no se quiere que esta sincronización ocurra, o si se está procesando una cantidad inmensa de objetos que haga necesario manejar la memoria eficientemente, se puede usar el método evict() para quitar objetos y sus colecciones del caché de primer nivel.

ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //un resultset inmenso
while ( cats.next() ) {
    Cat cat = (Cat) cats.get(0);
    doSomethingWithACat(cat);
    sess.evict(cat);
}

La Session también provee un método contains() para determinar si una instancia ya pertenece al caché de sesión. Para desalojar a todos los objetos del caché de sesión, llame Session.clear(). Para el caché de segundo nivel, hay métodos definidos en SessionFactory para desalojar el estado cacheado de una instancia, de toda una clase, de la instancia de una colección, o de un rol de colección completo.

sessionFactory.evict(Cat.class, catId); //desaloja una instancia de Cat en particular
sessionFactory.evict(Cat.class);  //desaloja todos los Cats
sessionFactory.evictCollection("Cat.kittens", catId); //desaloja un colección de kittens en particular
sessionFactory.evictCollection("Cat.kittens"); //desaloja todas las colecciones de kittens

CacheMode controla cómo una sesión en particular interactúa con el caché de segundo nivel.

  • CacheMode.NORMAL: lee y escribe items en el caché de segundo nivel
  • CacheMode.GET: lee items del caché de segundo nivel, pero no escribe en el caché de segundo nivel excepto cuando se actualicen datos.
  • CacheMode.PUT: escribe items en el caché de segundo nivel, pero no lee.
  • CacheMode.REFRESH: escribe items en el caché de segundo nivel, pero lee del caché de segundo nivel, elude los efectos de hibernate.cache.use_minimal_puts, forzando un refresco del caché de segundo nivel para todos los items leídos de la base de datos.

Para navegar por los contenidos del caché de segundo nivel, o de una región de cacheo de consultas, use la API de Statistics:

Map cacheEntries = sessionFactory.getStatistics().getSecondLevelCacheStatistics(regionName).getEntries();

Necesitará habilitar estadísticas, y, optativamente, forzar a Hibernate a que escriba las entradas de caché en un formato más legible :

hibernate.generate_statistics true
hibernate.cache.use_structured_entries true

El caché de consultas (query cache)

Los resultados de las consultas también pueden ser cacheados. Esto sólo es útil para consultas que son ejecutadas frecuentemente, y con los mismos parámetros. Para usar el caché de consutas, primero hay que habilitarlo:

hibernate.cache.use_query_cache = true

Esta configuración provoca la creación de dos nuevas regiones de caché: una que contendrá los resultados cacheados de las consultas (org.hibernate.cache.StandardQueryCache), y la otra conteniendo la fecha y hora de las actualizaciones más recientes hechas en las tablas "consultables" (org.hibernate.cache.UpdateTimestampsCache). Note que el caché de consultas no cachea el estado de las entidades contenidas en el resultado; cachea solamente los valores de los identificadores, y los resultados de tipo "value type". Así que el caché de consultas siempre debería ser usado en conjunción con el caché de segundo nivel.

A la mayoría de las consultas no les representa ninguna ventaja el ser cacheadas, así que las consultas no se cachean por defecto. Para habilitar el cacheo, invoque Query.setCacheable(true). Esta llamada permite que la consulta, al ser ejecutada, busque resultados ya existentes, o agregue sus resultados al caché.

Si se require un control más granular sobre las políticas de expiración de los cachés, se puede especificar una región de caché para una consulta en particular, invocando Query.setCacheRegion().

List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
    .setEntity("blogger", blogger)
    .setMaxResults(15)
    .setCacheable(true)
    .setCacheRegion("frontpages")
    .list();

Si la consulta forzara un refresco de esta región de caché en particular, habría que invocar Query.setCacheMode(CacheMode.REFRESH). Esto es particularmente útil para los casos en donde los datos subyacentes pudieran haber sido actualizados por un proceso separado (por ejemplo, no por Hibernate), y permite a la aplicación refrescar selectivamente un resultado en particular. Esto es más eficiente que el desalojo de toda una región de cachés via SessionFactory.evictQueries().

Ejemplos

Dentro del catálogo interno de la Junta de Andalucía se encuentra el proyecto CRIJA, en el cual se hace uso de Hibernate. Este proyecto se encarga del mantenimiento del censo de equipos microinformáticos.

En este proyecto existe un fichero de configuración denominado persistence.xml en el que se define el acceso a BBDD:

<?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence&#95;1&#95;0.xsd">
   
    <persistence-unit name="default&#95;manager" transaction-type="RESOURCE&#95;LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>java:comp/env/POOL&#95;JDBC</jta-data-source> 
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect"/>
            <property name="hibernate.max&#95;fetch&#95;depth" value="3"/>
            <property name="hibernate.default&#95;schema" value="inventario"/>
        </properties>
    </persistence-unit>       
</persistence>

Dentro su código se encuentra definida la clase BienCbhVO cuya definición es la siguiente:

@Entity
@Table(name="`INV&#95;BIEN&#95;CBH`")
public class BienCbhVO implements VO {

Esta clase contiene un conjunto de atributos que representan a cada una de las columnas de la tabla a la cual hace referencia.

/&#42; Copyright (C) 2007 Viavansi Servicios Avanzados para las Instituciones S.L. (VIAVANSI)
   Se permite la libre distribución y modificación de esta librería bajo los
    ...
&#42;/

package com.viavansi.inventario.persistencia.VO;

//Persistence
import javax.persistence.&#42;;
import com.viavansi.framework.core.entidades.VO;

/&#42;&#42;
 &#42; Entidad de la aplicación, mapeada sobre la tabla :INVENTARIO.INV&#95;BIEN&#95;CBH .
 &#42; Implementación del patron Value Object para encapsular datos de negocio     gestionados por JPA.
 &#42; It's Encapsulate the business data.
 &#42;/
@Entity
@Table(name="`INV&#95;BIEN&#95;CBH`")
public class BienCbhVO implements VO {

    private static final long serialVersionUID=4745268235649285013L;
    /&#42;&#42;
    &#42; This field corresponds to the database column INVENTARIO.BIEN&#95;CBH.IDBIEN
    &#42;/
    private Long idbien;
   
    /&#42;&#42;
    &#42; This field corresponds to the database column INVENTARIO.BIEN&#95;CBH.COD&#95;CBH
    &#42;/
    private Long codCbh;

    /&#42;&#42;
    &#42; This field corresponds to the database column                         INVENTARIO.BIEN&#95;CBH.IDPROVEEDOR
    &#42;/
    private Long idproveedor;
   
    /&#42;&#42;
    &#42; This field corresponds to the database column INVENTARIO.BIEN&#95;CBH.PRECIO
    &#42;/
    private java.math.BigDecimal precio;

     /&#42;&#42; This method returns the value of the database column         INVENTARIO.BIEN&#95;CBH.IDBIEN
     &#42;
     &#42; @return the value of INVENTARIO.BIEN&#95;CBH.IDBIEN
     &#42;/
    @Id
    @SequenceGenerator(name="idbienGenerator",sequenceName="INV&#95;SEQ&#95;BIEN&#95;CBH")
    @GeneratedValue(strategy = GenerationType.AUTO,generator="idbienGenerator")
    @Column(name="IDBIEN")
    public Long getIdbien() {
        return idbien;
    }

    /&#42;&#42;
    &#42; This method sets the value of the database column INVENTARIO.BIEN&#95;CBH.IDBIEN
    &#42;
    &#42; @param Idbien the value for INVENTARIO.BIEN&#95;CBH.IDBIEN
    &#42;/
    public void setIdbien(Long idbien) {
        this.idbien = idbien;
    }
     /&#42;&#42; This method returns the value of the database column INVENTARIO.BIEN&#95;CBH.COD&#95;CBH
     &#42;
     &#42; @return the value of INVENTARIO.BIEN&#95;CBH.COD&#95;CBH
     &#42;/

    @Column(name="COD&#95;CBH")
    public Long getCodCbh() {
        return codCbh;
    }

    /&#42;&#42;
    &#42; This method sets the value of the database column INVENTARIO.BIEN&#95;CBH.COD&#95;CBH
    &#42;
    &#42; @param CodCbh the value for INVENTARIO.BIEN&#95;CBH.COD&#95;CBH
    &#42;/
    public void setCodCbh(Long codCbh) {
        this.codCbh = codCbh;
    }
     /&#42;&#42; This method returns the value of the database column INVENTARIO.BIEN&#95;CBH.IDPROVEEDOR
     &#42;
     &#42; @return the value of INVENTARIO.BIEN&#95;CBH.IDPROVEEDOR
     &#42;/

    @Column(name="IDPROVEEDOR")
    public Long getIdproveedor() {
        return idproveedor;
    }

    /&#42;&#42;
    &#42; This method sets the value of the database column INVENTARIO.BIEN&#95;CBH.IDPROVEEDOR
    &#42;
    &#42; @param Idproveedor the value for INVENTARIO.BIEN&#95;CBH.IDPROVEEDOR
    &#42;/
    public void setIdproveedor(Long idproveedor) {
        this.idproveedor = idproveedor;
    }
     /&#42;&#42; This method returns the value of the database column INVENTARIO.BIEN&#95;CBH.PRECIO
     &#42;
     &#42; @return the value of INVENTARIO.BIEN&#95;CBH.PRECIO
     &#42;/

    @Column(name="PRECIO")
    public java.math.BigDecimal getPrecio() {
        return precio;
    }

    /&#42;&#42;
    &#42; This method sets the value of the database column                         INVENTARIO.BIEN&#95;CBH.PRECIO
    &#42;
    &#42; @param Precio the value for INVENTARIO.BIEN&#95;CBH.PRECIO
    &#42;/
    public void setPrecio(java.math.BigDecimal precio) {
        this.precio = precio;
    }
    /&#42;&#42;
    &#42; Returns a string representation of the object.  This implementation               constructs
    &#42; that representation based on the id fields.
    &#42; @return a string representation of the object.
    &#42;/
    @Override
    public String toString() {
        StringBuffer output=new StringBuffer();
        output.append("com.viavansi.inventario.persistencia.VO.BienCbhVO=\n");
        output.append("idbien="+idbien+"\n");
        output.append("codCbh="+codCbh+"\n");
        output.append("idproveedor="+idproveedor+"\n");
        output.append("precio="+precio+"\n");
        return output.toString();
    }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (!super.equals(obj))
                return false;
            if (getClass() != obj.getClass())
                return false;
            final BienCbhVO other=(BienCbhVO) obj;
             if (! idbien.equals(other.idbien)){
                return false;
            }  
            return true;
        }
}

En esta clase se encuentran atributos que representan las columnas de la tabla a la cual hace referencia la clase. Dentro de estos atributos se encuentra el que representa la clave primaria, idBien, el cual tiene asociado una secuencia en BBDD denominada INV_SEQ_BIEN_CBH Una vez que todas las tablas del esquema se encuentran representadas ya podrían realizarse numerosas operaciones sobre ellas (insert, update, delete, select). En caso de querer obtener el listado de proveedores de la tabla INV_PROVEEDOR será necesario realizar la siguiente llamada:

/&#42;&#42;
     &#42;     Genera el listado de Proveedor para el combo de selección.
     &#42;/
    protected void generarComboProveedor(){
        comboProveedor= new LinkedList<SelectItem>();
        //comboProveedor.add(0, new SelectItem(-1L,"(No seleccionado)"));
        try{
        ProveedorBO bo= ProveedorBO.getCurrentInstance();
            // listado de entidades disponibles en la combo
        Collection<ProveedorVO> list=  bo.findAllOrderedBy("denominacion");
                // recorro la colección generando la  combo
        for (ProveedorVO vo : list) {  
            String label=vo.getDenominacion() == null?"":vo.getDenominacion().toString();
                    comboProveedor.add(new SelectItem(vo.getIdproveedor(),label));
            }
        }catch(Exception e){
            addWarningGlobal("WARNING&#95;COMBO&#95;NOT&#95;FOUND&#95;PROVEEDOR",e.getMessage());
         }  
    }

El método bo.findAllOrderedBy("denominacion"); se encarga de recuperar todos los objetos de la tabla INV_PROVEEDOR ordenados por el campo que se le pasa como parámetro, denominación, en este caso. No obstante, también es posible utilizar consultas personalizadas a través del objeto del tipo EntityManager. Por ejemplo:

/&#42;&#42; 
     &#42; Recupera todos los objetos CentroComposite.
     &#42; @return Todo el conjunto de entidades CentroComposite.
     &#42; @throws ExcepcionDatosNoEncontrados
     &#42; @throws ExcepcionServicio
     &#42;/
@SuppressWarnings("unchecked")
public List<CentroComposite> findAll() throws ExcepcionDatosNoEncontrados, ExcepcionServicio{
         List<CentroComposite> c=new ArrayList<CentroComposite>();
      CentroComposite composite;
    EntityManager manager = getEntityManager();
    try {
        Query query =     manager.createQuery(ejb&#95;ql&#95;select+ejb&#95;ql&#95;from+ejb&#95;ql&#95;join);
        List<Object&#91;&#93;> results=query.getResultList();
        if (results!=null){
            for (Object&#91;&#93; result : results){
                if (result!=null){
                    composite=decode(result);
                    c.add(composite);
                }
            }
        }
    }catch (ExcepcionServicio e){
        throw e;
    }finally {
        manager.close();
    }
    if (c==null || (c!=null && c.size()==0)){
        throw new ExcepcionDatosNoEncontrados();
    }else{
        return c;
    }       
    }

Donde ejb_ql_select+ejb_ql_from+ejb_ql_join representa la cadena:

SELECT vo1.idcentro,  vo1.denominacion,  vo1.observaciones,  vo1.fechaAlta,  vo1.fechaBaja,  vo1.fechaUltModif,  vo2.idorganismo,  vo2.denominacion,  vo2.codOrganismo,  vo2.observaciones,  vo2.fechaAlta,  vo2.fechaBaja,  vo2.fechaUltModif,  vo3.idpersona,  vo3.nombreCompleto,  vo3.tlfFijo,  vo3.tlfMovil,  vo3.email,  vo3.idorganismo,  vo3.puestoTrabajo  
   FROM CentroVO vo1, OrganismoVO vo2, PersonaVO vo3 
      WHERE vo1.idorganismo=vo2.idorganismo
       AND vo1.responsable=vo3.idpersona