La mayoría de los usuarios de Spring Framework eligen la gestión de transacciones declarativas. Esta opción tiene el menor impacto en el código de la aplicación y, por lo tanto, es más consistente con la idea de un contenedor liviano no invasivo.

La gestión de transacciones declarativas en Spring Framework es posible gracias a la programación orientada a aspectos (AOP) en primavera. Sin embargo, dado que el código del aspecto transaccional se distribuye con la distribución Spring Framework y se puede usar como plantilla, generalmente no se requiere una comprensión de los conceptos de AOP para usar este código de manera efectiva.

Gestión de transacciones declarativas en Spring El marco es similar al CMT de EJB, ya que es posible determinar la lógica de la transacción (o la falta de ella) hasta el nivel de los métodos individuales. Si es necesario, puede llamar a setRollbackOnly() en el contexto de una transacción. Las diferencias entre los dos tipos de gestión de transacciones son las siguientes:

  • A diferencia del CMT de EJB, que está vinculado a JTA, la gestión de transacciones declarativas de Spring Framework funciona en cualquier entorno. Puede funcionar con transacciones JTA o transacciones locales usando JDBC, JPA o Hibernate ajustando los archivos de configuración.

  • Puede aplicar la gestión de transacciones declarativas en Spring Framework a cualquier clase, no solo a clases especiales como clases EJB.

  • Spring Framework ofrece reglas de reversiónque no tienen análogos en EJB. Hay soporte programático y declarativo para reglas de reversión.

  • Spring Framework le permite personalizar la lógica de transacciones usando AOP. Por ejemplo, puede implementar una lógica personalizada para trabajar en caso de que se revierta una transacción. También puede agregar consejos personalizados junto con consejos transaccionales. Cuando se utiliza CMT desde un EJB, no se puede influir en la gestión de transacciones del contenedor excepto mediante el uso de setRollbackOnly().

  • Spring Framework no admite la propagación de contextos de transacciones entre llamadas remotas como lo hacen los servidores de aplicaciones de alta gama. Si necesita esta funcionalidad, le recomendamos utilizar EJB. Sin embargo, piense detenidamente antes de utilizar dicha función porque, en general, las transacciones no deberían estar cubiertas por llamadas remotas.

El concepto de reglas de reversión es muy importante. Estas reglas le permiten especificar qué excepciones (y eventos generados) deberían dar como resultado una reversión automática. Puede establecer esto de forma declarativa, en la configuración, en lugar de en el código Java. Entonces, si bien todavía puedes llamar a setRollbackOnly() en un objeto TransactionStatus para revertir la transacción actual, a menudo es mejor establecer la regla de que la excepción es MyApplicationExceptionException siempre debería resultar en una reversión. Una ventaja significativa de esta opción es que los objetos comerciales no dependerán de la infraestructura de transacciones. Por ejemplo, normalmente no requieren importar las API de transacciones de Spring u otras API de Spring.

Aunque la lógica predeterminada del contenedor EJB revierte automáticamente una transacción ante una excepción del sistema (normalmente una excepción de tiempo de ejecución), EJB CMT sí lo hace. no revierte una transacción automáticamente cuando ocurre una excepción de la aplicación (es decir, una excepción marcada distinta de java.rmi.RemoteException). Aunque la lógica predeterminada de Spring para la gestión de transacciones declarativas sigue la convención EJB (la reversión se produce automáticamente solo para excepciones no verificadas), a menudo resulta útil personalizar esta lógica.

Comprensión básica de la implementación de transacciones declarativas de Spring Framework

No basta con decir simplemente que necesita marcar sus clases con la anotación @Transactional, agregar la anotación @EnableTransactionManagement a su configuración y esperar que comprenda cómo funciona todo. Para proporcionar una comprensión más profunda, esta sección describe los aspectos internos del marco de transacciones declarativas de Spring Framework en el contexto de cuestiones relacionadas con las transacciones.

Los conceptos más importantes que se deben comprender con respecto al soporte de transacciones declarativas de Spring Framework son: que este soporte lo proporciona proxy AOP y que el asesoramiento transaccional se basa en metadatos (actualmente basado en XML o anotaciones). La combinación de AOP con metadatos transaccionales produce un proxy AOP que utiliza un TransactionInterceptor junto con una implementación TransactionManager correspondiente para administrar transacciones en torno a llamadas a métodos.

AOP en Spring se analiza en sección por AOP.

TransactionInterceptor en Spring Framework proporciona gestión de transacciones para modelos de programación imperativos y reactivos. El interceptor determina el tipo deseado de control de transacciones verificando el tipo de devolución del método. Los métodos que devuelven un tipo reactivo, como Publisher o Flow (o un subtipo de los mismos) en Kotlin, son adecuados para gestionar transacciones reactivas. Todos los demás tipos de devolución, incluido void, utilizan la ruta de ejecución del código para la gestión de transacciones imperativa.

Los detalles de la gestión de transacciones afectan qué administrador de transacciones se requiere. Las transacciones imperativas requieren un PlatformTransactionManager y las transacciones reactivas utilizan una implementación de ReactiveTransactionManager.

La anotación @Transactional normalmente funciona con transacciones vinculadas a subprocesos administradas por PlatformTransactionManager, abriendo una transacción para todas las operaciones de acceso a datos en el subproceso que se está ejecutando actualmente. Nota: Esto no se aplica a los subprocesos recién iniciados dentro de un método.

Una transacción reactiva administrada por un ReactiveTransactionManager utiliza el contexto de Reactor en lugar del subproceso. -atributos locales. Como consecuencia, todas las operaciones de acceso a datos participantes deben ejecutarse en el mismo contexto desde Reactor en el mismo canal reactivo.

La siguiente figura muestra una representación conceptual de una llamada a un método para un proxy transaccional:

Un ejemplo de implementación de transacción declarativa

Considere la siguiente interfaz y su implementación correspondiente. Este ejemplo utiliza las clases Foo y Bar como marcadores de posición para que pueda concentrarse en el uso de transacciones sin distraerse con un modelo de dominio específico. A los efectos de este ejemplo, el hecho de que la clase DefaultFooService arroje instancias de UnsupportedOperationException en el cuerpo de cada método implementado es positivo. Esta lógica operativa le permite comprender cómo se crean las transacciones y luego se revierten en respuesta a una instancia de UnsupportedOperationException. La siguiente lista muestra la interfaz FooService:

Java

// interfaz de servicio que queremos hacer paquete transaccional
package x.y.service;
public interface FooService {
    Foo getFoo(String fooName);
    Foo getFoo(String fooName, String barName);
    void insertFoo(Foo foo);
    void updateFoo(Foo foo);
}
Kotlin

// interfaz de servicio que queremos hacer paquete transaccional
package x.y.service
interface FooService {
    fun getFoo(fooName: String): Foo
    fun getFoo(fooName: String, barName: String): Foo
    fun insertFoo(foo: Foo)
    fun updateFoo(foo: Foo)
}

El siguiente ejemplo muestra la implementación de la interfaz anterior:

Java

