Hibernate.org Community Documentation

- Anterior
- Siguiente

## Capítulo 13. Transacciones y concurrencia

El punto más importante sobre Hibernate y el control de concurrencia es que es fácil de comprender. Hibernate usa directamente conexiones JDBC y recursos JTA sin agregar ningún comportamiento de bloqueo adicional. Le recomendamos bastante que tome algo de tiempo con la especificación de JDBC, ANSI y el aislamiento de transacciones de su sistema de gestión de base de datos.

Hibernate no bloquea objetos en la memoria. Su aplicación puede esperar el comportamiento definido por el nivel de aislamiento de sus transacciones de las bases de datos. Gracias a la `Session`, la cual también es un caché con alcance de transacción, Hibernate proporciona lecturas repetidas para búsquedas del identificador y consultas de entidad y no consultas de reporte que retornan valores escalares.

Además del versionado del control de concurrencia optimista automático, Hibernate también ofrece una API (menor) para bloqueo pesimista de filas, usando la sintáxis `SELECT FOR UPDATE`. Esta API y el control de concurrencia optimista se discuten más adelante en este capítulo.

Comenzamos la discusión del control de concurrencia en Hibernate con la granularidad de `Configuration`, `SessionFactory` y `Session`, así como las transacciones de la base de datos y las conversaciones largas.

## 13.1. Ámbitos de sesión y de transacción

Una `SessionFactory` es un objeto seguro entre hilos y costoso de crear pensado para que todas las hebras de la aplicación lo compartan. Se crea una sola vez, usualmente en el inicio de la aplicación, a partir de una instancia `Configuration`.

Una `Session` es un objeto de bajo costo, inseguro entre hilos que se debe utilizar una sola vez y luego se debe descartar: para un sólo pedido, una sola conversación o una sóla unidad de trabajo. Una `Session` no obtendrá una `Connection` JDBC o un `Datasource` a menos de que sea necesario. No consumirá recursos hasta que se utilice.

Una transacción de la base de datos tiene que ser tan corta como sea posible para reducir la contención de bloqueos en la base de datos. Las transacciones largas de la base de datos prevendrán a su aplicación de escalar a una carga altamente concurrente. Por lo tanto, no se recomienda que mantenga una transacción de la base de datos abierta durante el tiempo para pensar del usuario, hasta que la unidad de trabajo se encuentre completa.

¿Cuál es el ámbito de una unidad de trabajo? ¿Puede una sola `Session` de Hibernate extenderse a través de varias transacciones de la base de datos o ésta es una relación uno-a-uno de ámbitos? ¿Cuándo debe abrir y cerrar una `Session`? y ¿cómo demarca los límites de la transacción de la base de datos? En las siguientes secciones abordaremos estas preguntas.

### 13.1.1. Unidad de trabajo

First, let's define a unit of work. A unit of work is a design pattern described by Martin Fowler as “ [maintaining] a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. ”[PoEAA] In other words, its a series of operations we wish to carry out against the database together. Basically, it is a transaction, though fulfilling a unit of work will often span multiple physical database transactions (see Sección 13.1.2, “Conversaciones largas”). So really we are talking about a more abstract notion of a transaction. The term "business transaction" is also sometimes used in lieu of unit of work.

Primero, no use el antipatrón sesión-por-operación: no abra y cierre una `Session` para cada llamada simple a la base de datos en un solo hilo. Lo mismo aplica para las transacciones de base de datos. Las llamadas a la base de datos en una aplicación se hacen usando una secuencia planeada; estas se agrupan dentro de unidades de trabajo atómicas. Esto también significa que el auto-commit después de cada una de las declaraciones SQL es inútil en una aplicación ya que este modo está pensado para trabajo ad-hoc de consola SQL. Hibernate deshabilita, o espera que el servidor de aplicaciones lo haga, el modo auto-commit inmediatamente. Las transacciones de las bases de datos nunca son opcionales. Toda comunicación con una base de datos tiene que ocurrir dentro de una transacción. El comportamiento auto-commit para leer datos se debe evitar, ya que hay muy poca probabilidad de que las transacciones pequeñas funcionen mejor que una unidad de trabajo definida claramente. La última es mucho más sostenible y extensible.

El patrón más común en una aplicación multiusuario cliente/servidor es sesión-por-petición. En este modelo, una petición del cliente se envia al servidor, en donde se ejecuta la capa de persistencia de Hibernate. Se abre una nueva `Session` de Hibernate y todas las operaciones de la base de datos se ejecutan en esta unidad de trabajo. Una vez completado el trabajo, y una vez se ha preparado la respuesta para el cliente, se limpia la sesión y se cierra. Use una sóla transacción de la base de datos para servir la petición del cliente, dándole inicio y guardándola cuando abre y cierra la `Session`. La relación entre las dos es uno-a-uno y este modelo es a la medida perfecta de muchas aplicaciones.

