Transacciones y Concurrencia El punto más importante sobre Hibernate y el control de concurrencia es que muy fácil de comprender. Hibernate usa directamente conexiones JDBC y recursos JTA sin agregar ningún comportamiento de bloqueo adicional. Recomendamos altamente que gastes algo de tiempo con la especificación de JDBC, ANSI, y el aislamiento de transacciones de tu sistema de gestión de base de datos. Hibernate sólo añade versionado automático pero no bloquea objetos en memoria ni cambia el nivel de aislamiento de tus transacciones de base de datos. Básicamente, usa Hibernate como usarías JDBC directo (o JTA/CMT) con tus recursos de base de datos. Sin embargo, además del versionado automático, Hibernate ofrece una API (menor) para bloqueo pesimista de filas, usando la sintáxis SELECT FOR UPDATE. Esta API se discute 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 la base de datos y las transacciones de aplicación largas. Ámbitos de sesión y de transacción Una SessionFactory es un objeto seguro entre hebras caro-de-crear pensado para ser compartido por todas las hebras de la aplicación. Es creado una sola vez, usualmente en el arranque de la aplicación, a partir de una instancia de Configuration. Una Session es un objeto barato, inseguro entre hebras que debe ser usado una sola vez, para un solo proceso de negocio, una sola unidad de trabajo, y luego descartado. Una Session no obtendrá una Connection JDBC (o un Datasource) a menos que sea necesario, de modo que puedas abrir y cerrar seguramente una Session incluso si no estás seguro que se necesitará acceso a los datos para servir una petición en particular. (Esto se vuelve importante en cuanto estés implementando alguno de los siguientes patrones usando intercepción de peticiones). Para completar este cuadro tienes que pensar también en las transacciones de base de datos. Una transacción de 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 base de datos prevendrán a tu aplicación de escalar a una carga altamente concurrente. ¿Qué es el ámbito de una unidad de trabajo? ¿Puede una sola Session de Hibernate extenderse a través de varias transacciones de base de datos o es ésta una relación uno-a-uno de ámbitos? ¿Cuándo debes abrir y cerrar una Session y cómo demarcas los límites de la transacción de base de datos? Unidad de trabajo Primero, no uses el antipatrón sesión-por-operación, esto es, ¡no abras y cierres una Session para cada simple llamada a la base de datos en una sola hebra! Por supuesto, lo mismo es verdad para transacciones de base de datos. Las llamadas a base de datos en una aplicación se hacen usando una secuencia prevista, que están agrupadas dentro de unidades de trabajo atómicas. (Nota que esto también significa que el auto-commit después de cada una de las sentencias SQL es inútil en una aplicación, 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.) El patrón más común en una aplicación mutiusuario cliente/servidor es sesión-por-petición. En este modelo, una petición del cliente es enviada al servidor (en donde se ejecuta la capa de persistencia de Hibernate), se abre una nueva Session de Hibernate, y todas las operaciones de base de datos se ejecutan en esta unidad de trabajo. Una vez completado el trabajo (y se ha preparado la respuesta para el cliente) la sesión es limpiada y cerrada. Podrías usar una sola transacción de base de datos para servir a petición del cliente, comenzándola y comprometiéndola cuando abres y cierras la Session. La relación entre las dos es uno-a-uno y este modelo es a la medida perfecta de muchas aplicaciones. El desafío yace en la implementación: no sólo tienen que comenzarse y terminarse correctamente la Session y la transacción, sino que además tienen que estar accesibles para las operaciones de acceso a datos. La demarcación de una unidad de trabajo se implementa idealmente usando un interceptor que se ejecuta cuando una petición llama al servidor y anter que la respuesta sea enviada (es decir, un ServletFilter). Recomendamos ligar la Session a la hebra que atiende la petición, usando una variable ThreadLocal. Esto permite un fácil acceso (como acceder a una variable static) en tódo el código que se ejecuta en esta hebra. Dependiendo del mecanismo de demarcación de transacciones de base de datos que elijas, podrías mantener también el contexto de la transacción en una variable ThreadLocal. Los patrones de implementación para esto son conocidos como Sesión Local de Hebra (ThreadLocal Session) y Sesión Abierta en Vista (Open Session in View). Puedes extender fácilmente la clase de ayuda HibernateUtil mostrada anteriormente para encontrar una forma de implementar un interceptor e instalarlo en tu entorno. Ver el sitio web de Hibernate para consejos y ejemplos. Transacciones de aplicación El patrón sesión-por-petición no es el único concepto útil que puedes usar para diseñar unidades de trabajo. Muchos procesos de negocio requiere una serie completa de interacciones con el usuario intercaladas con accesos a base de datos. En aplicaciones web y de empresa no es aceptable que una transacción de base de datos se extienda a través de la interacción de un usuario. Considera el siguiente ejemplo: Se abre la primera pantalla de un diálogo, los datos vistos por el usuario han sido cargados en una Session y transacción de base de datos particular. El usuario es libre de modificar los objetos. El usuario hace click en "Salvar" después de 5 minutos y espera que sus modificaciones sean hechas persistentes. También espera que él sea la única persona editando esta información y que no puede ocurrir ninguna modificación en conflicto. Llamamos a esto unidad de trabajo, desde el punto de vista del usuario, una larga transacción de aplicación ejecutándose. Hay muchas formas en que puedes implementar esto en tu aplicación. Una primera implementación ingenua podría mantener abierta la Session y la transacción de base de datos durante el tiempo de pensar del usuario, con bloqueos tomados en la base de datos para prevenir la modificación concurrente, y para garantizar aislamiento y atomicidad. Esto es, por supuesto, un antipatrón, ya que la contención de bloqueo no permitiría a la aplicación escalar con el número de usuarios concurrentes. Claramente, tenemos que usar muchas transacciones de base de datos para implementar la transacción de aplicación. En este caso, mantener el aislamiento de los procesos de negocio se vuelve una responsabilidad parcial de la capa de aplicación. Una sola transacción de aplicación usualmente abarca varias transacciones de base de datos. Será atómica si sólo una de estas transacciones de base de datos (la última) almacena los datos actualizados, todas las otras simplemente leen datos (por ejemplo, en un diálogo estilo-asistente abarcando muchos ciclos petición/respuesta). Esto es más fácil de implementar de lo que suena, especialmente si usas las funcionalidades de Hibernate: Versionado Automático - Hibernate puede llevar un control automático de concurrencia optimista por ti, puede detectar automáticamente si una modificación concurrente ha ocurrido durante el tiempo de pensar del usuario. Objetos Separados - Si decides usar el ya discutido patrón de sesión-por-petición, todas las instancias cargadas estarán en estado separado durante el tiempo de pensar del usuario. Hibernate te 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 versionado automático para aislar las modificaciones concurrentes. Sesión Larga - La Session de Hibernate puede ser desconectada de la conexión JDBC subyacente después que se haya sido comprometida la transacción de base de datos, y reconectada cuando ocurra una nueva petición del cliente. Este patrón es conocido como sesión-por-transacción-de-aplicación y hace la re-unión innecesaria. Para aislar las modificaciones concurrentes se usa el versionado automático. Tanto sesión-por-petición-con-objetos-separados como sesión-por-transacción-de-aplicación, ambas tienen ventajas y desventajas, las discutimos más adelante en este capítulo en el contexto del control optimista de concurrencia. Considerando la identidad del objeto Una aplicación puede acceder concurrentemente a el mismo estado persistente en dos Sessions 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 Entonces para objetos unidos a una Session en particular (es decir en el ámbito de una Session) las dos nociones son equivalentes, y la identidad JVM para la identidad de base de datos está garantizada por Hibernate. Sin embargo, mientras la aplicación acceda concurrentemente al "mismo" (identidad persistente) objeto de negocio en dos sesiones diferentes, las dos instancias serán realmente "diferentes" (identidad JVM). Los conflictos se resuelven (con versionado automático) en tiempo de limpieza (flush) usando un enfoque optimista. Este enfoque deja que Hibernate y la base de datos se preocupen sobre la concurrencia. Además provee la mejor escalabilidad, ya que garantizando la identidad un unidades de trabajo monohebra no se necesitan bloqueos caros u otros medios de sincronización. La aplicación nunca necesita sincronizar sobre ningún objeto de negocio, siempre que se apegue a una sola hebra 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 pones dos instancias separadas dentro del mismo Set. Ambas podrían tener la misma identidad de base de datos (es decir, representar la misma fila), pero la identidad JVM, 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 uses el identificador de base de datos para implementar la igualdad, usa una clave de negocio, una combinación de atributos únicos, usualmente inmutables. El identificador de base de datos cambiará si un objeto transitorio es hecho persistente. Si la instancia transitoria (usualmente junta a instancias separadas) es mantenida en un Set, cambiar el código hash rompe el contrato del Set. Los atributos para las claves de negocio no tienen que ser tan estables como las claves primarias de base de datos, sólo tienes que garantizar estabilidad en tanto los objetos estén en el mismo Set. Mira el sitio web de Hibernate para una discusión más cuidadosa de este tema. Nota también que éste no es un tema de Hibernate, sino simplemente cómo la identidad y la igualdad de los objetos Java tiene que ser implementada. Temas comunes Nunca uses los antipatrones sesión-por-sesión-de-usuario o sesión-por-aplicación (por supuesto, hay raras excepciones a esta regla). Nota que algunis de los siguientes temas podrían también aparecer con los patrones recomendados. Asegúrate que entiendes las implicaciones antes de tomar una decisión de diseño: Una Session no es segura entre hebras. Las cosas que se suponen que funcionan concurrentemente, como peticiones HTTP, beans de sesión, o workers de Swing, provocarán condiciones de competencia si una instancia de Session fuese compartida. Si guardas tu Session de Hibernate en tu HttpSession (discutido más adelante), debes considerar sincronizar el acceso a tu sesión HTTP. De otro modo, un usuario que hace click lo suficientemente rápido puede llegar a usar la misma Session en dos hebras ejecutándose concurrentemente. Una excepción lanzada por Hibernate significa que tienes que deshacer (rollback) tu transacción de base de datos y cerrar la Session inmediatamente (discutido en más detalle luego). Si tu Session está ligada a la aplicación, tienes que parar la aplicación. Deshacer (rollback) la transacción de base de datos no pone a tus 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 de negocio quedan fuera de sincronía. Usualmente esto no es un problema, pues las excepciones no son recuperables y tienes que volver a comenzar después del rollback de todos modos. La Session pone en caché todo objeto que esté en estado persistente (vigilado y chequeado por estado sucio por Hibernate). Esto significa que crece sin fin hasta que obtienes una OutOfMemoryException, si la mantienes abierta por un largo tiempo o simplemente cargas demasiados datos. Una solución para esto es llamar a clear() y evict() para gestionar el caché de la Session, pero probalemente debas considerar un procedimiento almacenado si necesitas operaciones de datos masivas. Se muestran algunas soluciones en . Mantener una Session abierta por la duración de una sesión de usuario significa también una alta probabilidad de datos añejos. Demarcación de la transacción de base de datos Los límites de las transacciones de base de datos (o sistema) son siempre necesarios. Ninguna comunicación con la base de datos puede darse fuera de una transacción de base de datos (esto parece confundir muchos desarrolladores acostumbrados al modo auto-commit). Siempre usa límites de transacción claros, incluso para las operaciones de sólo lectura. Dependiendo del nivel de aislamiento y las capacidades de base de datos, esto podría o no ser requerido, pero no hay un merma si siempre demarcas explícitamente las transacciones. Una aplicación Hibernate puede ejecutarse en entornos no manejados (es decir, como independiente, Web simple, o aplicaciones Swing) y entornos manejados J2EE. En un entorno no manejado, Hibernate es usualmente responsable de su propio pool de conexiones de base de datos. El desarrollador de aplicaciones tiene que establecer manualmente los límites de transacción, en otras palabras, hacer begin, commit, o rollback las transacciones de base de datos por sí mismo. Un entorno manejado usualmente provee transacciones gestionadas por contenedor, con el ensamble de transacción definido declarativamente en descriptores de despliegue de beans de sesión EJB, por ejemplo. La demarcación programática de transacciones no es más necesario, incluso limpiar (flush) la Session es hecho automáticamente. Sin embargo, frecuentemente es deseable mantener portable tu capa de persistencia. Hibernate ofrece una API de envoltura llamada Transaction que se traduce al sistema de transacciones nativo de tu entorno de despliegue. Esta API es realmente opcional, pero recomendamos fuertemente su uso salvo que estés en un bean de sesión CMT. Usualmente, finalizar una Session implica cuatro fases distintas: limpiar (flush) la sesión comprometer la transacción cerrar la sesión manejar excepciones Limpiar la sesión ha sido discutido anteriormente, tendremos ahora una mirada más de cerca a la demarcación de transacciones y manejo de excepciones en sendos entornos manejado y no manejados. Entorno no manejado Si una capa de persistencia Hibernate se ejecuta en un entorno no manejado, las conexiones de base de datos son manejadas usualmente por el mecanismo de pooling de Hibernate. El idioma manejo de sesión/transacción se ve así: No tienes que limpiar con flush() la Session explícitamente - la llamada a commit() automáticamente dispara la sincronización. Una llamada a close() marca el fin de una sesión. La principal implicación de close() es que la conexión JDBC será abandonada por la sesión. Este código Java es portable y se ejecuta tanto en entornos no manejados como en entornos JTA. Muy probablemente nunca veas este idioma en código de negocio en una aplicación normal; las excepciones fatales (sistema) deben siempre 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) están en capas diferentes. Esto puede ser un desafío de diseñarlo tú mismo y debes usar los servicios de contenedor J2EE/EJB en cuanto estuviesen disponibles. El manejo de excepciones se dicute más adelante en este capítulo. Nota que debes seleccionar org.hibernate.transaction.JDBCTransactionFactory (que es el por defecto). Usando JTA Si tu capa de persistencia se ejecuta en un servidor de aplicaciones (por ejemplo, detrás de beans de sesión EJB), cada conexión de datasource obtenida por Hibernate será parte automáticamente de la transacción JTA global. Hibernate ofrece dos estrategias para esta integración. Si usas transacciones gestionadas-por-bean (BMT) Hibernate le dirá al servidor de aplicaciones que comience y finalice una transacción BMT si usas 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 manejado. Con CMT, la demarcación de la transacción se hace en descriptores de despliegue de beans de sesión, no programáticamente. Si no quieres limpiar (flush) y cerrar manualmente la Session por ti mismo, solamente establece hibernate.transaction.flush_before_completion a true, hibernate.connection.release_mode a after_statement o auto y hibernate.transaction.auto_close_session a true. Hibernate limpiará y cerrará entonces automáticamente la Session para ti. Lo único que resta es deshacer (rollback) la transacción cuando ocurra una excepción. Afortunadamente, en un bean CMT, incluso esto ocurre automáticamente, ya que una RuntimeException no manejada disparada por un método de un bean de sesión le dice al contenedor que ponga a deshacer la transacción global. Esto significa que, en CMT, no necesitas usar en absoluto la API de Transaction de Hibernate. Nota que debes elegir org.hibernate.transaction.JTATransactionFactory en un bean de sesión BMT, y org.hibernate.transaction.CMTTransactionFactory en un bean de sesión CMT, cuando configures la fábrica de transacciones de Hibernate. Recuerda además establecer org.hibernate.transaction.manager_lookup_class. Si trabajas en un entorno CMT, y usas limpieza (flushing) y cierre automáticos de la sesión, podrías querer también usar la misma sesión en diferentes partes de tu código. Típicamente, en un entorno no manejado, usarías una variable ThreadLocal para tener la sesión, pero una sola petición de EJB puede ejecutarse en diferentes hebras (por ejemplo, un bean de sesión llamando a otro bean de sesión). Si no quieres molestarte en pasar tu Session por alrededor, la SessionFactory provee el método getCurrentSession(), que devuelve una sesión que está pegada al contexto de transacción JTA. ¡Esta es la forma más fácil de integrar Hibernate en una aplicación! La sesión "actual" siempre tiene habilitados limpieza, cierre y liberación de conexión automáticos (sin importar la configuración de las propiedades anteriores). Nuestra idioma de gestión de sesión/transacción se reduce a: En otras palabras, todo lo que tienes que hacer en un entorno manejado, es llamar a SessionFactory.getCurrentSession(), hacer tu trabajo de acceso a datos, y dejar el resto al contenedor. Los límites de transacción se establecen declarativamente en los descriptores de despliegue de tu bean de sesión. El ciclo de vida de la sesión es manejado completamente por Hibernate. Existe una advertencia al uso del modo de liberación de conexión after_statement. Debido a una limitación tonta de la especificación de JTA, no es posible para Hibernate limpiar automáticamente ningún ScrollableResults no cerrado ni instancias de Iterator devueltas por scroll() o iterate(). Debes liberar el cursor de base de datos subyacente llamando a ScrollableResults.close() o Hibernate.close(Iterator) explícitamente desde un bloque finally. (Por supuesto, la mayoría de las aplicaciones pueden evitarlo fácilmente no usando en absoluto ningún scroll() o iterate() desde el código CMT.) Manejo de excepciones Si la Session lanza una excepción (incluyendo cualquier SQLException), debes inmediatamente deshacer (rollback) la transacción de 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úrate que la Session sea cerrada llamando a close() en un bloque finally. La HibernateException, que envuelve la mayoría de los errores que pueden ocurrir en la capa de persistencia de Hibernate, en 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 (es decir, en las capas más altas) y se presenta un mensaje de error al usuario de la aplicación (o se toma alguna otra acción apropiada). Nota que Hibernate podría también lanzar otras excepciones no chequeadas que no sean una HibernateException. Una vez más, no son recuperables y debe tomarse una acción apropiada. Hibernate envuelve SQLExceptions 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 está siempre disponible vía 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 para el dialecto configurado; sin embargo, es también posible enchufar una implementación personalizada (ver los javadocs de la clase SQLExceptionConverterFactory para los 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 cayó en ninguna de las otras categorías. Control optimista de concurrencia El único enfoque que es consistente con alta concurrencia y alta escalabilidad es el control optimista de concurrencia con versionamiento. El chuequeo de versión usa números de versión, o timestamps, para detectar actualizaciones en conflicto (y para prevenir actualizaciones perdidas). Hibernate provee para tres enfoques posibles de escribir código de aplicación que use concurrencia optimista. Los casos de uso que hemos mostrado están en el contexto de transacciones de aplicación largas pero el chequeo de versiones tiene además el beneficio de prevenir actualizaciones perdidas en transacciones de base de datos solas. Chequeo de versiones de aplicación En una implementación sin mucha ayuda de Hibernate, cada interacción con la base de datos ocurre en una nueva Session y el desarrollador es 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 base de datos. Es el enfoque más similar a los EJBs de entidad. La propiedad version se mapea usando <version>, e Hibernate la incrementará automáticamente durante la limpieza si la entidad está sucia. Por supuesto, si estás operando un entorno de baja-concurrencia-de-datos y no requieres chequeo de versiones, puedes usar este enfoque y simplemente saltar el chequeo de versiones. En ese caso, el último compromiso (commit) gana será la estrategia por defecto para tus transacciones de aplicación largas. Ten en mente que esto podría confundir a los usuarios de la aplicación, pues podrían experimentar actualizaciones perdidas sin mensajes de error ni chance de fusionar los cambios conflictivos. Claramente, el chequeo manual de versiones es factible solamente en circunstancias muy triviales, y no es práctico para la mayoría de aplicaciones. Frecuentemente, no sólo intancias solas, sino grafos completos de objetos modificados tienen que ser chequeados. Hibernate ofrece chequeo de versiones automático con el paradigma de diseño de Session larga o de instancias separadas. Sesión larga y versionado automático Una sola instancia de Session y sus instancias persistentes son usadas para toda la transacción de aplicación. Hibernate chequea las versiones de instancia en el momento de limpieza (flush), lanzando una excepción si se detecta una modificación concurrente. Concierne al desarrollador capturar y manejar esta excepción (las opciones comunes son la oportunidad del usuario de fusionar los cambios, o recomenzar el proceso de negocio sin datos añejos). La Session se desconecta de cualquier conexión JDBC subyacente al esperar por una interacción del usuario. Este enfoque es el más eficiente en términos de acceso a base de datos. La aplicación no necesita tratar por sí misma con el chequeo de versiones, ni re-uniendo instancias separadas, ni tiene que recargar instancias en cada transacción de base de datos. El objeto foo todavía conoce en qué Session fue cargado. Session.reconnect() obtiene una nueva conexión (o puedes proveer una) y reasume la sesión. El método Session.disconnect() desconectará la sesión de la conexión JDBC y la devolverá la conexión al pool (a menos que hayas provisto la conexión). Después de la reconexión, para forzar un chequeo de versión en datos que no estés actualizando, puedes llamar a Session.lock() con LockMode.READ sobre cualquier objeto que pudiese haber sido actualizado por otra transacción. No necesitas bloquear ningún dato que sí estés actualizando. Si las llamadas explícitas a disconnect() y reconnect() son muy onerosas, puedes usar en cambio hibernate.connection.release_mode. Este patrón es problemático si la Session es demasiado grande para ser almacenada durante el tiempo de pensar del usuario, por ejemplo, una HttpSession debe mantenerse tan pequeña como sea posible. Ya que la Session es también el caché (obligatorio) de primer nivel y contiene todos los objetos cargados, podemos probablemente cargar esta estrategia sólo para unos pocos ciclos petición/respuesta. Esto está de hecho recomendado, ya que la Session tendrá pronto también datos añejos. Nota también que debes mantener la Session desconectada próxima a la capa de persistencia. En otras palabras, usa una sesión de EJB con estado para tener la Session y no transferirla a la capa web para almacenarla en la HttpSession (ni incluso serializarla a una capa separada). Objetos separados y versionado automático Cada interacción con el almacén persistente ocurre en una nueva Session. Sin embargo, las mismas instancias persistentes son reusadas para cada interacción con la base de datos. La aplicación manipula el estado de las instancias separadas originalmente cargadas en otra Session y luego las readjunta usando Session.update(), Session.saveOrUpdate(), o Session.merge(). De nuevo, Hibernate chequeará las versiones de instancia durante la limpieza (flush), lanzando una excepción si ocurrieron actualizaciones en conflicto. Puedes también llamar a lock() en vez de update() y usar LockMode.READ (realizando un chequeo de versión, puenteando todos los cachés) si estás seguro que el objeto no ha sido modificado. Personalizando el versionado automático Puedes deshabilitar el incremento de versión automático de Hibernate para propiedades en particular y colecciones estableciendo el atributo de mapeo optimistic-lock a false. Hibernate entonces no incrementará ya más las versiones si la propiedad está sucia. Los esquemas de base de datos heredados son frecuentemente estáticos y no pueden ser modificados. U 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 incluso timestamps. 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 timestamp, con una comparación del estado de todos los campos en una fila, activa optimistic-lock="all" en el mapeo de <class>. Nota que esto conceptualmente funciona solamente si Hibernate puede comparar el estado viejo y nuevo, es decir, si usas una sola Session larga y no sesión-por-petición-con-instancias-separadas. A veces las modificaciones concurrentes pueden permitirse, en cuanto los cambios que hayan sido hechos no se traslapen. Si estableces optimistic-lock="dirty" al mapear la <class>, Hibernate sólo comparará los campos sucios durante la limpieza. En ambos casos, con columnas de versión/timestamp dedicadas o con comparación de campos completa/sucios, Hibernate usa una sola sentencia UPDATE (con una cláusula WHERE apropiada) por entidad para ejecutar el chequeo de versiones y actualizar la información. Si usas persistencia transitiva para la re-unión en cascada de entidades asociadas, Hibernate podría ejecutar actualizaciones innecesarias. Esto usualmente no es un problema, pero podrían ejecutarse disparadores (triggers) on update en la base de datos incluso cuando no se haya hecho ningún cambio a las instancias separadas. Puedes 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. Bloqueo pesimista No se pretende que los usuarios gasten 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 pueden a veces 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 bloqueo de objetos en memoria! La clase LockMode define los diferentes niveles de bloqueo que pueden ser adquiridos por Hibernate. Un bloqueo se obtiene por los siguientes mecanismos: LockMode.WRITE se adquiere automáticamente cuando Hibernate actualiza o inserta una fila. LockMode.UPGRADE puede ser adquirido bajo petición explícita del usuario usando SELECT ... FOR UPDATE en base de datos que soporten esa sintáxis. LockMode.UPGRADE_NOWAIT puede ser adquirido bajo petición explícita del usuario usando un SELECT ... FOR UPDATE NOWAIT bajo Oracle. LockMode.READ es adquirido automáticamente cuando Hibernate lee datos bajo los niveles de aislamiento Repeatable Read o Serializable. Puede ser readquirido por 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 vía 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 aún cargado 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 usará un modo alternativo apropiado (en vez de lanzar una excepción). Esto asegura que las aplicaciones serán portables.