package x.y.service;
public class DefaultFooService implements FooService {
    @Override
    public Foo getFoo(String fooName) {
        // ...
    }
    @Override
    public Foo getFoo(String fooName, String barName) {
        // ...
    }
    @Override
    public void insertFoo(Foo foo) {
        // ...
    }
    @Override
    public void updateFoo(Foo foo) {
        // ...
    }
}
Kotlin

package x.y.service
class DefaultFooService : FooService {
    override fun getFoo(fooName: String): Foo {
        // ...
    }
    override fun getFoo(fooName: String, barName: String): Foo {
        // ...
    }
    override fun insertFoo(foo: Foo) {
        // ...
    }
    override fun updateFoo(foo: Foo) {
        // ...
    }
}

Supongamos que los dos primeros métodos de la interfaz FooService, getFoo(String) y getFoo(String, String), deben ejecutarse en el contexto de alguna transacción con semántica de solo lectura, y los otros métodos, insertFoo(Foo) y updateFoo(Foo), deben ejecutarse en el contexto de una transacción con semántica de lectura-escritura. La siguiente configuración se detalla en los siguientes párrafos:


<!-- del archivo "context.xml' -->
<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">;
    <!-- este es el objeto de servicio que queremos convertir en transaccional -->
    <bean id="fooService" class= "x.y.service.DefaultFooService"/>
    <!-- Consejo transaccional (qué está "pasando"; consulte <aop:advisor/> frijol a continuación) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- semántica transaccional...... -->
        <tx:attributes>
            <!-- todos los métodos que comienzan con "get" son de sólo lectura -->
            <tx:method name="get*" read-only="true"/>
            <!-- otros métodos utilizan parámetros de transacción predeterminados (ver más abajo) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <!-- asegúrese de que el Consejo transaccional anterior se ejecute cada vez que se ejecute
            una operación definida por la interfaz FooService -->
    <aop:config>;
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>
    <!-- no te olvides de DataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>
    <!-- Del mismo modo, no se olvide del TransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- otras definiciones <bean/--> -->
</beans>

Examine la configuración anterior. La suposición es que necesita hacer que el objeto de servicio, el bean fooService, sea transaccional. La semántica de transacción que debe aplicarse está contenida en la definición <tx:advice/>. La definición de <tx:advice/> establece: "todos los métodos que comienzan con get deben ejecutarse en el contexto de una transacción de solo lectura, y todos los demás métodos debe ejecutarse con la semántica de transacción predeterminada". El atributo transaction-manager de la etiqueta <tx:advice/> se indica en el nombre del bean TransactionManager que gestionará las transacciones. (en este caso es el bean txManager).

Puedes omitir el transaction-manager atributo en la transacción Advice (<tx:advice/>), si el nombre del bean TransactionManager que desea conectar es transactionManager. Si el bean TransactionManager que necesita asociar tiene cualquier otro nombre, entonces debe usar el atributo transaction-manager explícitamente, como en el ejemplo anterior.

La definición <aop:config/> permite garantizar que las recomendaciones transaccionales definidas por el bean txAdvice se ejecuten en los puntos apropiados del programa. Primero, se define un segmento que se asigna para realizar cualquier operación definida en la interfaz FooService(fooServiceOperation). Luego, el segmento se vincula a txAdvice mediante Advisor. El resultado muestra que cuando se ejecuta fooServiceOperation, el Consejo definido por txAdvice comienza a ejecutarse.

La expresión definida en el elemento <aop:pointcut/>, es una expresión de corte de AspectJ. Para obtener más información sobre cómo dividir expresiones en Spring, consulte la sección sobre AOP.

A menudo es necesario hacer transaccional toda la capa de servicio. La mejor manera de hacerlo es modificar la expresión del segmento para que coincida con cualquier operación de nivel de servicio. El siguiente ejemplo muestra cómo hacer esto:


<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
El ejemplo anterior supone que todas las interfaces de servicio están definidas en paquete x.y.service. Para obtener más información, consulte la sección AOP.

Ahora que hemos analizado configuración, entonces puede que se pregunte: "¿Qué hace exactamente toda esta configuración?"

La configuración mostrada anteriormente se utiliza para crear un proxy transaccional alrededor del objeto que se crea a partir de la definición del bean fooService. El proxy se configura mediante asesoramiento transaccional de tal manera que cuando se llama al método correspondiente en el proxy, la transacción se inicia, se suspende, se marca como de solo lectura, etc., según la configuración de la transacción asociada con ese método. Considere el siguiente programa, que realiza una ejecución de prueba de la configuración mostrada anteriormente:

Java

public final class Boot {
    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
        FooService fooService = ctx.getBean(FooService.class);
        fooService.insertFoo(new Foo());
    }
}
Kotlin

import org.springframework.beans.factory.getBean
fun main() {
    val ctx = ClassPathXmlApplicationContext("context.xml")
    val fooService = ctx.getBean<FooService>("fooService")
    fooService.insertFoo(Foo())
}

La salida del programa anterior debería verse así (la salida de Log4J y el seguimiento de la pila de la UnsupportedOperationException lanzada por el método insertFoo(..) del DefaultFooService se truncaron para mayor claridad):


<!-- Se inicia el contenedor Spring... --> 
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creación de proxy implícito para el bean 'fooService' con 0 interceptores comunes y 1 interceptor específico 
<!-- DefaultFooService en realidad es proxy --> 
[JdkDynamicAopProxy] - Creación de proxy dinámico JDK para [x.y.service.DefaultFooService] 
<!-- ... ahora se llama al método insertFoo(..) en el proxy --> 
[TransactionInterceptor] - Obteniendo transacción para x.y.service.FooService.insertFoo 
<!-- aquí es donde entra en juego el Consejo de transacción... --> 
[DataSourceTransactionManager] - Creación de una nueva transacción con el nombre [x.y.service.FooService.insertFoo] 
[DataSourceTransactionManager] - Conexión adquirida [org.apache.commons.dbcp.PoolableConnection@a53de4] para la transacción JDBC 
<!-- método insertFoo(..) de DefaultFooService arroja una excepción... --> 
[RuleBasedTransactionAttribute] - Aplicar reglas para determinar si la transacción debe revertirse en java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invocar la reversión de una transacción en x.y.service.FooService.insertFoo debido a una 
[java.lang.UnsupportedOperationException] arrojable 
<!-- y transacción se revierte (de forma predeterminada, las instancias de RuntimeException dan como resultado una reversión) --> 
[DataSourceTransactionManager] - Revertir la transacción JDBC en la conexión [org.apache.commons.dbcp.PoolableConnection@a53de4] 
[DataSourceTransactionManager] - Liberar la conexión JDBC después de la transacción 
[DataSourceUtils] - Devolver la conexión JDBC a la excepción DataSource 
en el hilo "principal" java.lang .UnsupportedOperationException en x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14) 
<!-- Se eliminaron elementos de seguimiento de la pila de infraestructura de AOP para mayor claridad --> 
en $Proxy0.insertFoo(Fuente desconocida) 
en Boot.main(Boot.java:11)