El reto se encuentra en la implementación. Hibernate brinda administración incorporada de la "sesión actual" para simplificar este patrón. Inicie una transacción cuando se tiene que procesar un pedido del servidor y termine la transacción antes de que se envie la respuesta al cliente. Las soluciones más comunes son `ServletFilter`, un interceptor AOP con un punto de corte en los métodos del servicio o un contenedor proxy/intercepción. Un contenedor EJB es una manera estandarizada de implementar aspectos de doble filo como demarcación de transacción en beans de sesión EJB, declarativamente con CMT. Si decide utilizar la demarcación de transacción programática, use el API `Transaction` de Hibernate de fácil uso y portable que se muestra más adelante en este capítulo.

Your application code can access a "current session" to process the request by calling `sessionFactory.getCurrentSession()`. You will always get a `Session` scoped to the current database transaction. This has to be configured for either resource-local or JTA environments, see Sección 2.3, “Sesiones contextuales”.

Puede extender el ámbito de una `Session` y transacción de la base de datos hasta que "se ha presentado la vista". Esto es bastante útil en aplicaciones de servlet que utilizan una fase de entrega separada después de que se ha procesado el pedido. El extender la transacción de la base de datos hasta que la entrega de la vista se encuentre completa es fácil de lograr si implementa su propio interceptor. Sin embargo, no se logra fácilmente si depende de EJBs con transacciones administradas por el contenedor. Una transacción se completará cuando un método EJB retorna, antes de que pueda empezar la entrega de cualquier vista. Vea el sitio web de Hibernate y el foro para encontrar consejos y ejemplos sobre este patrón de sesión abierta en vista.

### 13.1.2. Conversaciones largas

El patrón sesión-por-petición no es la única forma de diseñar unidades de trabajo. Muchos procesos empresariales requieren una serie completa de interacciones con el usuario intercaladas con accesos a la base de datos. En aplicaciones empresariales y web no es aceptable que una transacción de la base de datos abarque la interacción de un usuario. Considere el siguiente ejemplo:

- Se abre la primera pantalla de un diálogo. Los datos que ve el usuario han sido cargados en una `Session` en particular y en una transacción de la base de datos. El usuario es libre de modificar los objetos.

- El usuario hace click en "Guardar" después de 5 minutos y espera que sus modificaciones se hagan persistentes. También espera que él sea la única persona editando esta información y que no ocurra ningún conflicto en la modificación.

Desde el punto de vista del usuario, llamamos a esta unidad de trabajo, una larga conversación o transacción de aplicación. Hay muchas formas de implementar esto en su aplicación.

Una primera implementación ingenua podría mantener abierta la `Session` y la transacción de la base de datos durante el tiempo para pensar del usuario, con bloqueos en la base de datos para prevenir la modificación simultánea y para garantizar el aislamiento y la atomicidad. Esto es un antipatrón, ya que la contención de bloqueo no permitiría a la aplicación escalar con el número de usuarios simultáneos.

Tiene que usar varias transacciones de la base de datos para implementar la conversación. En este caso, mantener el aislamiento de los procesos empresariales se vuelve una responsabilidad parcial de la capa de la aplicación. Una sóla conversación usualmente abarca varias transacciones de la base de datos. Será atómica si sólo una de estas transacciones de la base de datos (la última) almacena los datos actualizados. Todas las otras simplemente leen datos (por ejemplo, en un diálogo de estilo-asistente abarcando muchos ciclos petición/respuesta). Esto es más fácil de implementar de lo que suena, especialmente si usa las funcionalidades de Hibernate:

- Versionado automático - Hibernate puede realizar un control automático de concurrencia optimista por usted .Puede detectar automáticamente si ha ocurrido una modificación simultánea durante el tiempo para pensar del usuario. Chequee esto al final de la conversación.

- Objetos separados: Si decide usar el patrón sesión-por-petición, todas las instancias cargadas estarán en estado separado durante el tiempo para pensar del usuario. Hibernate le permite volver a unir los objetos y hacer persistentes las modificaciones. El patrón se llama sesión-por-petición-con-objetos-separados. Se usa el versionado automático para aislar las modificaciones simultáneas.

- Sesión extendida (o larga) - La `Session` de Hibernate puede ser desconectada de la conexión JDBC subyacente después de que haya guardado la transacción de la base de datos y haya reconectado cuando ocurra una nueva petición del cliente. Este patrón se conoce como sesión-por-conversación y hace la re-unión innecesaria. Para aislar las modificaciones simultáneas se usa el versionado automático y usualmente no se permite que se limpie la `Session` automáticamente sino explícitamente.

Tanto la sesión-por-petición-con-objetos-separados como la sesión-por-conversación tienen ventajas y desventajas. Estas desventajas las discutimos más adelante en este capítulo en el contexto del control optimista de concurrencia.

### 13.1.3. Consideración de la identidad del objeto

Una aplicación puede acceder simultáneamente al mismo estado persistente en dos `Session`es diferentes. Sin embargo, una instancia de una clase persistente nunca se comparte entre dos instancias de `Session`. Por lo tanto, existen dos nociones diferentes de identidad:

Identidad de Base de Datos

  `foo.getId().equals( bar.getId() )`

Identidad JVM

  `foo==bar`

