Spring Framework ofrece dos formas de administrar transacciones mediante programación: usando:

  • TransactionTemplate o TransactionalOperator.

  • La implementación de TransactionManager en sí.

El equipo de Spring generalmente recomienda TransactionTemplate para el software gestión de transacciones en flujos imperativos y TransactionalOperator para código reactivo. El segundo enfoque es similar al uso de la API UserTransaction de JTA, aunque el manejo de excepciones está menos sobrecargado.

Uso de TransactionTemplate

Template TransactionTemplate adopta el mismo enfoque que otras plantillas Spring, como JdbcTemplate. Utiliza un enfoque de devolución de llamada (para liberar al código de la aplicación de tener que realizar la estereotipada adquisición y liberación de recursos transaccionales) e implica la creación de código impulsado por la intención, es decir, el trabajo de su código se centra únicamente en lo que necesita lograr.

Como muestran los siguientes ejemplos, usar un TransactionTemplate es vinculándolo completamente a la infraestructura de transacciones y a la API de Spring. Si la gestión programática de transacciones es apropiada para sus necesidades de desarrollo es una cuestión que usted mismo debe decidir.

El código de aplicación que debe ejecutarse en un contexto transaccional y que utiliza explícitamente TransactionTemplate es similar al siguiente ejemplo. Usted, como desarrollador de aplicaciones, puede escribir una implementación TransactionCallback (normalmente expresada como una clase interna anónima) que contenga el código que se ejecutará en el contexto de una transacción. Luego puede pasar una instancia de su TransactionCallback personalizado al método execute(..) expuesto al TransactionTemplate. El siguiente ejemplo muestra cómo hacer esto:

Java
clase pública SimpleService implementa el Servicio { // una única TransactionTemplate, común a todos los métodos de esta instancia private final TransactionTemplate plantilla de transacción; // usa la inyección de dependencia a través del constructor para proporcionar un PlatformTransactionManager public SimpleService(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } public Object someServiceMethod() { return transactionTemplate.execute(new TransactionCallback() { // el código de este método se ejecuta en el contexto de una transacción public Object doInTransaction(TransactionStatus status) { updateOperation1(); return resultOfUpdateOperation2(); } }); } }
Kotlin
// usa la inyección de dependencia a través del constructor para proporcionar la clase PlatformTransactionManager SimpleService(transactionManager: PlatformTransactionManager) : Servicio { // un único TransactionTemplate, común a todos los métodos de esta instancia private val transactionTemplate = TransactionTemplate(transactionManager) fun someServiceMethod() = transactionTemplate.execute<¿Alguno?> { updateOperation1() resultOfUpdateOperation2() } }

Si no hay ningún valor de retorno, puede utilizar una clase auxiliar TransactionCallbackWithoutResult con una clase anónima como esta se muestra a continuación:

Java
transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { updateOperation1(); updateOperation2 (); } } );
Kotlin
transactionTemplate.execute(object: TransactionCallbackWithoutResult() { anular diversión doInTransactionWithoutResult(status: TransactionStatus ) { updateOperation1() updateOperation2() } })

El código dentro de la devolución de llamada puede revertir la transacción llamando al setRollbackOnly() método para el objeto TransactionStatus proporcionado, como se muestra a continuación:

Java
transactionTemplate.execute(new TransactionCallbackWithoutResult() { void protegido doInTransactionWithoutResult(estado de TransactionStatus) { intentar { updateOperation1(); actualizarOperación2(); } catch (SomeBusinessException ex) { status.setRollbackOnly(); } } });
Kotlin
transactionTemplate.execute(object: TransactionCallbackWithoutResult() { anular fun doInTransactionWithoutResult( estado: TransactionStatus) { intentar { updateOperation1() updateOperation2() } catch (ej: SomeBusinessException) { status.setRollbackOnly() } } })

Tarea parámetros de transacción

Los parámetros de transacción (como modo de propagación, nivel de aislamiento, tiempo de espera, etc.) se pueden establecer para TransactionTemplate mediante programación o en configuración. De forma predeterminada, las instancias TransactionTemplate tienen configuración de transacciones estándar. El siguiente ejemplo muestra la configuración programática de los parámetros de transacción para un TransactionTemplate específico:

Java
clase pública SimpleService implementa el Servicio { private final TransactionTemplate transactionTemplate ; public SimpleService(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); // si lo desea, puede establecer explícitamente los parámetros de la transacción aquí this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); this.transactionTemplate.setTimeout(30); // 30 segundos // y así sucesivamente... } }
Kotlin
clase SimpleService(transactionManager: PlatformTransactionManager): Servicio { valor privado transacciónTemplate = TransactionTemplate(transactionManager).apply { // si lo desea, puede establecer explícitamente los parámetros de transacción aquí. aislamientoLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED tiempo de espera = 30 // 30 segundos // y así sucesivamente... } } 

En el siguiente ejemplo, TransactionTemplate se define con algunas configuraciones de transacción personalizadas usando la configuración XML en Spring:

<bean id="sharedTransactionTemplate" class=" org.springframework.transaction.support.TransactionTemplate"> <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/> <nombre de propiedad="tiempo de espera" valor="30"/> </bean>

Luego puede inyectar sharedTransactionTemplate en tantos servicios como sea necesario.

Finalmente, las instancias de la clase TransactionTemplate son seguras para subprocesos porque las instancias no retienen ningún estado de diálogo. Sin embargo, las instancias TransactionTemplate conservan el estado de configuración. Por lo tanto, aunque varias clases pueden compartir una única instancia de TransactionTemplate, si desea que una clase use un TransactionTemplate con diferentes configuraciones (por ejemplo, diferentes niveles de aislamiento), debe crear dos instancias diferentes de TransactionTemplate.

El uso de TransactionalOperator

TransactionalOperator sigue la estructura de programación de el operador, que es similar a otros operadores reactivos. Utiliza un enfoque de devolución de llamada (para liberar al código de la aplicación de tener que realizar la estereotipada adquisición y liberación de recursos transaccionales) e implica la creación de código impulsado por la intención, es decir, el trabajo de su código se centra únicamente en lo que necesita lograr.

Como muestran los siguientes ejemplos, usar TransactionalOperator es completamente vinculándolo a la infraestructura de transacciones y a la API de Spring. Si la gestión programática de transacciones es adecuada para sus necesidades de desarrollo es una cuestión que usted mismo debe decidir.

Código de aplicación que debe ejecutarse en el contexto de una transacción que utiliza explícitamente el TransactionalOperator es similar al que se representa en el siguiente ejemplo:

Java
clase pública SimpleService implementa el Servicio { // un único TransactionalOperator, común a todos los métodos de esta instancia es privada TransactionalOperator transactionalOperator; // usa la inyección de dependencia del constructor para proporcionar un ReactiveTransactionManager public SimpleService(ReactiveTransactionManager transactionManager) { this.transactionalOperator = TransactionalOperator.create(transactionManager); } público Mono<Objeto> someServiceMethod() { // el código de este método se ejecuta en el contexto de una transacción Mono<Object> actualizar = actualizarOperación1(); return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional); } }
Kotlin
// usa la inyección de dependencia a través del constructor para proporcionar la clase ReactiveTransactionManager SimpleService(transactionManager: ReactiveTransactionManager) : Servicio { // un único TransactionalOperator, común a todos los métodos de esta instancia private val transactionalOperator = TransactionalOperator.create(transactionManager) suspend fun someServiceMethod() = transactionalOperator.executeAndAwait<Any?> { updateOperation1() resultOfUpdateOperation2() } }

TransactionalOperator se puede utilizar de dos maneras:

  • Basado en un operador que utiliza tipos de Project Reactor (mono.as(transactionalOperator::transactional))

  • Basado en un devolución de llamada para todos los demás casos (transactionalOperator.execute(TransactionCallback<T>))

El código dentro de la devolución de llamada puede revertir la transacción llamando el método setRollbackOnly() en el objeto ReactiveTransaction proporcionado como se muestra a continuación:

Java
transactionalOperator.execute (nueva TransactionCallback<> ;() { público Mono<Object> doInTransaction(estado de ReactiveTransaction) { return updateOperation1().then(updateOperation2) .doOnError(SomeBusinessException.class, e -> estado.setRollbackOnly()); } } });
Kotlin
transactionalOperator.execute(object: TransactionCallback() { anular fun doInTransactionWithoutResult( estado: ReactiveTransaction) { updateOperation1().then(updateOperation2) .doOnError(SomeBusinessException.class, e -> status.setRollbackOnly()) } })

Cancelar señales

En Reactive Streams, un Suscriptor puede cancelar su Suscripción y detener su Editor. Operadores en Project Reactor, así como en otras bibliotecas, como next(), take(long), timeout(Duration) y otras , Se podrán realizar cancelaciones. Es imposible averiguar el motivo de la cancelación, si fue un error o simplemente una falta de interés en seguir consumiendo. Desde la versión 5.3, las señales de cancelación provocan una reversión. Como resultado, es importante considerar los operadores utilizados en orden descendente desde las transacciones Publisher. Particularmente en el caso de Flux u otro Publisher de múltiples valores, se debe consumir la salida completa para que se complete la transacción.

Configuración de los parámetros de la transacción

Puede establecer parámetros de transacción (como modo de propagación, nivel de aislamiento, tiempo de espera, etc.) para TransactionalOperator. De forma predeterminada, las instancias TransactionalOperator tienen configuraciones transaccionales estándar. El siguiente ejemplo muestra cómo configurar parámetros transaccionales para un TransactionalOperator específico:

Java
clase pública SimpleService implementa el Servicio { private final TransactionalOperator operador transaccional; public SimpleService(ReactiveTransactionManager transactionManager) { Definición de DefaultTransactionDefinition = new DefaultTransactionDefinition(); // si lo desea, puede establecer explícitamente los parámetros de la transacción aquí definición.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); definición.setTimeout(30); // 30 segundos // y así sucesivamente... this.transactionalOperator = TransactionalOperator.create(transactionManager, definición); } }
Kotlin
clase SimpleService(transactionManager: ReactiveTransactionManager): Servicio {definición de valor privado = DefaultTransactionDefinition() .apply { // si lo desea, puede establecer explícitamente los parámetros de la transacción aquí InsulationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED timeout = 30 // 30 segundos // y así sucesivamente... } private val transactionalOperator = TransactionalOperator(transactionManager, definición) } 

Uso de TransactionManager

Las siguientes secciones cubren el uso programático de administradores de transacciones imperativos y reactivos.

Uso de PlatformTransactionManager

Para transacciones imperativas, puede utilizar org.springframework.transaction.PlatformTransactionManager para gestionar la transacción directamente . Para hacer esto, pase la implementación de PlatformTransactionManager que está utilizando a su bean a través de una referencia de bean. Luego, utilizando los objetos TransactionDefinition y TransactionStatus, las transacciones se pueden iniciar, revertir y confirmar. El siguiente ejemplo muestra cómo hacer esto:

Java
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // la configuración explícita del nombre de la transacción sólo se puede realizar mediante programación def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); Estado de transacción = txManager.getTransaction(def); try { // ponga su lógica de negocios aquí } catch (MyException ex) { txManager.rollback(status); tirar ex; } txManager.commit(status);
Kotlin
val def = DefaultTransactionDefinition() // estableciendo explícitamente el las transacciones de nombres solo se pueden realizar mediante programación def.setName("SomeTxName") def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED val status = txManager.getTransaction(def) try { // coloque su lógica de negocios aquí } catch (ej: MyException) { txManager. rollback (estado) throw ex } txManager.commit(status)

Usando ReactiveTransactionManager

Cuando se trabaja con Con transacciones reactivas, puede usar org.springframework.transaction.ReactiveTransactionManager para administrar la transacción directamente. Para hacer esto, pase la implementación del ReactiveTransactionManager que está usando a su bean a través de una referencia de bean. Luego, utilizando los objetos TransactionDefinition y ReactiveTransaction, puede iniciar, deshacer y confirmar transacciones. El siguiente ejemplo muestra cómo hacer esto:

Java
            DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // la configuración explícita
                del nombre de la transacción sólo se puede realizar mediante programación def.setName("SomeTxName");
                def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); Mono<ReactiveTransaction>
                reactivoTx = txManager.getReactiveTransaction(def); reactiveTx.flatMap(status -> { Mono<Object>
                tx = ...; // coloque su lógica de negocios aquí return tx.then(txManager.commit(status))
                .onErrorResume(ex -> txManager.rollback( status ).then(Mono.error(ex))); });
Kotlin
val def = DefaultTransactionDefinition() // La configuración explícita del nombre de la transacción solo se puede realizar mediante programación def.setName("SomeTxName") def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED val reactiveTx = txManager.getReactiveTransaction(def) reactiveTx.flatMap { status -> val tx = ... // pon tu lógica de negocios aquí tx.then(txManager.commit(status)) .onErrorResume { ex -> txManager.rollback(status).then(Mono.error(ex)) } }