Para utilizar la gestión de transacciones reactivas, el código debe utilizar tipos reactivos .

Spring Framework utiliza ReactiveAdapterRegistry para determinar si el tipo de retorno de un método es reactivo.

El siguiente listado muestra una versión modificada del FooService usado anteriormente, pero esta vez el código usa tipos reactivos:

Java
 
// interfaz reactiva del servicio que queremos convertir en paquete transaccional
package x.y.service;
public interface FooService {
    Flux<Foo> getFoo(String fooName);
    Publisher<Foo> getFoo(String fooName, String barName);
    Mono<Void> insertFoo(Foo foo);
    Mono<Void> updateFoo(Foo foo);
}
Kotlin

// la interfaz de servicio reactivo que queremos hacer paquete transaccional
package x.y.service
interface FooService {
    fun getFoo(fooName: String): Flow<Foo>
    fun getFoo(fooName: String, barName: String): Publisher<Foo>
    fun insertFoo(foo: Foo) : Mono<Void>
    fun updateFoo(foo: Foo) : Mono<Void>
}

El siguiente ejemplo muestra la implementación de la interfaz anterior:

Java

package x.y.service;
public class DefaultFooService implements FooService {
    @Override
    public Flux<Foo> getFoo(String fooName) {
        // ...
    }
    @Override
    public Publisher<Foo> getFoo(String fooName, String barName) {
        // ...
    }
    @Override
    public Mono<Void> insertFoo(Foo foo) {
        // ...
    }
    @Override
    public Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}
Kotlin

package x.y.service
class DefaultFooService : FooService {
    override fun getFoo(fooName: String): Flow<Foo> {
        // ...
    }
    override fun getFoo(fooName: String, barName: String): Publisher<Foo> {
        // ...
    }
    override fun insertFoo(foo: Foo): Mono<Void> {
        // ...
    }
    override fun updateFoo(foo: Foo): Mono<Void> {
        // ...
    }
}

La gestión de transacciones imperativa y reactiva tienen la misma semántica para definir los límites y los atributos de las transacciones. La principal diferencia entre transacciones imperativas y reactivas es el carácter diferido de estas últimas. Para comenzar, el TransactionInterceptor decora el tipo de retorno del tipo reactivo con un operador transaccional y borra la transacción. Por lo tanto, llamar a un método reactivo transaccional transfiere el control de la transacción real al tipo de suscripción, lo que permite el procesamiento del tipo reactivo.

Otro aspecto del control de transacciones reactivo implica el escape de datos, que es una consecuencia natural del modelo de programación.

Devoluciones Los valores de los métodos de transacción imperativos se devuelven desde los métodos transaccionales cuando el método se completa exitosamente, de modo que los resultados parcialmente calculados no salen del cierre del método.

Devuelven los métodos de transacción reactiva un tipo de función contenedora reactiva, que es una secuencia de evaluaciones junto con una promesa de comenzar y completar los cálculos.

Publisher puede producir datos mientras la transacción está en curso, pero no necesariamente completada. Por lo tanto, los métodos que dependen de la finalización exitosa de toda la transacción deben garantizar que el código de llamada complete y almacene los resultados en el buffer.

Revertir una transacción declarativa

B La sección anterior estableció los conceptos básicos de cómo establecer de forma declarativa parámetros de transacción para clases (generalmente clases de capa de servicio) en su aplicación. Esta sección describe cómo administrar la reversión de transacciones de una manera simple y declarativa en una configuración XML. Para obtener más información sobre la gestión declarativa de la semántica de reversión utilizando la anotación @Transactional, consulte @Transactional parámetros de anotación.

La forma recomendada de indicar al marco transaccional Spring Framework que revierta una transacción es lanzar una Exception del código, que se está ejecutando actualmente en el contexto de una transacción. El código del marco transaccional de Spring Framework intercepta cualquier Exception no controlada a medida que aparece en la pila de llamadas y determina si la transacción debe marcarse para reversión.

En la configuración predeterminada, Spring Framework código de marco transaccional Marca una transacción para reversión solo en el caso de excepciones de tiempo de ejecución no verificadas. Es decir, si la excepción lanzada es una instancia o subclase de RuntimeException. (Los casos de Error también provocan una reversión de forma predeterminada). Las excepciones marcadas que se generan desde un método transaccional no dan como resultado una reversión en la configuración predeterminada.

Puede configurar qué tipos de Exception marcan una transacción para reversión, incluidas las excepciones marcadas especificando reglas de reversión.

Reglas de reversión

Las reglas de reversión determinan si una transacción debe revertirse cuando ocurre una excepción específica, y estas reglas se basan en patrones. El patrón puede ser un nombre de clase completo o una subcadena del nombre de clase completo para el tipo de excepción (que debe ser una subclase de Throwable), sin soporte de comodines disponible actualmente. Por ejemplo, el valor "javax.servlet.ServletException" o "ServletException" coincidirá con javax.servlet.ServletException y sus subclases.

Las reglas de reversión se pueden configurar en XML usando los atributos rollback-for y no-rollback-for, que permiten especificar patrones como cadenas. Cuando se utiliza la anotación @Transactional revertir Las reglas se pueden configurar utilizando los atributos rollbackFor/noRollbackFor y rollbackForClassName/noRollbackForClassName, que permiten especificar patrones como referencias o cadenas de Class, respectivamente. Si el tipo de excepción se especifica como referencia de clase, su nombre completo se utilizará como plantilla. Por lo tanto, la anotación @Transactional(rollbackFor = example.CustomException.class) es equivalente a la anotación @Transactional(rollbackForClassName = "example.CustomException").

Deberá considerar cuidadosamente qué tan específica es la plantilla y si necesita incluir información del paquete (esto es opcional). Por ejemplo, "Exception" coincidirá con casi cualquier cosa y probablemente ocultará otras reglas. "java.lang.Exception" sería apropiado si "Exception" definiera una regla para que se verificaran todas las excepciones. Al utilizar nombres de excepción más exclusivos, como "BaseBusinessException", probablemente no necesitará utilizar el nombre de clase completo para el patrón de excepción.

Además, las reglas de reversión pueden causar coincidencias no deseadas para excepciones del mismo nombre y clases anidadas. Esto se debe a que se considera que una excepción lanzada coincide con una regla de reversión determinada si el nombre de la excepción lanzada contiene la plantilla de excepción configurada para esa regla de reversión. Por ejemplo, si una regla está configurada para coincidir con com.example.CustomException, coincidirá con una excepción denominada com.example.CustomExceptionV2 (una excepción del mismo paquete que CustomException, pero con un sufijo adicional) o una excepción denominada com.example.CustomException$AnotherException (una excepción declarada como una clase anidada de CustomException) .

El siguiente fragmento XML demuestra cómo configurar un respaldo para una aplicación marcada y específica. Exception al proporcionar un patrón de excepción a través del atributo rollback-for:


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