Para los bjetos unidos a una `Session` en particular (por ejemplo, en el ámbito de una `Session`) las dos nociones son equivalentes y la identidad de la MVJ para la identidad de la base de datos se encuentra garantizada por Hibernate. Mientras la aplicación acceda simultáneamente al "mismo" objeto empresarial (identidad persistente) en dos sesiones diferentes, las dos instancias serán realmente "diferentes" (identidad MVJ). Los conflictos se resuelven usando un enfoque optimista y el versionado automático en tiempo de vaciado/ al guardar.

Este enfoque deja que Hibernate y la base de datos se preocupen de la concurrencia. Además provee la mejor escalabilidad, ya que garantizando la identidad en unidades de trabajo monohilo no se necesitan bloqueos caros u otros medios de sincronización. La aplicación no necesita sincronizar sobre ningún objeto empresarial, siempre que se mantenga un solo hilo por `Session`. Dentro de una `Session` la aplicación puede usar con seguridad `==` para comparar objetos.

Sin embargo, una aplicación que usa `==` fuera de una `Session`, podría ver resultados inesperados. Esto podría ocurrir incluso en sitios algo inesperados. Por ejemplo, si pone dos instancias separadas dentro del mismo `Set` ambas podrían tener la misma identidad de la base de datos (por ejemplo, representar la misma fila). Sin embargo, la identidad MVJ, por definición, no está garantizada para las instancias en estado separado. El desarrollador tiene que sobrescribir los métodos `equals()` y `hashCode()` en las clases persistentes e implementar su propia noción de igualdad de objetos. Hay una advertencia: nunca use el identificador de la base de datos para implementar la igualdad. Use una clave de negocio, una combinación de atributos únicos, usualmente inmutables. El identificador de la base de datos cambiará si un objeto transitorio es hecho persistente. Si la instancia transitoria (usualmente junto a las instancias separadas) es mantenida en un `Set`, cambiar el código hash rompe el contrato del `Set`. Los atributos para las claves empresariales no tienen que ser tan estables como las claves principales de la base de datos, sólo tiene que garantizar estabilidad en tanto los objetos estén en el mismo `Set`. Mire el sitio web de Hibernate para obetener una discusión más profunda de este tema. Note también que éste no es problema de Hibernate, sino que simplemente se tiene que implementar la identidad y la igualdad de los objetos Java.

### 13.1.4. Temas comúnes

No use los antipatrones sesión-por-sesión-de-usuario o sesión-por-aplicación (hay excepciones raras a esta regla). Algunos de los siguientes temas también podrían aparecer con los patrones recomendados así que asegúrese de que entiende las implicaciones antes de tomar una decisión de diseño:

- Una `Session` no es segura entre hilos. Las cosas que funcionan de manera simultánea, como las peticiones HTTP, beans de sesión, o workers de Swing, provocarán condiciones de competencia si una instancia de `Session` se comparte. Si guarda su `Session` de Hibernate en su `HttpSession` (se discute más adelante), debe considerar el sincronizar el acceso a su sesión HTTP. De otro modo, un usuario que hace click lo suficientemente rápido puede llegar a usar la misma `Session` en dos hilos ejecutándose simultáneamente.

- Una excepción lanzada por Hibernate significa que tiene que deshacer su transacción de la base de datos y cerrar la `Session` inmediatamente (se discute en más detalle más adelante en este capítulo). Si su `Session` está vinculada a la aplicación, tiene que parar la aplicación. Deshacer la transacción de la base de datos no pone a sus objetos de vuelta al estado en que estaban al comienzo de la transacción. Esto significa que el estado de la base de datos y los objetos empreariales quedan fuera de sincronía. Usualmente esto no es un problema, pues las excepciones no son recuperables y tendrá que volver a comenzar después de deshacer de todos modos.

- The `Session` caches every object that is in a persistent state (watched and checked for dirty state by Hibernate). If you keep it open for a long time or simply load too much data, it will grow endlessly until you get an OutOfMemoryException. One solution is to call `clear()` and `evict()` to manage the `Session` cache, but you should consider a Stored Procedure if you need mass data operations. Some solutions are shown in Capítulo 15, Procesamiento por lotes. Keeping a `Session` open for the duration of a user session also means a higher probability of stale data.

## 13.2. Demarcación de la transacción de la base de datos

Los límites de las transacciones de la base de datos o el sistema son siempre necesarios. Ninguna comunicación con la base de datos puede darse fuera de una transacción de la base de datos (esto parece confundir a muchos desarrolladores acostumbrados al modo auto-commit). Siempre use límites de transacción claros, incluso para las operaciones de sólo lectura. Dependiendo del nivel de aislamiento y las capacidades de la base de datos, esto podría requerirse o no, pero no hay inconvenientes si siempre demarca explícitamente las transacciones. Con seguridad, una transacción única de base de datos va a funcionar mejor que muchas transacciones pequeñas, inclusive para leer datos.

