926 lines
56 KiB
XML
926 lines
56 KiB
XML
<chapter id="transactions" revision="1">
|
|
<title>Transacciones y Concurrencia</title>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<para>
|
|
Sin embargo, además del versionado automático, Hibernate ofrece una API (menor) para
|
|
bloqueo pesimista de filas, usando la sintáxis <literal>SELECT FOR UPDATE</literal>.
|
|
Esta API se discute más adelante en este capítulo:
|
|
</para>
|
|
|
|
<para>
|
|
Comenzamos la discusión del control de concurrencia en Hibernate con la granularidad
|
|
de <literal>Configuration</literal>, <literal>SessionFactory</literal>, y
|
|
<literal>Session</literal>, así como la base de datos y las transacciones de aplicación
|
|
largas.
|
|
</para>
|
|
|
|
<sect1 id="transactions-basics">
|
|
<title>Ámbitos de sesión y de transacción</title>
|
|
|
|
<para>
|
|
Una <literal>SessionFactory</literal> 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 <literal>Configuration</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Una <literal>Session</literal> 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 <literal>Session</literal> no obtendrá una <literal>Connection</literal>
|
|
JDBC (o un <literal>Datasource</literal>) a menos que sea necesario, de modo que puedas
|
|
abrir y cerrar seguramente una <literal>Session</literal> 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>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<para>
|
|
¿Qué es el ámbito de una unidad de trabajo? ¿Puede una sola <literal>Session</literal> 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 <literal>Session</literal> y cómo demarcas los
|
|
límites de la transacción de base de datos?
|
|
</para>
|
|
|
|
<sect2 id="transactions-basics-uow">
|
|
<title>Unidad de trabajo</title>
|
|
|
|
<para>
|
|
Primero, no uses el antipatrón <emphasis>sesión-por-operación</emphasis>, esto es,
|
|
¡no abras y cierres una <literal>Session</literal> 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.)
|
|
</para>
|
|
|
|
<para>
|
|
El patrón más común en una aplicación mutiusuario cliente/servidor es
|
|
<emphasis>sesión-por-petición</emphasis>. 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 <literal>Session</literal> 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 <literal>Session</literal>.
|
|
La relación entre las dos es uno-a-uno y este modelo es a la medida perfecta de muchas
|
|
aplicaciones.
|
|
</para>
|
|
|
|
<para>
|
|
El desafío yace en la implementación: no sólo tienen que comenzarse y terminarse correctamente
|
|
la <literal>Session</literal> 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 <literal>ServletFilter</literal>). Recomendamos ligar la
|
|
<literal>Session</literal> a la hebra que atiende la petición, usando una variable
|
|
<literal>ThreadLocal</literal>. 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 <literal>ThreadLocal</literal>. Los patrones de implementación para esto son
|
|
conocidos como <emphasis>Sesión Local de Hebra (ThreadLocal Session)</emphasis> y
|
|
<emphasis>Sesión Abierta en Vista (Open Session in View)</emphasis>. Puedes extender fácilmente
|
|
la clase de ayuda <literal>HibernateUtil</literal> 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.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-basics-apptx">
|
|
<title>Transacciones de aplicación</title>
|
|
|
|
<para>
|
|
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:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
Se abre la primera pantalla de un diálogo, los datos vistos por el usuario han sido
|
|
cargados en una <literal>Session</literal> y transacción de base de datos particular.
|
|
El usuario es libre de modificar los objetos.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
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.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Llamamos a esto unidad de trabajo, desde el punto de vista del usuario, una larga
|
|
<emphasis>transacción de aplicación</emphasis> ejecutándose. Hay muchas formas en
|
|
que puedes implementar esto en tu aplicación.
|
|
</para>
|
|
|
|
<para>
|
|
Una primera implementación ingenua podría mantener abierta la <literal>Session</literal>
|
|
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.
|
|
</para>
|
|
|
|
<para>
|
|
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:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Versionado Automático</emphasis> - 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.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Objetos Separados</emphasis> - Si decides usar el ya discutido patrón
|
|
de <emphasis>sesión-por-petición</emphasis>, 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 <emphasis>sesión-por-petición-con-objetos-separados</emphasis>. Se usa
|
|
versionado automático para aislar las modificaciones concurrentes.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Sesión Larga</emphasis> - La <literal>Session</literal> 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 <emphasis>sesión-por-transacción-de-aplicación</emphasis>
|
|
y hace la re-unión innecesaria. Para aislar las modificaciones concurrentes se usa el
|
|
versionado automático.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Tanto <emphasis>sesión-por-petición-con-objetos-separados</emphasis> como
|
|
<emphasis>sesión-por-transacción-de-aplicación</emphasis>, ambas tienen
|
|
ventajas y desventajas, las discutimos más adelante en este capítulo en el contexto
|
|
del control optimista de concurrencia.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-basics-identity">
|
|
<title>Considerando la identidad del objeto</title>
|
|
|
|
<para>
|
|
Una aplicación puede acceder concurrentemente a el mismo estado persistente en dos
|
|
<literal>Session</literal>s diferentes. Sin embargo, una instancia de una clase
|
|
persistente nunca se comparte entre dos instancias de <literal>Session</literal>.
|
|
Por lo tanto existen dos nociones diferentes de identidad:
|
|
</para>
|
|
|
|
<variablelist spacing="compact">
|
|
<varlistentry>
|
|
<term>Identidad de Base de Datos</term>
|
|
<listitem>
|
|
<para>
|
|
<literal>foo.getId().equals( bar.getId() )</literal>
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry>
|
|
<term>Identidad JVM</term>
|
|
<listitem>
|
|
<para>
|
|
<literal>foo==bar</literal>
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
</variablelist>
|
|
|
|
<para>
|
|
Entonces para objetos unidos a una <literal>Session</literal> <emphasis>en particular</emphasis>
|
|
(es decir en el ámbito de una <literal>Session</literal>) 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.
|
|
</para>
|
|
|
|
<para>
|
|
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
|
|
<literal>Session</literal>. Dentro de una <literal>Session</literal> la aplicación puede usar
|
|
con seguridad <literal>==</literal> para comparar objetos.
|
|
</para>
|
|
|
|
<para>
|
|
Sin embargo, una aplicación que usa <literal>==</literal> fuera de una <literal>Session</literal>,
|
|
podría ver resultados inesperados. Esto podría ocurrir incluso en sitios algo inesperados,
|
|
por ejemplo, si pones dos instancias separadas dentro del mismo <literal>Set</literal>.
|
|
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 <literal>equals()</literal> y
|
|
<literal>hashCode()</literal> 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
|
|
<literal>Set</literal>, cambiar el código hash rompe el contrato del <literal>Set</literal>.
|
|
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
|
|
<literal>Set</literal>. 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.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-basics-issues">
|
|
<title>Temas comunes</title>
|
|
|
|
<para>
|
|
Nunca uses los antipatrones <emphasis>sesión-por-sesión-de-usuario</emphasis> o
|
|
<emphasis>sesión-por-aplicación</emphasis> (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:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
Una <literal>Session</literal> 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 <literal>Session</literal>
|
|
fuese compartida. Si guardas tu <literal>Session</literal> de Hibernate en tu
|
|
<literal>HttpSession</literal> (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 <literal>Session</literal> en dos hebras ejecutándose
|
|
concurrentemente.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Una excepción lanzada por Hibernate significa que tienes que deshacer (rollback) tu
|
|
transacción de base de datos y cerrar la <literal>Session</literal> inmediatamente
|
|
(discutido en más detalle luego). Si tu <literal>Session</literal> 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.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
La <literal>Session</literal> 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
|
|
<literal>clear()</literal> y <literal>evict()</literal> para gestionar el caché de la
|
|
<literal>Session</literal>, pero probalemente debas considerar un procedimiento almacenado
|
|
si necesitas operaciones de datos masivas. Se muestran algunas soluciones en
|
|
<xref linkend="batch"/>. Mantener una <literal>Session</literal> abierta por la duración
|
|
de una sesión de usuario significa también una alta probabilidad de datos añejos.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="transactions-demarcation">
|
|
<title>Demarcación de la transacción de base de datos</title>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<para>
|
|
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 <literal>Session</literal> es hecho automáticamente.
|
|
</para>
|
|
|
|
<para>
|
|
Sin embargo, frecuentemente es deseable mantener portable tu capa de persistencia. Hibernate ofrece
|
|
una API de envoltura llamada <literal>Transaction</literal> 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.
|
|
</para>
|
|
|
|
<para>
|
|
Usualmente, finalizar una <literal>Session</literal> implica cuatro fases distintas:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
limpiar (flush) la sesión
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
comprometer la transacción
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
cerrar la sesión
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
manejar excepciones
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
|
|
<sect2 id="transactions-demarcation-nonmanaged">
|
|
<title>Entorno no manejado</title>
|
|
|
|
<para>
|
|
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í:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[//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();
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
No tienes que limpiar con <literal>flush()</literal> la <literal>Session</literal> explícitamente -
|
|
la llamada a <literal>commit()</literal> automáticamente dispara la sincronización.
|
|
</para>
|
|
|
|
<para>
|
|
Una llamada a <literal>close()</literal> marca el fin de una sesión. La principal implicación
|
|
de <literal>close()</literal> es que la conexión JDBC será abandonada por la sesión.
|
|
</para>
|
|
|
|
<para>
|
|
Este código Java es portable y se ejecuta tanto en entornos no manejados como en entornos JTA.
|
|
</para>
|
|
|
|
<para>
|
|
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 <literal>RuntimeException</literal> (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.
|
|
</para>
|
|
|
|
<para>
|
|
Nota que debes seleccionar <literal>org.hibernate.transaction.JDBCTransactionFactory</literal>
|
|
(que es el por defecto).
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-demarcation-jta">
|
|
<title>Usando JTA</title>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<para>
|
|
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 <literal>Transaction</literal>.
|
|
De modo que, el código de gestión de la transacción es idéntico al de un entorno no manejado.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// 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();
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
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 <literal>Session</literal>
|
|
por ti mismo, solamente establece <literal>hibernate.transaction.flush_before_completion</literal> a
|
|
<literal>true</literal>, <literal>hibernate.connection.release_mode</literal> a
|
|
<literal>after_statement</literal> o <literal>auto</literal> y
|
|
<literal>hibernate.transaction.auto_close_session</literal> a <literal>true</literal>. Hibernate
|
|
limpiará y cerrará entonces automáticamente la <literal>Session</literal> 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 <literal>RuntimeException</literal> 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. <emphasis>Esto significa que, en CMT, no necesitas usar en absoluto la API de
|
|
<literal>Transaction</literal> de Hibernate.</emphasis>
|
|
</para>
|
|
|
|
<para>
|
|
Nota que debes elegir <literal>org.hibernate.transaction.JTATransactionFactory</literal> en un
|
|
bean de sesión BMT, y <literal>org.hibernate.transaction.CMTTransactionFactory</literal> en un
|
|
bean de sesión CMT, cuando configures la fábrica de transacciones de Hibernate. Recuerda además
|
|
establecer <literal>org.hibernate.transaction.manager_lookup_class</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
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 <literal>ThreadLocal</literal> 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 <literal>Session</literal>
|
|
por alrededor, la <literal>SessionFactory</literal> provee el método
|
|
<literal>getCurrentSession()</literal>, 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:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// CMT idiom
|
|
Session sess = factory.getCurrentSession();
|
|
|
|
// do some work
|
|
...
|
|
|
|
]]></programlisting>
|
|
|
|
<para>
|
|
En otras palabras, todo lo que tienes que hacer en un entorno manejado, es llamar a
|
|
<literal>SessionFactory.getCurrentSession()</literal>, 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.
|
|
</para>
|
|
|
|
<para>
|
|
Existe una advertencia al uso del modo de liberación de conexión <literal>after_statement</literal>.
|
|
Debido a una limitación tonta de la especificación de JTA, no es posible para Hibernate
|
|
limpiar automáticamente ningún <literal>ScrollableResults</literal> no cerrado ni
|
|
instancias de <literal>Iterator</literal> devueltas por <literal>scroll()</literal> o
|
|
<literal>iterate()</literal>. <emphasis>Debes</emphasis> liberar el cursor de base de datos
|
|
subyacente llamando a <literal>ScrollableResults.close()</literal> o
|
|
<literal>Hibernate.close(Iterator)</literal> explícitamente desde un bloque <literal>finally</literal>.
|
|
(Por supuesto, la mayoría de las aplicaciones pueden evitarlo fácilmente no usando en absoluto ningún
|
|
<literal>scroll()</literal> o <literal>iterate()</literal> desde el código CMT.)
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-demarcation-exceptions">
|
|
<title>Manejo de excepciones</title>
|
|
|
|
<para>
|
|
Si la <literal>Session</literal> lanza una excepción (incluyendo cualquier
|
|
<literal>SQLException</literal>), debes inmediatamente deshacer (rollback) la
|
|
transacción de base de datos, llamar a <literal>Session.close()</literal> y
|
|
descartar la instancia de <literal>Session</literal>. Ciertos métodos de
|
|
<literal>Session</literal> <emphasis>no</emphasis> dejarán la sesión en un
|
|
estado consistente. Ninguna excepción lanzada por Hibernate puede ser tratada
|
|
como recuperable. Asegúrate que la <literal>Session</literal> sea cerrada llamando
|
|
a <literal>close()</literal> en un bloque <literal>finally</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
La <literal>HibernateException</literal>, 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 <literal>HibernateException</literal>. Una vez más, no son recuperables y debe
|
|
tomarse una acción apropiada.
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate envuelve <literal>SQLException</literal>s lanzadas mientras se interactúa con la base
|
|
de datos en una <literal>JDBCException</literal>. De hecho, Hibernate intentará convertir la excepción
|
|
en una subclase de <literal>JDBCException</literal> más significativa. La <literal>SQLException</literal>
|
|
está siempre disponible vía <literal>JDBCException.getCause()</literal>. Hibernate convierte la
|
|
<literal>SQLException</literal> en una subclase de <literal>JDBCException</literal> apropiada usando
|
|
el <literal>SQLExceptionConverter</literal> adjunto a la <literal>SessionFactory</literal>. Por defecto,
|
|
el <literal>SQLExceptionConverter</literal> está definido para el dialecto configurado; sin embargo,
|
|
es también posible enchufar una implementación personalizada (ver los javadocs de la clase
|
|
<literal>SQLExceptionConverterFactory</literal> para los detalles). Los subtipos estándar de
|
|
<literal>JDBCException</literal> son:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
<literal>JDBCConnectionException</literal> - indica un error con la comunicación JDBC subyacente.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>SQLGrammarException</literal> - indica un problema de gramática o sintáxis con el
|
|
SQL publicado.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>ConstraintViolationException</literal> - indica alguna forma de violación de restricción
|
|
de integridad.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>LockAcquisitionException</literal> - indica un error adquiriendo un nivel de bloqueo
|
|
necesario para realizar una operación solicitada.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>GenericJDBCException</literal> - una excepción genérica que no cayó en ninguna de las
|
|
otras categorías.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="transactions-optimistic">
|
|
<title>Control optimista de concurrencia</title>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<sect2 id="transactions-optimistic-manual">
|
|
<title>Chequeo de versiones de aplicación</title>
|
|
|
|
<para>
|
|
En una implementación sin mucha ayuda de Hibernate, cada interacción con la base de datos ocurre en una
|
|
nueva <literal>Session</literal> 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.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// 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();]]></programlisting>
|
|
|
|
<para>
|
|
La propiedad <literal>version</literal> se mapea usando <literal><version></literal>,
|
|
e Hibernate la incrementará automáticamente durante la limpieza si la entidad está sucia.
|
|
</para>
|
|
|
|
<para>
|
|
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, <emphasis>el último compromiso (commit) gana</emphasis> 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.
|
|
</para>
|
|
|
|
<para>
|
|
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 <literal>Session</literal> larga o de instancias separadas.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-optimistic-longsession">
|
|
<title>Sesión larga y versionado automático</title>
|
|
|
|
<para>
|
|
Una sola instancia de <literal>Session</literal> 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).
|
|
</para>
|
|
|
|
<para>
|
|
La <literal>Session</literal> 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.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// foo is an instance loaded earlier by the Session
|
|
session.reconnect(); // Obtain a new JDBC connection
|
|
Transaction t = session.beginTransaction();
|
|
foo.setProperty("bar");
|
|
t.commit(); // End database transaction, flushing the change and checking the version
|
|
session.disconnect(); // Return JDBC connection ]]></programlisting>
|
|
|
|
<para>
|
|
El objeto <literal>foo</literal> todavía conoce en qué <literal>Session</literal> fue cargado.
|
|
<literal>Session.reconnect()</literal> obtiene una nueva conexión (o puedes proveer una) y
|
|
reasume la sesión. El método <literal>Session.disconnect()</literal> 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 <literal>Session.lock()</literal> con <literal>LockMode.READ</literal> sobre
|
|
cualquier objeto que pudiese haber sido actualizado por otra transacción. No necesitas bloquear
|
|
ningún dato que <emphasis>sí estés</emphasis> actualizando.
|
|
</para>
|
|
|
|
<para>
|
|
Si las llamadas explícitas a <literal>disconnect()</literal> y <literal>reconnect()</literal>
|
|
son muy onerosas, puedes usar en cambio <literal>hibernate.connection.release_mode</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Este patrón es problemático si la <literal>Session</literal> es demasiado grande para ser almacenada
|
|
durante el tiempo de pensar del usuario, por ejemplo, una <literal>HttpSession</literal> debe
|
|
mantenerse tan pequeña como sea posible. Ya que la <literal>Session</literal> 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 <literal>Session</literal> tendrá pronto también datos añejos.
|
|
</para>
|
|
|
|
<para>
|
|
Nota también que debes mantener la <literal>Session</literal> desconectada próxima a la capa
|
|
de persistencia. En otras palabras, usa una sesión de EJB con estado para tener la
|
|
<literal>Session</literal> y no transferirla a la capa web para almacenarla en la
|
|
<literal>HttpSession</literal> (ni incluso serializarla a una capa separada).
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-optimistic-detached">
|
|
<title>Objetos separados y versionado automático</title>
|
|
|
|
<para>
|
|
Cada interacción con el almacén persistente ocurre en una nueva <literal>Session</literal>.
|
|
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
|
|
<literal>Session</literal> y luego las readjunta usando <literal>Session.update()</literal>,
|
|
<literal>Session.saveOrUpdate()</literal>, o <literal>Session.merge()</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// 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();]]></programlisting>
|
|
|
|
<para>
|
|
De nuevo, Hibernate chequeará las versiones de instancia durante la limpieza (flush),
|
|
lanzando una excepción si ocurrieron actualizaciones en conflicto.
|
|
</para>
|
|
|
|
<para>
|
|
Puedes también llamar a <literal>lock()</literal> en vez de <literal>update()</literal>
|
|
y usar <literal>LockMode.READ</literal> (realizando un chequeo de versión, puenteando
|
|
todos los cachés) si estás seguro que el objeto no ha sido modificado.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-optimistic-customizing">
|
|
<title>Personalizando el versionado automático</title>
|
|
|
|
<para>
|
|
Puedes deshabilitar el incremento de versión automático de Hibernate para propiedades en particular
|
|
y colecciones estableciendo el atributo de mapeo <literal>optimistic-lock</literal> a
|
|
<literal>false</literal>. Hibernate entonces no incrementará ya más las versiones si la propiedad está
|
|
sucia.
|
|
</para>
|
|
|
|
<para>
|
|
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
|
|
<literal>optimistic-lock="all"</literal> en el mapeo de <literal><class></literal>.
|
|
Nota que esto conceptualmente funciona solamente si Hibernate puede comparar el estado viejo y nuevo,
|
|
es decir, si usas una sola <literal>Session</literal> larga y no
|
|
sesión-por-petición-con-instancias-separadas.
|
|
</para>
|
|
|
|
<para>
|
|
A veces las modificaciones concurrentes pueden permitirse, en cuanto los cambios que hayan sido
|
|
hechos no se traslapen. Si estableces <literal>optimistic-lock="dirty"</literal> al mapear la
|
|
<literal><class></literal>, Hibernate sólo comparará los campos sucios durante la limpieza.
|
|
</para>
|
|
|
|
<para>
|
|
En ambos casos, con columnas de versión/timestamp dedicadas o con comparación de campos
|
|
completa/sucios, Hibernate usa una sola sentencia <literal>UPDATE</literal>
|
|
(con una cláusula <literal>WHERE</literal> 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)
|
|
<emphasis>on update</emphasis> en la base de datos incluso cuando no se haya hecho ningún cambio
|
|
a las instancias separadas. Puedes personalizar este comportamiento estableciendo
|
|
<literal>select-before-update="true"</literal> en el mapeo de <literal><class></literal>,
|
|
forzando a Hibernate a <literal>SELECT</literal> la instancia para asegurar que las actualizaciones
|
|
realmente ocurran, antes de actualizar la fila.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="transactions-locking">
|
|
<title>Bloqueo pesimista</title>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<para>
|
|
¡Hibernate siempre usará el mecanismo de bloqueo de la base de datos, nunca bloqueo
|
|
de objetos en memoria!
|
|
</para>
|
|
|
|
<para>
|
|
La clase <literal>LockMode</literal> define los diferentes niveles de bloqueo que pueden ser adquiridos
|
|
por Hibernate. Un bloqueo se obtiene por los siguientes mecanismos:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode.WRITE</literal> se adquiere automáticamente cuando Hibernate actualiza o
|
|
inserta una fila.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode.UPGRADE</literal> puede ser adquirido bajo petición explícita del usuario
|
|
usando <literal>SELECT ... FOR UPDATE</literal> en base de datos que soporten esa sintáxis.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode.UPGRADE_NOWAIT</literal> puede ser adquirido bajo petición explícita del usuario
|
|
usando un <literal>SELECT ... FOR UPDATE NOWAIT</literal> bajo Oracle.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode.READ</literal> 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.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode.NONE</literal> representa la ausencia de un bloqueo. Todos los objetos se pasan
|
|
a este modo de bloqueo al final de una <literal>Transaction</literal>. Los objetos asociados con una
|
|
sesión vía una llamada a <literal>update()</literal> o <literal>saveOrUpdate()</literal> también
|
|
comienzan en este modo de bloqueo.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
La "petición explícita del usuario" se expresa en una de las siguientes formas:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
Una llamada a <literal>Session.load()</literal>, especificando un <literal>LockMode</literal>.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Una llamada a <literal>Session.lock()</literal>.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Una llamada a <literal>Query.setLockMode()</literal>.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Si se llama a <literal>Session.load()</literal> con <literal>UPGRADE</literal> o
|
|
<literal>UPGRADE_NOWAIT</literal>, y el objeto pedido no ha sido aún cargado por la sesión, el objeto es
|
|
cargado usando <literal>SELECT ... FOR UPDATE</literal>. Si se llama a <literal>load()</literal> para
|
|
un objeto que ya esté cargado con un bloqueo menos restrictivo que el pedido, Hibernate llama a
|
|
<literal>lock()</literal> para ese objeto.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>Session.lock()</literal> realiza un chequeo de número de versión si el modo de bloqueo especificado
|
|
es <literal>READ</literal>, <literal>UPGRADE</literal> o <literal>UPGRADE_NOWAIT</literal>. (En el caso de
|
|
<literal>UPGRADE</literal> o <literal>UPGRADE_NOWAIT</literal>, se usa
|
|
<literal>SELECT ... FOR UPDATE</literal>.)
|
|
</para>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|