Si no desea que la transacción se revierta cuando se produce una excepción, también puede configurar "sin reversión". "normas. El siguiente ejemplo indica al marco de transacciones Spring Framework que confirme la transacción correspondiente incluso en el caso de una excepción InstrumentNotFoundException no controlada:


<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

Si el marco de transacciones de Spring Framework detecta la excepción y busca las reglas de reversión configuradas para determinar si se debe marcar la transacción para la reversión, tiene prioridad y se convierte en la regla más estricta. Por lo tanto, en el caso de la siguiente configuración, cualquier excepción excepto InstrumentNotFoundException conduce a la reversión de la transacción correspondiente:


<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
    </tx:attributes>
</tx:advice>

También puede especificar una reversión obligatoria mediante programación. Aunque simple, este proceso es bastante invasivo y acopla estrechamente su código a la infraestructura de transacciones de Spring Framework. El siguiente ejemplo muestra cómo especificar mediante programación una reversión obligatoria:

Java

public void resolvePosition() {
    try {
        // algo de lógica empresarial...
    } catch ( NoProductInStockException ex) {
        // inicia la reversión mediante programación
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}
Kotlin

fun resolvePosition() {
    try {
        // algo de lógica empresarial...
    } catch (ex: NoProductInStockException) {
        // inicia la reversión mediante programación
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

Se recomienda encarecidamente utilizar un enfoque declarativo para revertir si es posible. La reversión del software está disponible si es absolutamente necesario, pero su uso va en contra de los principios de una arquitectura puramente basada en POJO.

Configuración de diferentes semánticas de transacciones para diferentes beans

Considere un escenario en el que Hay una capa de servicio de múltiples objetos y es necesario aplicar configuraciones transaccionales completamente diferentes a cada uno de ellos. Esto se puede hacer definiendo elementos <aop:advisor/> separados con diferentes valores para los atributos pointcut y advice-ref.

A modo de comparación, primero suponga que todas las clases de la capa de servicio están definidas en el paquete raíz x.y.service. Para garantizar que todos los beans que son instancias de clases definidas en este paquete (o en subpaquetes) y cuyos nombres terminan en Service tengan una configuración transaccional predeterminada, puede escribir lo siguiente:


<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <aop:config>
        <aop:pointcut id="serviceOperation"
                expression="execution(* x.y.service..*Service.*(..))"/>
        <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
    </aop:config>
    <!-- estos dos beans serán transaccionales... -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>
    <!-- ... y estos dos contenedores no... -->
    <bean id="anotherService" class="org.xyz.SomeService"/>
    <!-- (no en el paquete correcto) -->
    <bean id="barManager" class="x.y.service.SimpleBarManager"/>
    <!-- (no termina en 'Servicio') -->
    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <!-- se omiten otros beans de infraestructura de transacciones, como TransactionManager... -->
</beans>

El siguiente ejemplo muestra cómo configurar dos beans diferentes con parámetros de transacción completamente diferentes:


<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <aop:config>
        <aop:pointcut id="defaultServiceOperation"
                expression="execution(* x.y.service.*Service.*(..))"/>
        <aop:pointcut id="noTxServiceOperation"
                expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
        <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
        <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
    </aop:config>
    <!-- este bean será transaccional (consulte el segmento "defaultServiceOperation") -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <!-- este bean también será transaccional, pero con parámetros transaccionales completamente diferentes -->
    <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>
    <tx:advice id="defaultTxAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <tx:advice id="noTxAdvice">
        <tx:attributes>
            <tx:method name="*" propagation="NEVER"/>
            </tx:attributes>
        </tx:advice>
<!-- se omiten otros beans de infraestructura de transacciones, como TransactionManager... -->
</beans>

Configuración <tx:advice/>

Esta sección describe brevemente los diversos parámetros transaccionales que se pueden configurar usando la etiqueta <tx:advice/>. Los parámetros <tx:advice/> predeterminados son los siguientes:

  • El parámetro de propagación es REQUIRED.

  • El nivel de aislamiento es DEFAULT .

  • La transacción se realiza en base a lectura y escritura.

  • El tiempo de espera predeterminado de la transacción es igual al tiempo de espera predeterminado del sistema de transacciones base o falta si los valores de tiempo de espera no son compatibles.

  • Cualquier RuntimeException provoca una reversión, y cualquier marcado Exception - no.

Puedes cambiar esta configuración predeterminada. La siguiente tabla muestra los diversos atributos de las etiquetas <tx:method/> que están anidadas dentro de <tx:advice/> y etiquetas <tx:atributos/>:

Tabla 1. Parámetros <tx:method/>
Attribute ¿Required? Default Description

name

Nombres de los métodos a los que se deben asociar los atributos de transacción. El carácter comodín (*) se puede utilizar para asociar los mismos parámetros de atributo de transacción con múltiples métodos (por ejemplo, get*, handle*, on* Event y así sucesivamente).

propagation

No

REQUIRED

Lógica de operación al propagar transacciones.

isolation

No

DEFAULT

Nivel de aislamiento de transacción. Se aplica solo a la configuración de distribución REQUIRED o REQUIRES_NEW.

timeout

No

-1

Tiempo de espera de transacción (segundos). Se aplica solo a las distribuciones REQUIRED o REQUIRES_NEW.

read-only

No

falso

Transacción para transacción de lectura-escritura versus transacción de solo lectura. Se aplica solo a REQUIRED o REQUIRES_NEW.

rollback-for

None

Lista de instancias de Exception que provocar una reversión, separados por comas. Por ejemplo, com.foo.MyBusinessException,ServletException.

no-rollback-for

No

Lista de instancias de Exception que no no tirar retroceso, separados por comas. Por ejemplo, com.foo.MyBusinessException,ServletException.

Uso de anotaciones @Transactional

Además del enfoque declarativo para la configuración de transacciones basada en XML, puede utilizar un enfoque basado en anotaciones. Declarar la semántica de transacciones directamente en el código fuente de Java hace que las declaraciones se acerquen mucho más al código afectado. Hay poco peligro de sobreacoplamiento porque el código destinado a uso transaccional casi siempre se implementa de esa manera.

Anotación estándar javax.transaction.Transactional también se admite como reemplazo de la anotación nativa de Spring. Puede encontrar más información en la documentación de JTA 1.2.

La facilidad de uso de la anotación @Transactional se ilustra mejor con un ejemplo, que se explica en el texto siguiente. Considere la siguiente definición de clase:

Java

// la clase de servicio que queremos hacer transaccional
@Transactional
public class DefaultFooService implements FooService {
    @Override
    public Foo getFoo(String fooName) {
        // ...
    }
    @Override
    public Foo getFoo(String fooName, String barName) {
        // ...
    }
    @Override
    public void insertFoo(Foo foo) {
        // ...
    }
    @Override
    public void updateFoo(Foo foo) {
        // ...
    }
}
Kotlin

// clase de servicio que queremos crear transaccional
@Transactional
class DefaultFooService : FooService {
    override fun getFoo(fooName: String): Foo {
        // ...
    }
    override fun getFoo(fooName: String, barName: String): Foo {
        // ...
    }
    override fun insertFoo(foo: Foo) {
        // ...
    }
    override fun updateFoo(foo: Foo) {
        // ...
    }
}

Usado en la clase nivel la anotación, como se describió anteriormente, especifica el valor predeterminado para todos los métodos de la clase declarante (así como sus subclases). Además, cada método se puede anotar por separado. Consulte "Visibilidad del método y @Transactional" para obtener más información sobre qué métodos Spring considera transaccionales. Tenga en cuenta que la anotación a nivel de clase no se extiende a las clases antecesoras superiores en la jerarquía de clases; en este caso, los métodos heredados deben redeclararse localmente para participar en la anotación a nivel de subclase.

Si una clase POJO como la anterior se define como un bean en el marco Spring, puede hacer que el bean instancia transaccional usando annotation @EnableTransactionManagement en una clase anotada con @Configuration. Para obtener más información, consulte javadoc.

En una configuración XML, la etiqueta <tx:annotation-driven/> proporciona un mecanismo similar:


<!-- del archivo "context.xml' - ->
<?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
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/tx
            https://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd ">
        <!-- este es el objeto de servicio que queremos convertir en transaccional -->
        <bean id="fooService" class="x.y.service.DefaultFooService"/>
        <!-- activar la configuración de la lógica de transacciones basada en anotaciones -->
        <!-- Aún se necesita TransactionManager -->
        <tx:annotation-driven transaction-manager="txManager"/> 
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- (this dependency is defined elsewhere) -->
            <property name="dataSource" ref="dataSource"/>
        </bean>
            <!-- otras definiciones <bean/--> -->
</beans>
  1. Una cadena que hace que la instancia del bean sea transaccional.
Puede omitir el atributo transaction-manager en Etiqueta <tx:annotation-driven/> si el bean TransactionManager al que desea conectarse se llama transactionManager. Si el bean TransactionManager que necesita inyectar tiene cualquier otro nombre, debe usar el atributo transaction-manager como en el ejemplo anterior.

Transaccional reactivo Los métodos utilizan tipos de retorno reactivos en lugar de mecanismos de programación imperativos, como se muestra en la siguiente lista:

Java

// la clase de servicio reactivo que queremos convertir en transaccional
@Transactional
public class DefaultFooService implements FooService {
    @Override
    public Publisher<Foo> getFoo(String fooName) {
        // ...
    }
    @Override
    public Mono<Foo> getFoo(String fooName, String barName) {
        // ...
    }
    @Override
    public Mono<Void> insertFoo(Foo foo) {
        // ...
    }
    @Override
    public Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}
Kotlin

// clase de servicio reactivo, que queremos que sea transaccional
class DefaultFooService : FooService {
    override fun getFoo(fooName: String): Flow<Foo> {
        // ...
    }
    override fun getFoo(fooName: String, barName: String): Mono<Foo> {
        // ...
    }
    override fun insertFoo(foo: Foo): Mono<Void> {
        // ...
    }
    override fun updateFoo(foo: Foo): Mono<Void> {
        // ...
    }
}

Tenga en cuenta que el Publisher devuelto tiene consideraciones especiales con respecto a las señales de cancelación de Reactive Streams. Para obtener más información, consulte "Cancelar señales" en "Uso del TransactionalOperator" sección".

Visibilidad del método y anotación @Transactional

Si está utilizando proxies transaccionales con la configuración estándar de Spring, entonces la anotación @Transactional solo debe aplicarse a los métodos. con visibilidad public. Si marca métodos protegido, privados o métodos con alcance de paquete con la anotación @Transactional, no se producirá ningún error, pero el método anotado sí. no devolverá los parámetros de transacción configurados. Si necesita anotar métodos no públicos, consulte el consejo en el siguiente párrafo que describe los servidores proxy basados en clases, o utilice el tiempo de carga basado en AspectJ o el enlace en tiempo de compilación (que se describe más adelante).

Cuándo Al usar la anotación @EnableTransactionManagement en una clase marcada con la anotación @Configuration, los métodos protected o los métodos con alcance de paquete también se pueden convertir en transaccionales para un proxy basado en clases registrando un bean personalizado transactionAttributeSource, como se muestra en el siguiente ejemplo. Sin embargo, tenga en cuenta que los métodos transaccionales en un proxy basado en interfaz siempre deben ser public y estar definidos en la interfaz proxy.


/**
    * Registre un AnnotationTransactionAttributeSource personalizado usando el indicador
    * publicMethodsOnly establecido en false para habilitar la compatibilidad con
    * métodos y métodos protegidos con alcance de paquete,
    * marcado con la anotación @Transactional, en
    * proxies basados en clases.
    *
    * @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
*/ @Bean
TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource(false);
}

Spring TestContext Framework de forma predeterminada admite métodos de prueba no privados anotados con @Transactional. Para ver ejemplos, consulte "Gestión de transacciones" en el capítulo de pruebas.

La anotación @Transactional se puede aplicar a una definición de interfaz, un método de interfaz, una definición de clase o un método de clase. Sin embargo, la mera presencia de la anotación @Transactional no es suficiente para activar la lógica transaccional del trabajo. La anotación @Transactional son simplemente metadatos que pueden ser consumidos por alguna infraestructura de tiempo de ejecución que soporte la anotación @Transactional y puede usar los metadatos para configurar beans apropiados con lógica operativa transaccional. En el ejemplo anterior, el elemento <tx:annotation-driven/> alterna la lógica transaccional.

El elemento Spring El equipo recomienda la anotación La anotación @Transactional solo se aplica a clases concretas (y métodos de clases concretas) y no anota interfaces. Por supuesto, puede anotar una interfaz (o un método de interfaz) con la anotación @Transactional, pero eso solo funcionará de la forma en que normalmente funcionaría si estuviera usando un proxy basado en interfaz. El hecho de que las anotaciones de Java no hereden de las interfaces significa que si utiliza un proxy basado en clases (proxy-target-class="true") o un aspecto basado en enlaces (mode="aspectj"), los parámetros de transacción no serán reconocidos por la infraestructura de enlace y proxy, y el objeto no se incluirá en un proxy transaccional.
En el modo proxy (que es el predeterminado), solo se interceptan las llamadas a métodos externos que llegan a través del proxy. Esto significa que una autollamada (esencialmente un método dentro de un objeto de destino que llama a otro método en el objeto de destino) no da como resultado una transacción real en tiempo de ejecución, incluso si el método que se llama está marcado con @Transactional anotación. Además, el proxy debe estar completamente inicializado para realizar la lógica esperada, por lo que no debe confiar en esta función en el código de inicialización, por ejemplo, en un método anotado con @PostConstruct.

Considere la posibilidad de utilizar el modo AspectJ (consulte el atributo mode en la siguiente tabla) si se espera que las autollamadas también estén incluidas en las transacciones. En este caso, el proxy generalmente no ocupará el primer lugar. En su lugar, se modifica la clase de destino (es decir, se modifica su código de bytes) para comenzar a admitir la lógica de tiempo de ejecución de la anotación @Transactional para un método de cualquier tipo.

Tabla 2. Configuración de transacciones basadas en anotaciones
XML Attribute Annotation Attribute Default Description

transaction-manager

N/A (consulte javadoc en TransactionManagementConfigurer )

transactionManager

El nombre del administrador de transacciones que se utilizará. Obligatorio solo si el nombre del administrador de transacciones no es transactionManager, como en el ejemplo anterior.

mode

mode

proxy

El modo predeterminado (proxy) maneja beans anotados para el proxy utilizando el marco Spring AOP (siguiendo la semántica del proxy (descrita anteriormente) que se aplica solo a las llamadas a métodos viniendo a través de un proxy). En cambio, el modo alternativo (aspectj) vincula las clases afectadas a un aspecto de transacción basado en AspectJ de Spring, modificando el código de bytes de la clase de destino para aplicarlo a una llamada a un método de cualquier tipo. Los enlaces basados en AspectJ requieren que spring-aspects.jar esté presente en el classpath y que los enlaces estén habilitados en el momento de la carga (o enlaces en el tiempo de compilación). (Para obtener más información sobre cómo configurar el enlace en el momento del arranque, consulte "Spring Configuration").

proxy-target-class

proxyTargetClass

false

Se aplica solo en modo proxy. Controla qué tipo de proxies transaccionales se crean para las clases anotadas con @Transactional. Si el atributo proxy-target-class se establece en true, se crean servidores proxy basados en clases. Si proxy-target-class es false o se omite el atributo, entonces se crean servidores proxy estándar basados en la interfaz JDK. (Para obtener una descripción detallada de los diferentes tipos de proxies, consulte "Mecanismos de proxy").

order

order

Ordered.LOWEST_PRECEDENCE

Define el orden de los consejos transaccionales que se aplican a los beans marcados con el anotación @Transactional. (Para obtener más información sobre las reglas asociadas con el pedido de consejos de AOP, consulte "Pedido de consejos"). La ausencia de un orden específico significa que el subsistema AOP determinará el orden del Consejo.

Mode Advice proporciona el valor predeterminado para procesar anotaciones @Transactional: proxy, que le permite interceptar llamadas solo a través de un proxy. Las llamadas locales dentro de la misma clase no se pueden interceptar de la misma manera. Para un modo de interceptación más avanzado, considere cambiar al modo aspectj en combinación con el enlace en tiempo de compilación o en tiempo de carga.
El atributo proxy-target-class controla qué tipo de proxies transaccionales se crean para las clases anotadas con @Transactional. Si proxy-target-class es true, se crean proxies basados en clases. Si proxy-target-class es false o se omite el atributo, se crean servidores proxy estándar basados en la interfaz JDK. (Para obtener una descripción de los diferentes tipos de servidores proxy, consulte "Mecanismos de proxy").
@EnableTransactionManagement y <tx:annotation-driven/> busque @Transactional solo para beans en el mismo contexto de aplicación en el que están definidos. Esto significa que si coloca una configuración basada en anotaciones en WebApplicationContext para DispatcherServlet, solo buscará beans con la anotación @Transactional. en sus controladores, pero no en sus servicios. Para obtener más información, consulte la sección sobre MVC.

Al calcular los parámetros transaccionales de un método, la ubicación más derivada tiene prioridad. En el siguiente ejemplo, la clase DefaultFooService está anotada en el nivel de clase con configuraciones de transacción de solo lectura, pero la anotación @Transactional en el método updateFoo(Foo) está en la misma clase y tiene prioridad sobre la configuración de transacción definida en el nivel de clase.

Java

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
    public Foo getFoo( String fooName) {
        // ...
    }
    // these settings take precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // ...
    }
}
Kotlin