Una aplicación Hibernate puede ejecutarse en entornos no administrados (por ejemplo, aplicaciones simples Web o Swing autónomas) y entornos administrados por J2EE. En un entorno no administrado, Hibernate es usualmente responsable de su propio pool de conexiones de la base de datos. El desarrollador de aplicaciones tiene que establecer manualmente los límites de transacción (inicar, guardar o deshacer las transacciones de la base de datos) por sí mismo. Un entorno administrado usualmente proporciona transacciones gestionadas por contenedor, con el ensamble de transacción definido declarativamente (por ejemplo, en descriptores de despliegue de beans de sesión EJB). La demarcación programática de transacciones ya no es necesaria.

Sin embargo, comúnmente se quiere mantener su capa de persistencia portátil entre entornos locales- de recursos no-administrados y sistemas que pueden confiar en JTA, pero utilizar BMT en vez de CMT. En ambos casos utilizaría la demarcación de transacción programática. Hibernate ofrece una API de envoltura llamada `Transaction` que se traduce al sistema de transacciones nativo de su entorno de despliegue. Esta API es de hecho opcional, pero le recomendamos bastante su uso salvo que esté en un bean de sesión CMT.

Usualmente, el finalizar una `Session` implica cuatro fases distintas:

- limpiar la sesión

- someter la transacción

- cerrar la sesión

- manejar excepciones

Anteriormente se discutió el vacido de la sesión así que ahora vamos a mirar más de cerca la demarcación de transacciones y el manejo de excepciones en los dos entornos administrado y no administrado.

### 13.2.1. Entorno no administrado

Si una capa de persistencia Hibernate se ejecuta en un entorno no administrado, las conexiones de la base de datos se manejan usualmente por simples pools de conexión (por ejemplo, no-DataSource) del cual Hibernate obtiene conexiones al ser necesario. El idioma de manejo de sesión/transacción se ve así:

    // Non-managed environment idiom

    Session sess = factory.openSession();

    Transaction tx = null;

    try {

        tx = sess.beginTransaction();

        // do some work

        ...

        tx.commit();

    }

    catch (RuntimeException e) {

        if (tx != null) tx.rollback();

        throw e; // or display error message

    }

    finally {

        sess.close();

    }

No tiene que vaciar con `flush()` la `Session` explícitamente: la llamada a `commit()` automáticamente dispara la sincronización dependiendo del FlushMode para la sesión. Una llamada a `close()` marca el final de una sesión. La implicación principal de `close()` es que la conexión JDBC será abandonada por la sesión. Este código Java es portátil y ejecuta en entornos tanto no-administrados como JTA.

Como se mencionó anteriormente, una solución mucho más flexible es la administración de contexto "sesión actual" incorporada en Hibernate:

    // Non-managed environment idiom with getCurrentSession()

    try {

        factory.getCurrentSession().beginTransaction();

        // do some work

        ...

        factory.getCurrentSession().getTransaction().commit();

    }

    catch (RuntimeException e) {

        factory.getCurrentSession().getTransaction().rollback();

        throw e; // or display error message

    }

No verá estos pedazos de código en una aplicación normal; las excepciones fatales (del sistema) siempre deben ser capturadas en la "cima". En otras palabras, el código que ejecuta las llamadas de Hibernate en la capa de persistencia y el código que maneja `RuntimeException` (y usualmente sólo puede limpiar y salir) se encuentran en capas diferentes. La administración de contexto actual de Hibernate puede simplificar de manera importante este diseño, ya que todo lo que necesita hacer es acceder a `SessionFactory`. El manejo de excepciones se discute más adelante en este capítulo.

Debe seleccionar `org.hibernate.transaction.JDBCTransactionFactory`, el cual es el predeterminado, y para el segundo ejemplo seleccionar `"thread"` como su `hibernate.current_session_context_class`.

### 13.2.2. Utilización de JTA

Si su capa de persistencia se ejecuta en un servidor de aplicaciones (por ejemplo, detrás de los beans de sesión EJB), cada conexión de fuente de datos obtenida por Hibernate será parte de la transacción JTA global de manera automática. También puede instalar una implementación JTA autónoma y utilizarla sin EJB. Hibernate ofrece dos estrategias para esta integración JTA.

Si usa transacciones gestionadas-por-bean (BMT) Hibernate le dirá al servidor de aplicaciones que comience y finalice una transacción BMT si usa la API de `Transaction`. De modo que, el código de gestión de la transacción es idéntico al de un entorno no administrado.

    // BMT idiom

    Session sess = factory.openSession();

    Transaction tx = null;

    try {

        tx = sess.beginTransaction();

        // do some work

        ...

        tx.commit();

    }

    catch (RuntimeException e) {

        if (tx != null) tx.rollback();

        throw e; // or display error message

    }

    finally {

        sess.close();

    }

Si quiere utilizar un vínculo de transacción `Session`, es decir, la funcionalidad `getCurrentSession()` para propagación de contexto de manera fácil, tendrá que utilizar el API `UserTransaction` del JTA directamente:

    // BMT idiom with getCurrentSession()

    try {

        UserTransaction tx = (UserTransaction)new InitialContext()

                                .lookup("java:comp/UserTransaction");

        tx.begin();

        // Do some work on Session bound to transaction

        factory.getCurrentSession().load(...);

        factory.getCurrentSession().persist(...);

        tx.commit();

    }

    catch (RuntimeException e) {

        tx.rollback();

        throw e; // or display error message

    }

