Transacciones Con Spring

De Dos Ideas.
Saltar a: navegación, buscar

Spring Framework permite configurar transacciones en forma declarativa. De esta forma, la manipulación de transacciones queda establecida en archivos de configuración, sin generar ningún impacto en el código.

Transacciones en Spring

En Spring cualquier bean puede ser transaccional. La transaccionalidad se declara por método. En ningún momento se utiliza explícitamente JTA, sino que Spring inyecta funcionalidad de acuerdo a ciertas reglas (similares a las de EJB).

Rollback de transacciones

Por default, una transacción falla solamente cuando el método tira una unchecked exception (las que no se declaran, heredan de RuntimeException). Cuando una transacción falla, se realiza un rollback.

Noten que, por lo tanto, las excepciones de negocio no realizan un rollback (declaradas en el "throws" del método). Es decir, pese a que se lance una excepción de negocio, igualmente se hará un commit de la transacción. Este comportamiento puede cambiarse en la configuración, como veremos más adelante.

Configuración

Spring 2.x introduce una forma extensible de agregar tags en los archivos de configuración. Así, existen distintas extensiones que se pueden agregar a la configuración de Spring, ampliando la funcionalidad.

Esta configuración se agrega en el encabezado de cada XML de Spring, e indica qué schemas utiliza dicho archivo.

Para la configuración de transacciones usaremos los schemas "aop" y "tx". El encabezado de nuestro archivo entonces queda:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:aop="http://www.springframework.org/schema/aop"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
</beans>

Pointcuts y Advices

Con ese encabezado estamos entonces listos para configurar transacciones. Lo que haremos será declarar todos los objetos que necesitamos sean transaccionales, y luego aplicaremos transacciones a todo un grupo de objetos.

Es decir, la configuración de transacciones ya no será "por objeto", sino mucho más amplia (por ejemplo, "todos las clases del paquete business").

Crearemos entonces un Pointcut que indicará qué clases serán interceptadas para agregar transacción, al cual le agregaremos un Advice. El advice indicará qué tipo de transacción tendrán las clases interceptadas por el pointcut.

En la práctica, esto es realmente muy simple:

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="buscar*" read-only="true"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut id="businessOperation" expression="execution(* com.dosideas.business.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="businessOperation"/>
</aop:config>


<bean id="business.PaisBo" class="com.dosideas.business.impl.PaisBoImpl"/>

<bean id="business.ProvinciaBo" class="com.dosideas.business.impl.ProvinciaBoImpl"/>
<bean id="business.LocalidadBo" class="com.dosideas.business.impl.LocalidadBoImpl"/>


Todas las clases de "business.impl" serán interceptadas, y se les aplicará el advice "txAdvice". Este advice indica que los métodos que comienzan con "buscar" son de sólo lectura, y el resto serán transaccionales (de tipo "REQUIRED", que es el default).

Es posible tambien tener mas de un pointcut:

<aop:pointcut id="businessOperation" 
              expression="execution(* com.dosideas.business.*.*(..)) 
                          and
                         !execution(* com.dosideas.business.Cliente.guardar(..))"/>
<aop:pointcut id="businessOperationGuardarCliente" 
              expression="execution(* com.dosideas.business.Cliente.guardar(..))"/>

El pointcut businessOperation referencia a todas las clases de business.impl, salvo el metodo guardar de la clase com.dosideas.business.Cliente (que es referenciado por el pointcut businessOperationGuardarCliente).


Noten entonces que la definición de transaccionalidad se escribe una única vez (o por paquete, o como sea más cómodo), y los los BO se declaran luego normalmente.

Configurando un Advice

Como sabemos, las transacciones realizan un rollback ante una RuntimeException, aunque esto es posible cambiarlo:

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="buscar*" read-only="true" rollback-for="ObjetoNoEncontradoException"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

Por otro lado, también es posible cambiar la propagación de las transacciones para determiandos métodos:

<tx:advice id="noTxAdvice">
    <tx:attributes>
        <tx:method name="*" propagation="NEVER"/>
    </tx:attributes>
</tx:advice>

Anotaciones

Es posible configurar las transacciones usando anotaciones en las clases en vez de declarando pointcuts en un XML. De esta manera, la declaración de la transacción queda más cerca del código que afecta.

Para esto es necesario agregar el siguiente tag al XML de Spring, para indicarle que habilite el soporte de transacciones por anotaciones: <tx:annotation-driven transaction-manager="transactionManager"/>

El atributo "transaction-manager" referencia al Transaction Manager en uso.

Luego, podemos usar la anotación @Transactional para declarar que un método (o todos los métodos de una clase) es transaccional: @Transactional public class DefaultFooService implements FooService {

 Foo getFoo(String fooName);
 Foo getFoo(String fooName, String barName);
 void insertFoo(Foo foo);
 void updateFoo(Foo foo);

}

También es posible pasarle parámetros a la anotación @Transactional para configurar detalles de la transacción. Por ejemplo, la siguiente clase declara a todos sus métodos como de sólo-lectura, excepto el método updateFoo(): @Transactional(readOnly = true) public class DefaultFooService implements FooService {

 public Foo buscarFoo(long codigo) { ... }
 @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
 public void updateFoo(Foo foo) {... }

}

Ver también