@Transactional(readOnly = true)
class DefaultFooService : FooService {
    override fun getFoo(fooName: String): Foo {
        // ...
    }
    // these settings take precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    override fun updateFoo(foo: Foo) {
        // ...
    }
}

Parámetros de la anotación @Transactional

@Transactional son metadatos que indican que la interfaz, clase o método debe tener semántica transaccional (por ejemplo, "cuando se llama a este método, comienza una transacción completamente nueva en modo de solo lectura, suspendiendo todas las transacciones existentes"). Los parámetros @Transactional predeterminados son:

  • El parámetro de propagación es PROPAGATION_REQUIRED.

  • El nivel de aislamiento es ISOLATION_DEFAULT.

  • La transacción se realiza en modo lectura-escritura.

  • El tiempo de espera predeterminado de la transacción es igual al tiempo de espera predeterminado del sistema de transacciones subyacente, o no si los valores de tiempo de espera no son compatibles.

  • Cualquier RuntimeException o Error provocan una reversión, pero cualquier Exception marcada no lo hace.

Puedes cambiar estas configuraciones predeterminadas. La siguiente tabla muestra las diversas propiedades de la anotación @Transactional:

Parámetros de la anotación @Transactional
Propiedad Tipo Descripción

value

String

Un calificador opcional que especifica el administrador de transacciones que se utilizará.