Con CMT, la demarcación de transacción se realiza en los descriptores de implementacion bean de sesión, no programáticamente. Por lo tanto el código se reduce a:

    // CMT idiom

     Session sess = factory.getCurrentSession();

     // do some work

     ...

En un CMT/EJB incluso el deshacer sucede de forma automática. Un `RuntimeException` lanzado por un método bean de sesión le dice al contenedor que establezca una transacción global para deshacer. No necesita utilizar el API `Transaction` de Hibernate con BMT o CMT y obtiene la propagación automática de sesión"actual" vinculada a la transacción.

Al configurar la fábrica de transacciones de Hibernate, escoja `org.hibernate.transaction.JTATransactionFactory` si utiliza JTA directamente (BMT) y `org.hibernate.transaction.CMTTransactionFactory` en una bean de sesión CMT. Recuerde establecer también `hibernate.transaction.manager_lookup_class`. Asegúrese de que su `hibernate.current_session_context_class` no se encuentra configurado (compatibilidad retrasada) o configurada como `"jta"`.

La operación `getCurrentSession()` tiene un inconveniente en un entorno JTA. Hay una desventaja en el uso del modo de liberación de la conección `after_statement`, la cual luego se utiliza por defecto. Debido a una limitación de la especificación JTA, no le es posible a Hibernate limpiar automáticamente cualquier instancia `ScrollableResults` o `Iterator` no cerradas y retornadas por `scroll()` o `iterate()`. Tiene que liberar el cursor de la base de datos subyacente llamando a `ScrollableResults.close()` o `Hibernate.close(Iterator)` explícitamente desde un bloque `finally`. La mayoría de las aplicaciones pueden evitar fácilmente el utilizar `scroll()` o `iterate()` del código JTA o CMT.

### 13.2.3. Manejo de excepciones

Si la `Session` lanza una excepción, incluyendo cualquier `SQLException`, debe deshacer inmediatamente la transacción de la base de datos, llamar a `Session.close()` y descartar la instancia de `Session`. Ciertos métodos de `Session` no dejarán la sesión en un estado consistente. Ninguna excepción lanzada por Hibernate puede ser tratada como recuperable. Asegúrese de que la `Session` se cierre llamando a `close()` en un bloque `finally`.

La `HibernateException`, que envuelve a la mayoría de los errores que pueden ocurrir en la capa de persistencia de Hibernate, es una excepción no chequeada. No lo era en versiones anteriores de Hibernate. En nuestra opinión, no debemos forzar al desarrollador de aplicaciones a capturar una excepción irrecuperable en una capa baja. En la mayoría de los sistemas, las excepciones no chequeadas y fatales son manejadas en uno de los primeros cuadros de la pila de llamadas a métodos (por ejemplo, en las capas más altas) y presenta un mensaje de error al usuario de la aplicación o se toma alguna otra acción apropiada. Note que Hibernate podría también lanzar otras excepciones no chequeadas que no sean una `HibernateException`. Estas no son recuperables y debe tomarse una acción apropiada.

Hibernate envuelve `SQLException`s lanzadas mientras se interactúa con la base de datos en una `JDBCException`. De hecho, Hibernate intentará convertir la excepción en una subclase de `JDBCException` más significativa. La `SQLException` subyacente siempre está disponible por medio de `JDBCException.getCause()`. Hibernate convierte la `SQLException` en una subclase de `JDBCException` apropiada usando el `SQLExceptionConverter` adjunto a la `SessionFactory`. Por defecto, el `SQLExceptionConverter` está definido por el dialecto configurado. Sin embargo, también es posible enchufar una implementación personalizada . Consulte los javadocs de la clase `SQLExceptionConverterFactory` para obtener más detalles. Los subtipos estándar de `JDBCException` son:

- `JDBCConnectionException`: indica un error con la comunicación JDBC subyacente.

- `SQLGrammarException`: indica un problema de gramática o sintáxis con el SQL publicado.

- `ConstraintViolationException`: indica alguna forma de violación de restricción de integridad.

- `LockAcquisitionException`: indica un error adquiriendo un nivel de bloqueo necesario para realizar una operación solicitada.

- `GenericJDBCException`: una excepción genérica que no encajó en ninguna de las otras categorías.

### 13.2.4. Tiempo de espera de la transacción

Una característica importante proporcionada por un entorno administrado como EJB que nunca es proporcionado para un código no-administrado, es el tiempo de espera de la transacción. Estos tiempos de espera se aseguran de que ninguna transacción que se comporte inapropiadamente pueda vincular recursos mientras no devuelva una respuesta al usuario. Fuera de un entorno administrado (JTA), Hibernate no puede proporcionar completamente esta funcionalidad. Sin embargo, Hibernate puede por lo menos controlar las operaciones de acceso de datos, asegurándose de que los bloqueos a nivel de base de datos y las consultas con grandes grupos de resultados se encuentran limitados por un tiempo de espera definido. En un entorno administrado, Hibernate puede delegar el tiempo de espera de la transacción a 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();

    }

