iberck
11/5/2015 - 8:59 AM

Transacciones en spring/hibernate

Transacciones en hibernate

#Transacciones en spring framework

Mapa mental con los conceptos

Rollback y excepciones

Checked exception: No causa rollback.

Runtime exception (o subclases): Lanza rollback

Estas reglas se definieron así pensando que en las checked exceptions el usuario tiene posibilidad de hacer algo (el rollback debe ser manual).

Reglas @Transactional

  • Sólo debe ser puesto en métodos públicos. Si anota métodos con visibilidad private, protected o package no se lanza ningún error pero no se toma en cuenta la anotación.
  • Spring recomienda anotar las clases concretas en vez de las interfaces.
  • En el modo proxy (default) sólo las llamadas que vienen de métodos externos son interceptadas por el proxy. Esto significa que la autoinvocación directa (this.metodo) no conducirá a una transacción aún cuando el método sea público y esté anotado con @Transactional.
  • El proxy debe estar completamente inicializado, no deberá confiar en @Transactional dentro de @PostConstruct.

Se pueden cambiar los comportamientos descritos anteriormente cambiando el comportamiento default y utilizando en algunos casos aspectj.

Transacción local y distribuida

Las transacciones locales involucran un mismo recurso (p.e. la misma base de datos), las transacciones globales o distribuidas involucran varios recursos (p.e. múltiples bases de datos de distintos proveedores).

JDBC y transacciones

Las transacciones también agrupan operaciones de acceso a datos, de hecho cualquier sentencia SQL, sean queries o DML, debe ser ejecutada dentro de una transacción de base de datos. No puede haber comunicación con la base de datos si no existe una transacción. Por lo tanto 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.

Un método para solventar esto (para no estar creando tx por cada operación), es el modo autocommit true donde cualquier sentencia simple es envuelta en una transacción muy corta. Este modo nunca es apropiado para una aplicación, solo se utiliza para la ejecución de comandos en una consola. Hibernate deshabilita o espera que el entorno (J2EE/JEE) deshabilite el modo autocommit.

El método correcto para las aplicaciones es marcar autocommit=false y definir explicitamente dónde empieza y dónde hacer commit/rollback de la transacción (ya sea con anotaciones o programáticamente).

Desenmascarando mitos de las transacciones

Ninguna sentencia sql puede ser lanzada hacia la base de datos si no existe una transacción de base de datos. El término acceso de datos no transaccional (notransactional data access) significa que no hay una transacción definida a nivel de sistema por lo tanto se utiliza el modo autocommit=true. Esto no significa que estén involucradas transacciones de base de datos en el acceso de datos no transaccional.

La recomendación es no utilizar el modo autocommit=true en una aplicación, y aplicar transacciones read-only solo cuando exista un beneficio obvio en performance o cuando se desee indicar explicitamente que el método no escribe hacia la base de datos. Siempre prefiera transacciones regulares para agrupar las operaciones de acceso a datos sin importar si lee o escribe datos.

Acceso NO transaccional con hibernate

Considere el siguiente código:

Session session = getSessionFactory().openSession();   
session.get(Item.class, 123l);   
session.close();   

Lo que sucede es lo siguiente:

  1. Una nueva sesión es abierta. No se obtiene una conexión a base de datos en este punto.
  2. La llamada a get() lanza un SQL SELECT. La sesión ahora obtiene una conexión JDBC del connection pool. Hibernate, por default, inmediatamente deshabilita el modo autocommit sobre esta conexión con setAutoCommit(false). Esto efectivamente comienza una transacción JDBC.
  3. El SELECT es ejecutado dentro de esta transacción JDBC. La sesión es cerrada , y la conexión retornada al pool y liberada por hibernate (hibernate llama a conn.close()). Pero qué pasa con la transacción uncommited?

La respuesta a la pregunta es ¡depende!. La especificación JDBC no dice nada sobre qué hacer con las transacciones pendientes cuando se invoca close() sobre una conexión. Lo que sucede depende del vendedor que implementa la especificación. El driver JDBC de oracle, por ejemplo hace commit de la transacción cuando se invoca a close(). Muchos otros vendedores JDBC toman el camino más sano y hacen rollback de cualquier transacción pendiente cuando la conexión es cerrada y el recurso regresado al pool. Obviamente, esto no es un problema para la sentencia SELECT pero veamos otra variación:

Session session = getSessionFactory().openSession();   
Long generatedId = session.save(item);   
session.close();  

Este código resulta en una sentencia INSERT, ejecutado dentro de una transacción que nunca es commited o rollback. En oracle, esta pieza de código inserta los datos permanentemente, en otras bases de datos, podría no hacerlo!.

De hecho aún no hemos tocado el modo autocommit, solo se ha puesto de manifiesto un problema que puede aparecer wi intenta trabajar sin trabajar explicitamente con transacciones. Supongamos que usted aún piensa que trabajar sin transacciones es buena idea y desea trabajar con el modo autocommit. Primero, usted necesita decirle a hibernate que debe permitir conexiones con autocommit:

<property name="connection.autocommit">true</property>

Con este parámetro, Hibernate ya no apagará el modo autocommit cuando una conexión sea obtenida del pool. Los ejemplos previos ahora trabajarán de manera predecible, y el driver JDBC englobará las sentencias SQL en una transacción corta --con las implicaciones que se ha platicado anteriormente.

En resumen, se recomienda utilizar siempre transacciones regulares para realizar operaciones hacia la base de datos sin importar si son de lectura o escritura.

Transactional(readOnly=true)

Haciendo la transacción readonly se deshabilita el dirty checking (y por lo tanto su costo en performance).

@Transactional(readOnly=true)
public void someBusinessMethod() {
    ....
}

setRollbackOnly

Marca la transacción actual como rollback. Una transacción marcada como rollback será deshecha y nunca puede ser commited.

Cuando una transacción se marca como setRollbackOnly afecta a las transacciones externas que se unieron a la transacción y serán deshechas (rollback).

No es común llamar a setRollbackOnly() manualmente, a menudo se activa por automático cuando se lanza una RuntimeException.

Transacciones y proxies

When Spring loads your bean definitions, and has been configured to look for @Transactional annotations, it will create these proxy objects around your actual bean. These proxy objects are instances of classes that are auto-generated at runtime. The default behaviour of these proxy objects when a method is invoked is just to invoke the same method on the "target" bean (i.e. your bean).

However, the proxies can also be supplied with interceptors, and when present these interceptors will be invoked by the proxy before it invokes your target bean's method. For target beans annotated with @Transactional, Spring will create a TransactionInterceptor, and pass it to the generated proxy object. So when you call the method from client code, you're calling the method on the proxy object, which first invokes the TransactionInterceptor (which begins a transaction), which in turn invokes the method on your target bean. When the invocation finishes, the TransactionInterceptor commits/rolls back the transaction. It's transparent to the client code.

As for the "external method" thing, if your bean invokes one of its own methods, then it will not be doing so via the proxy. Remember, Spring wraps your bean in the proxy, your bean has no knowledge of it. Only calls from "outside" your bean go through the proxy.

From http://stackoverflow.com/questions/1099025/spring-transactional-what-happens-in-background

It's a limitation with Spring AOP. (dynamic objects and CGLIB). If you configure Spring to use AspectJ to handle the transactions, your code will work.

The problem here is, that Spring's AOP proxies don't extend but rather wrap your service instance to intercept calls. This has the effect, that any call to "this" from within your service instance is directly invoked on that instance and cannot be intercepted by the wrapping proxy (the proxy is not even aware of any such call).

El comportamiento default de esos objetos proxy cuando un método es invocado es invocar el mismo método sobre el target bean, por tal motivo this siempre será del tipo de la clase en la que se encuentra y nunca del proxy (aunque el método sea invocado a través del proxy).

Por tal motivo se omite @Transactional en los métodos que se invocan de la misma clase (self-invocations) ya que se están invocando a través de un objeto que no es el proxy. Para llamar un método dentro de la misma clase y tome en cuenta @Transactional hay que inyectar un objeto de la misma clase e invocar el método a través de ella.

Rerefencia

https://developer.jboss.org/wiki/Non-transactionaldataaccessandtheauto-commitmode

http://www.codesenior.com/tutorial/Hibernate-Session-Commit-Rollback-Save-Concepts