transactionManager

String

Un alias para value.

label

Una matriz de etiquetas String para agregar una descripción significativa a una transacción.

Los administradores de transacciones pueden calcular las etiquetas para asociar una transacción específica que implemente la lógica para trabajar con la transacción real.

propagación

enum: Propagation

Parámetro de distribución adicional.

isolation

enum: Isolation

Aislamiento adicional nivel. Se aplica solo a los valores de propagación REQUIRED o REQUIRES_NEW.

timeout

int (en segundos de granularidad)

Tiempo de espera de transacción opcional. Se aplica solo a los valores de propagación REQUIRED o REQUIRES_NEW.

timeoutString

String (en segundos de granularidad)

Alternativa para configurar tiempo de espera en segundos como valor String, por ejemplo, como marcador de posición.

readOnly

boolean

Transacción en modo lectura-escritura versus transacción en modo de solo lectura. Se aplica solo a los valores REQUIRED o REQUIRES_NEW.

rollbackFor

Una matriz de objetos Class que deben derivarse de Throwable.

Una matriz opcional de tipos de excepción que deberían causar una reversión.

rollbackForClassName

Una matriz de patrones de nombres de excepción.

Una matriz opcional de patrones de nombres de excepción que debería desencadenar una reversión.

noRollbackFor

Matriz de objetos Class que deben derivarse de Throwable.

Una matriz opcional de tipos de excepción que no deberían provocar una reversión.

noRollbackForClassName

Una matriz de patrones de nombres de excepción.

Una matriz opcional de patrones de nombres de excepción que no lo son debería provocar una reversión.

Obtenga más información sobre la semántica de las reglas, patrones y advertencias de reversión. Para posibles coincidencias no deseadas, consulte "Reglas de reversión".

Actualmente, no puede controlar explícitamente el nombre de la transacción, donde "nombre" se refiere al nombre de la transacción que aparece en el monitor de transacciones, si corresponde (por ejemplo, WebLogic Transaction Monitor), y en la salida registrada. Para transacciones declarativas, el nombre de la transacción es siempre el nombre de clase completo + . + nombre del método de la clase Advice equipada transaccionalmente. Por ejemplo, si el método handlePayment(..) de la clase BusinessService inicia una transacción, el nombre de la transacción sería: com.example.BusinessService.handlePayment.