`setTimeout()` no se puede llamar en un bean CMT, en donde se deben definir declarativamente los tiempos de espera de las transacciones.

## 13.3. Control de concurrencia optimista

El único enfoque consistente con una alta concurrencia y una alta escalabilidad es el control de concurrencia optimista con versionamiento. El chequeo de versión utiliza números de versión, o sellos de fecha (timestamps), para detectar actualizaciones en conflicto y para prevenir la pérdida de actualizaciones. Hibernate proporciona tres enfoques posibles de escribir código de aplicación que utilice concurrencia optimista. Los casos de uso que mostramos se encuentran en el contexto de conversaciones largas, pero el chequeo de versiones tiene además el beneficio de prevenir la pérdida de actualizaciones en transacciones individuales de la base de datos.

### 13.3.1. Chequeo de versiones de la aplicación

En una implementación que no tiene mucha ayuda de Hibernate, cada interacción con la base de datos ocurre en una nueva `Session` y el desarrollador es el responsable de recargar todas las intancias persistentes desde la base de datos antes de manipularlas. Este enfoque fuerza a la aplicación a realizar su propio chequeo de versiones para asegurar el aislamiento de transacciones de conversaciones. Este enfoque es el menos eficiente en términos de acceso a la base de datos. Es el enfoque más similar a los EJBs de entidad.

    // foo is an instance loaded by a previous Session

    session = factory.openSession();

    Transaction t = session.beginTransaction();

    int oldVersion = foo.getVersion();

    session.load( foo, foo.getKey() ); // load the current state

    if ( oldVersion != foo.getVersion() ) throw new StaleObjectStateException();

    foo.setProperty("bar");

    t.commit();

    session.close();

La propiedad `version` se mapea utilizando `<version>`, e Hibernate la incrementará automáticamente durante la limpieza si la entidad está desactualizada.

Si está operando un entorno de baja-concurrencia-de-datos y no requiere chequeo de versiones, puede usar este enfoque y simplemente saltarse el chequeo de versiones. En ese caso, el último que guarda gana y será la estrategia por defecto para conversaciones largas. Tenga en mente que esto podría confundir a los usuarios de la aplicación, pues podrían experimentar pérdidas de actualizaciones sin mensajes de error ni oportunidad de fusionar los cambios conflictivos.

El chequeo manual de versiones es factible sólamente en circunstancias muy triviales y no es práctico para la mayoría de las aplicaciones. Con frecuencia se tienen que chequear no sólamente las intancias sólas, sino también grafos completos de objetos modificados. Hibernate ofrece el chequeo de versiones automático con el paradigma de diseño de `Session` larga o de instancias separadas.

### 13.3.2. Sesión extendida y versionado automático

Una sóla instancia de `Session` y sus instancias persistentes se utilizan para toda la convervsación conocida como sesión-por-conversación. Hibernate chequea las versiones de instancia en el momento de vaciado, lanzando una excepción si se detecta una modificación concurrente. Le concierne al desarrollador capturar y manejar esta excepción. Las opciones comunes son la oportunidad del usuario de fusionar los cambios, o de recomenzar el proceso empresarial sin datos desactualizados.

La `Session` se desconecta de cualquier conexión JDBC subyacente a la espera de una interacción del usuario. Este enfoque es el más eficiente en términos de acceso a la base de datos. La aplicación no necesita por sí misma tratar con el chequeo de versiones, ni re-unir instancias separadas, ni tiene que recargar instancias en cada transacción de la base de datos.

    // foo is an instance loaded earlier by the old session

    Transaction t = session.beginTransaction(); // Obtain a new JDBC connection, start transaction

    foo.setProperty("bar");

    session.flush();    // Only for last transaction in conversation

    t.commit();         // Also return JDBC connection

    session.close();    // Only for last transaction in conversation

El objeto `foo` sabe en qué `Session` fue cargado. El dar inicio a una nueva base de datos en una sesión vieja obtiene una nueva conexión y reanuda la sesión. El guardar una transacción de la base de datos desconecta una sesión de la conexion JDBC y devuelve la conexión al pool. Después de la reconexión, para poder forzar una verificación de versión sobre datos que usted no está actalizando, puede llamar a `Session.lock()` con `LockMode.READ` en cualquier objeto que pueda haber sido actualizado por otra transacción. No necesita bloquear ningún dato que sí esté actualizando. Usualmente configuraría `FlushMode.MANUAL` en una `Session` extendida, de manera que de hecho sólamente se permite persistir el último ciclo de transacción de la base de datos de todas las modificaciones realizadas en esta conversación. Sólamente esta última transacción de la base de datos incluiría la operación `flush()` y luego cierra -`close()`- la sesión para dar fin a la conversación.

Este patrón es problemático si la `Session` es demasiado grande para almacenarla durante el tiempo para pensar del usuario, por ejemplo, una `HttpSession` se debe mantener tan pequeña como sea posible. Como la `Session` también lo es el caché de primer nivel (obligatorio) y comprende todos los objetos cargados, probablemente podemos utilizar esta estrategia sólamente para unos pocos ciclos de pedido/respuesta. Debe utilizar una `Session` sólamente para una conversación única ya que pronto también tendrá datos añejos.