Usar múltiples administradores de transacciones usando @Transactional

La mayoría de las aplicaciones Spring solo necesitan un administrador de transacciones, pero puede haber situaciones en las que necesite Múltiples administradores de transacciones independientes en la misma aplicación. Puede utilizar value o el atributo transactionManager de la anotación @Transactional para especificar opcionalmente el identificador del TransactionManager para usar. Puede ser el nombre del bean o el valor calificador del bean del administrador de transacciones. Por ejemplo, utilizando la notación de calificador, puede combinar el siguiente código Java con las siguientes declaraciones de beans de administrador de transacciones en el contexto de la aplicación:

Java

public class TransactionalService {
    @Transactional("order")
    public void setSomething(String name) { ... }
    @Transactional("account")
    public void doSomething() { ... }
    @Transactional("reactive-account")
    public Mono<Void> doSomethingReactive() { ... }
}
Kotlin

class TransactionalService {
    @Transactional("order")
    fun setSomething(name: String) {
        // ...
    }
    @Transactional("account")
    fun doSomething() {
        // ...
    }
    @Transactional("reactive-account")
    fun doSomethingReactive(): Mono<Void> {
        // ...
    }
}

La siguiente lista muestra las declaraciones de beans:


<tx:annotation-driven/>
    <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="order"/>
    </bean>
    <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="account"/>
    </bean>
    <bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
        ...
        <qualifier value="reactive-account"/>
    </bean>

En este caso, métodos separados para TransactionalService operan bajo el control de administradores de transacciones separados, que se diferencian en calificadores order, account y reactive-account. El nombre del bean de destino predeterminado <tx:annotation-driven>, transactionManager, todavía se utiliza si no se puede encontrar un bean TransactionManager especialmente calificado.

Anotaciones compuestas personalizadas

Si descubre que los mismos atributos con la anotación @Transactional se utilizan repetidamente en muchos métodos diferentes, El soporte de Spring para metaanotaciones le permitirá definir anotaciones compuestas personalizadas para casos de uso específicos. Como ejemplo, considere la siguiente definición de anotación:

Java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}
Kotlin

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "order", label = ["causal-consistency"])
annotation class OrderTx
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "account", label = ["retryable"])
annotation class AccountTx

Las anotaciones anteriores nos permiten escribir el ejemplo de la sección anterior de la siguiente manera:

Java

public class TransactionalService {
    @OrderTx
    public void setSomething(String name) {
        // ...
    }
    @AccountTx
    public void doSomething() {
        // ...
    }
}
Kotlin

class TransactionalService {
    @OrderTx
    fun setSomething(name: String) {
        // ...
    }
    @AccountTx
    fun doSomething() {
        // ...
    }
}

En el ejemplo anterior, usamos la sintaxis para definir el calificador del administrador de transacciones y las etiquetas transaccionales, pero también podemos agregar lógica de propagación, reglas de reversión, tiempos de espera y otras funciones.

Propagación de transacciones

Esta sección describe la semántica específica de la propagación de transacciones en Spring. Tenga en cuenta que esta sección no es una introducción adecuada al tema de la propagación de transacciones. Más bien, detalla la semántica de la propagación de transacciones en Spring.

Para las transacciones administradas por Spring, tenga en cuenta la distinción entre transacciones físicas y lógicas, y cómo se aplica la opción de propagación para tener en cuenta esa distinción.

Comprensión de PROPAGATION_REQUIRED

PROPAGATION_REQUIRED garantiza que se ejecute una transacción física localmente para la disponibilidad del alcance actual si la transacción aún no existe, o al estar en una transacción "externa" existente definida para un área de disponibilidad más grande. Este es un valor predeterminado perfectamente aceptable en una organización típica de pila de llamadas de un solo subproceso (por ejemplo, una fachada de servicio que delega a múltiples métodos de repositorio, donde todos los recursos subyacentes deben estar presentes en una transacción de nivel de servicio).

De forma predeterminada, la transacción participante une las características del alcance de disponibilidad externo, ignorando silenciosamente el nivel de aislamiento local, el valor de tiempo de espera o el indicador de solo lectura (si corresponde). Considere cambiar el indicador validateExistingTransactions a true para su administrador de transacciones si desea que las declaraciones de nivel de aislamiento no estén disponibles al participar en una transacción existente con un nivel de aislamiento diferente. Este modo también rechaza discrepancias en el modo de solo lectura (es decir, si una transacción interna de lectura y escritura intenta ingresar a un alcance de disponibilidad de solo lectura externo).

Si la opción de propagación está configurada en PROPAGATION_REQUIRED , se crea un área de transacción lógica para cada método al que se aplica este parámetro. Cada área de transacción lógica puede definir el estado de solo reversión individualmente, siendo el área de transacción externa lógicamente independiente del área de transacción interna. En el caso de la lógica operativa estándar PROPAGATION_REQUIRED, todas estas áreas de disponibilidad se asignan a la misma transacción física. Por lo tanto, un token de solo reversión establecido en el alcance de disponibilidad de la transacción interna afecta la capacidad de confirmación de la transacción externa.

Sin embargo, en el caso en que el alcance de disponibilidad de la transacción interna establece el marcador de solo reversión, En la transacción externa, la transacción no toma la decisión de revertir por sí sola, por lo que la reversión (causada silenciosamente por el alcance de las transacciones internas) es inesperada. En este punto, se lanza la UnexpectedRollbackException correspondiente. Esta es la lógica de operación esperada, por lo que el código que llama a la transacción no puede comportarse mal pensando que se ha realizado una confirmación cuando en realidad no es así. Por lo tanto, si una transacción interna (que el código de llamada externo desconoce) marca silenciosamente la transacción como revertida, el código de llamada externo seguirá causando la confirmación. La persona que llama externa debe recibir una UnexpectedRollbackException para indicar claramente que en su lugar se realizó una reversión.

Comprensión de PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRES_NEW, a diferencia de PROPAGATION_REQUIRED, siempre utiliza una transacción física independiente para cada región de disponibilidad de transacciones afectada, y nunca participa en una transacción existente destinada a el alcance de disponibilidad exterior. En este diseño, las transacciones subyacentes al recurso son distintas y, por lo tanto, pueden confirmarse o revertirse independientemente unas de otras, sin que la transacción externa se vea afectada por el estado de reversión de la transacción interna y los bloqueos de la transacción interna se liberen inmediatamente después de su finalización. Esta transacción interna independiente también puede declarar su propio nivel de aislamiento, tiempo de espera y parámetros de solo lectura y no heredar las características de la transacción externa.

Comprensión de PROPAGATION_NESTED