## Nota

Las versiones anteriores de Hibernate necesitaban desconexión explícita y reconexión de una `Session`. Estos métodos ya no se aprueban ya que tienen el mismo efecto que dar inicio o finalizar a una transacción.

Mantenga la `Session` desconectada cerca a la capa de persistencia. Use un bean de sesión EJB con estado para mantener la `Session` en un entorno de tres capas . No la transfiera a la capa web ni la serialice en una capa separada para almacenarla en la `HttpSession`.

El patrón de sesión extendido, o sesión-por-conversación, es más dificil de implementar con la administración de contexto de sesión actual. Necesita proporcionar su propia implementación de la `CurrentSessionContext` para esto, vea el Wiki de Hibernate para obtener más ejemplos.

### 13.3.3. Objetos separados y versionado automático

Cada interacción con el almacenamiento persistente ocurre en una nueva `Session`. Sin embargo, las mismas instancias persistentes son reutilizadas para cada interacción con la base de datos. La aplicación manipula el estado de las instancias separadas cargadas originalmente en otra `Session` y luego las readjunta usando `Session.update()`, `Session.saveOrUpdate()`, o `Session.merge()`.

    // foo is an instance loaded by a previous Session

    foo.setProperty("bar");

    session = factory.openSession();

    Transaction t = session.beginTransaction();

    session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already

    t.commit();

    session.close();

De nuevo, Hibernate chequeará las versiones de la instancia durante el vaciado, lanzando una excepción si tuvieron lugar conflictos en las actualizaciones.

También puede llamar a `lock()` en lugar de `update()` y utilizar `LockMode.READ` (realizando un chequeo de versión, evitando todos los cachés) si está seguro de que el objeto no ha sido modificado.

### 13.3.4. Personalización del versionado automático

Puede deshabilitar el incremento de la versión automática de Hibernate para ciertas propiedades y colecciones en particular estableciendo el atributo de mapeo `optimistic-lock` como `false`. Hibernate entonces ya no incrementará más las versiones si la propiedad se encuentra desactualizada.

Los esquemas heredados de la base de datos con frecuencia son estáticos y no pueden ser modificados. Inclusive otras aplicaciones podrían también acceder la misma base de datos y no saber cómo manejar los números de versión ni los sellos de fecha. En ambos casos, el versionado no puede confiarse a una columna en particular en una tabla. Para forzar un chequeo de versiones sin un mapeo de propiedad de versión o sello de fecha, con una comparación del estado de todos los campos en una fila, active `optimistic-lock="all"` en el mapeo de `<class>`. Esto funciona conceptualmente sólamente si Hibernate puede comparar el estado viejo y el nuevo, es decir, si usa una sóla `Session` larga y no sesión-por-petición-con-instancias-separadas.

Las modificaciones simultáneas pueden permitirse en instancias en tanto los cambios que se hayan realizado no se superpongan. Si establece `optimistic-lock="dirty"` al mapear la `<class>`, Hibernate sólo comparará los campos desactualizados durante el vaciado.

En ambos casos, con columnas de versión/sello de fecha dedicadas o con comparación de campos completos/desactualizados, Hibernate utiliza una sóla declaración `UPDATE` (con una cláusula `WHERE` apropiada) por entidad para ejecutar el chequeo de versiones y actualizar la información. Si utiliza una persistencia transitiva para la re-unión en cascada de entidades asociadas, Hibernate podría ejecutar actualizaciones innecesarias. Esto usualmente no es problema, pero podrían ejecutarse disparadores (triggers) enactualizazción en la base de datos incluso cuando no se haya hecho ningún cambio a las instancias separadas. Puede personalizar este comportamiento estableciendo `select-before-update="true"` en el mapeo de `<class>`, forzando a Hibernate a `SELECT` la instancia para asegurar que las actualizaciones realmente ocurran, antes de actualizar la fila.

## 13.4. Bloqueo pesimista

No se pretende que los usuarios tomen mucho tiempo preocupándose de las estrategias de bloqueo. Usualmente es suficiente con especificar un nivel de aislamiento para las conexiones JDBC y entonces simplemente dejar que la base de datos haga todo el trabajo. Sin embargo, los usuarios avanzados a veces pueden obtener bloqueos exclusivos pesimistas, o reobtener bloqueos al comienzo de una nueva transacción.

Hibernate siempre usará el mecanismo de bloqueo de la base de datos, nunca el bloqueo de objetos en memoria.

La clase `LockMode` define los diferentes niveles de bloqueo que Hibernate puede adquirir. Un bloqueo se obtiene por medio de los siguientes mecanismos:

- `LockMode.WRITE` se adquiere automáticamente cuando Hibernate actualiza o inserta una fila.

- `LockMode.UPGRADE` se puede ser adquirir bajo petición explícita del usuario usando `SELECT ... FOR UPDATE` en bases de datos que soporten esa sintáxis.

- `LockMode.UPGRADE_NOWAIT` se puede adquirir bajo petición explícita del usuario usando un `SELECT ... FOR UPDATE NOWAIT` bajo Oracle.

- `LockMode.READ` se adquiere automáticamente cuando Hibernate lee los datos bajo los niveles de aislamiento de lectura repetible o serializable. Se puede readquirir por pedido explícito del usuario.

- `LockMode.NONE` representa la ausencia de un bloqueo. Todos los objetos se pasan a este modo de bloqueo al final de una `Transaction`. Los objetos asociados con una sesión por medio de una llamada a `update()` o `saveOrUpdate()` también comienzan en este modo de bloqueo.

La "petición explícita del usuario" se expresa en una de las siguientes formas:

- Una llamada a `Session.load()`, especificando un `LockMode`.

- Una llamada a `Session.lock()`.

- Una llamada a `Query.setLockMode()`.

Si se llama a `Session.load()` con `UPGRADE` o `UPGRADE_NOWAIT`, y el objeto pedido no ha sido cargado todavía por la sesión, el objeto es cargado usando `SELECT ... FOR UPDATE`. Si se llama a `load()` para un objeto que ya esté cargado con un bloqueo menos restrictivo que el pedido, Hibernate llama a `lock()` para ese objeto.

`Session.lock()` realiza un chequeo de número de versión si el modo de bloqueo especificado es `READ`, `UPGRADE` o `UPGRADE_NOWAIT`. En el caso de `UPGRADE` o `UPGRADE_NOWAIT`, se usa `SELECT ... FOR UPDATE`.

Si la base de datos no soporta el modo de bloqueo solicitado, Hibernate usa un modo opcional apropiado en lugar de lanzar una excepción. Esto asegura que las aplicaciones serán portátiles.

## 13.5. Modos de liberación de la conexión

La herencia (2x) de Hibernate en relación con la administración de la conexion JDBC fue que una `Session` obtendría una conexión cuando se necesitara por primera vez y luego la mantendría hasta que se cerrara la sesión. Hibernate 3.x introdujo la noción de modos de liberación de conexión para decirle a la sesión como manejar sus conexiones JDBC. La siguiente discusión sólamente es pertinente para las conexiones provistas por medio de un `ConnectionProvider` configurado. Las conexiones provistas por el usuario no se discuten aquí. Los diferentes modos de liberación se identifican por los valores numerados de `org.hibernate.ConnectionReleaseMode`:

- `ON_CLOSE`: es el comportamiento heredado descrito anteriormente. La sesión de Hibernate obtiene una conexión cuando necesita acceder a JDBC la primera vez y mantiene esa conexión hasta que se cierra la sesión.

- `AFTER_TRANSACTION`: libera las conecciones después de que se ha completado una `org.hibernate.Transaction`.

- `AFTER_STATEMENT` (también se conoce como una liberación agresiva): libera conexiones después de cada ejecución de una declaración. Se salta esta liberación agresiva si la declaración deja abiertos recursos asociados con la sesión dada. Actualmente la única situación donde ocurre esto es por medio del uso de `org.hibernate.ScrollableResults`.

El parámetro de configuración `hibernate.connection.release_mode` se utiliza para especificar el modo de liberación a utilizar. Los valores posibles son los siguientes:

- `auto` (predeterminado): esta opción delega al modo de liberación devuelto por el método `org.hibernate.transaction.TransactionFactory.getDefaultReleaseMode()`. Para JTATransactionFactory, esto devuelve ConnectionReleaseMode.AFTER_STATEMENT; para JDBCTransactionFactory, esto devuelve ConnectionReleaseMode.AFTER_TRANSACTION. No cambie este comportamiento predeterminado ya que las fallas debido a este valor de esta configuración tienden a indicar errores y/o suposiciones en el código del usuario.

- `on_close`: usa ConnectionReleaseMode.ON_CLOSE. Esta configuración se deja para la compatibilidad con versiones anteriores, pero no se recomienda para nada su utilización.

- `after_transaction`: utiliza ConnectionReleaseMode.AFTER_TRANSACTION. Esta configuración no se debe utilizar en entornos JTA. También note que con ConnectionReleaseMode.AFTER_TRANSACTION, si se considera que una sesión se encuentra en modo auto-commit, las conexiones serán liberada como si el modo de liberación fuese AFTER_STATEMENT.

- `after_statement`: usa ConnectionReleaseMode.AFTER_STATEMENT. Además se consulta la `ConnectionProvider` configurada para ver si soporta esta característica `supportsAggressiveRelease()`. Si no, el modo de liberación se vuelve a establecer como ConnectionReleaseMode.AFTER_TRANSACTION. Esta configuración sólamente es segura en entornos en donde podemos re-adquirir la misma conexión JDBC subyacente cada vez que llamamos a `ConnectionProvider.getConnection()` o en entornos auto-commit, en donde no importa si recibimos la misma conexión.

Copyright © 2004 Red Hat, Inc.

- AnteriorCapítulo 12. Read-only entities
- Subir
- Inicio
- SiguienteCapítulo 14. Interceptores y eventos