PROPAGATION_NESTED utiliza una única transacción física con múltiples puntos de guardado a los que se puede revertir. Estas reversiones parciales permiten que la región de disponibilidad de la transacción interna inicie una reversión para su región de disponibilidad, mientras que la transacción externa puede continuar la transacción física incluso aunque algunas operaciones se hayan revertido. Esta opción normalmente se asigna a puntos de rescate JDBC, por lo que solo funciona con transacciones de recursos JDBC. Consulte el DataSourceTransactionManager de Spring.

Proporcionar asesoramiento sobre operaciones transaccionales

Supongamos que necesita realizar operaciones transaccionales y algunos consejos básicos sobre creación de perfiles. ¿Cómo se puede hacer esto en el contexto de <tx:annotation-driven/>?

Si se llama al método updateFoo(Foo) , entonces puede esperar las siguientes acciones:

  • Se inicia el aspecto de creación de perfiles configurado.

  • El aviso transaccional comienza a ejecutarse.

  • El método para el objeto equipado con asesoramiento comienza a ejecutarse.

  • La transacción se confirma.

  • El aspecto de creación de perfiles informa la duración exacta de toda la llamada a un método transaccional.

No abordamos particularmente el AOP en este capítulo (excepto su aplicación a las transacciones). Consulte la sección sobre AOP para obtener una cobertura detallada de la configuración de AOP y AOP en general.

El siguiente código muestra el aspecto de creación de perfiles simple analizado anteriormente:

Java

package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;
public class SimpleProfiler implements Ordered {
    private int order;
    // nos permite controlar el orden de los consejos
    public int getOrder() {
        return this.order;
    }
    public void setOrder(int order) {
        this.order = order;
    }
    // este método es un consejo "en lugar de"
    public Object profile(ProceedingJoinPoint call) throws Throwable {
        Object returnValue;
        StopWatch clock = new StopWatch(getClass().getName());
        try {
            clock.start(call.toShortString());
            returnValue = call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
        return returnValue;
    }
}
Kotlin

class SimpleProfiler : Ordered {
    private var order: Int = 0
    // nos permite controlar el orden de los consejos
    override fun getOrder(): Int {
        return this.order
    }
    fun setOrder(order: Int) {
        this.order = order
    }
    // este método es Consejo "en lugar de"
    fun profile(call: ProceedingJoinPoint): Any {
        var returnValue: Any
        val clock = StopWatch(javaClass.name)
        try {
            clock.start(call.toShortString())
            returnValue = call.proceed()
        } finally {
            clock.stop()
            println(clock.prettyPrint())
        } return returnValue
    }
}

El pedido de consejos se controla mediante la interfaz Ordered. Para obtener más información sobre cómo organizar los consejos, consulte "Ordering Advice".

A continuación, La configuración crea un bean fooService, al que se aplican aspectos transaccionales y de perfiles en el orden requerido:


<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <!-- este es un aspecto de -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- se ejecuta antes del Aviso transaccional (por lo tanto, el número de secuencia es menor) -->
        <property name="order" value="1"/>
    </bean>
    <tx:annotation-driven transaction-manager="txManager" order="200"/>
    <aop:config>
            <!-- este Aviso se ejecuta en lugar del Aviso transaccional... -->
            <aop:aspect id="profilingAspect" ref="profiler">
                <aop:pointcut id="serviceMethodWithReturnValue"
                        expression="execution(!void x.y..*Service.*(..))"/>
                <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
            </aop:aspect>
    </aop:config>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

Cualquier cantidad de aspectos adicionales se pueden configurar de manera similar.

El siguiente ejemplo crea lo mismo conjunto de valores especificados, como en los dos ejemplos anteriores, pero utiliza un enfoque XML puramente declarativo:


<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <!-- Consejos sobre creación de perfiles -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- se ejecuta antes del Aviso transaccional (por lo tanto, el número de secuencia es menor) -->
        <property name="order" value="1"/>
    </bean>
    <aop:config>
        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
        <!-- runs after the profiling Advice (cf. queue attribute) -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
        <!-- the priority value is higher than that of the profiling aspect -->
        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue"
                    expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        </aop:aspect>
    </aop:config>
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <!-- other <bean/> definitions such as DataSource and TransactionManager -->
</beans>

El resultado de la configuración anterior es el bean fooService, al que se le asignan los aspectos transaccionales y de perfilado. aplicado en ese orden. Si desea que el Consejo de creación de perfiles se ejecute después del Consejo transaccional en la entrada y antes del Consejo transaccional en la salida, puede cambiar el valor de la propiedad order del bean de aspecto de creación de perfiles para que sea mayor que el valor del pedido del Aviso transaccional.

Se pueden configurar aspectos adicionales de manera similar.

Usando la anotación @Transactional usando AspectJ

También puede usar las herramientas de soporte de anotaciones @Transactional de Spring Framework fuera de un contenedor Spring usando un aspecto AspectJ. Para hacer esto, primero marque sus clases (y opcionalmente sus métodos de clase) con la anotación @Transactional y luego vincule (vincule) su aplicación a org.springframework.transaction.aspectj.AnnotationTransactionAspect definido en el archivo spring-aspects.jar. También necesitas configurar el aspecto usando el administrador de transacciones. Puede utilizar el contenedor Spring Framework IoC para inyectar dependencias en un aspecto. La forma más sencilla de configurar el aspecto de gestión de transacciones es utilizar el elemento <tx:annotation-driven/> y establecer el atributo mode en aspectj. como se describe Consulte "Uso de la anotación @Transactional". Dado que nos centramos en aplicaciones que se ejecutan fuera del contenedor Spring, le mostraremos cómo hacerlo mediante programación.

Antes de continuar, Es posible que desee consultar la anotación "Usando la @Transactional" y "AOP" respectivamente.

El siguiente ejemplo muestra cómo crear un administrador de transacciones y configurar AnnotationTransactionAspect para usar it:

Java

// construye el administrador de transacciones correspondiente
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());
// configura AnnotationTransactionAspect para usarlo; esto debe hacerse antes de ejecutar cualquier método transaccional
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
Kotlin

// construct the appropriate transaction manager
val txManager = DataSourceTransactionManager(getDataSource())
// configura AnnotationTransactionAspect para usarlo; esto debe hacerse antes de ejecutar cualquier método transaccional
AnnotationTransactionAspect.aspectOf().transactionManager = txManager
Cuando utilice este aspecto, debe anotar la clase de implementación (o los métodos dentro de esa clase, o ambos), en lugar de la interfaz (si la hay) que implementa la clase. AspectJ sigue la regla de Java de que las anotaciones para interfaces no se heredan.

La anotación @Transactional en una clase define la semántica de transacción predeterminada para ejecutar cualquier método público en la clase.

La anotación @Transactional en un método en una clase anula la semántica de transacción predeterminada especificada por la anotación de clase (si está presente). Puede anotar cualquier método, independientemente de su visibilidad.

Para vincular sus aplicaciones con AnnotationTransactionAspect, debe crear su aplicación utilizando AspectJ (consulte "AspectJ Development Guide"), o utilice el enlace de tiempo de carga. Consulte Consulte "Enlace en tiempo de carga con AspectJ en Spring Framework" para obtener una introducción al enlace en tiempo de carga con usando AspectoJ.