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

public class SimpleService implements Service {
    // 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) : Service {
    // un único TransactionTemplate, común a todos los métodos de esta instancia
    private val transactionTemplate = TransactionTemplate(transactionManager)
    fun someServiceMethod() = transactionTemplate.execute<Any?> {
        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() {
    override fun 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() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        try {
            updateOperation1();
            updateOperation2();
        } catch (SomeBusinessException ex) {
            status.setRollbackOnly();
        }
    }
});
Kotlin

transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
    override fun doInTransactionWithoutResult(status: TransactionStatus) {
        try {
            updateOperation1()
            updateOperation2()
        } catch (ex: 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

public class SimpleService implements Service {
    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

class SimpleService(transactionManager: PlatformTransactionManager) : Service {
    private val transactionTemplate = TransactionTemplate(transactionManager).apply {
        // si lo desea, puede establecer explícitamente los parámetros de transacción aquí
        isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED timeout = 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"/>
    <property name="timeout" value="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

public class SimpleService implements Service {
    // 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);
    }
    public Mono<Object> someServiceMethod() {
        // el código de este método se ejecuta en el contexto de una transacción
        Mono<Object> update = updateOperation1();
        return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
    }
}
Kotlin

// usa la inyección de dependencia a través del constructor para proporcionar la clase ReactiveTransactionManager
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {
    // 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(new TransactionCallback<>() {
    public Mono<Object> doInTransaction(ReactiveTransaction status) {
        return updateOperation1().then(updateOperation2)
                    .doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
        }
    }
});
Kotlin

transactionalOperator.execute(object : TransactionCallback() {
    override fun doInTransactionWithoutResult(status: ReactiveTransaction) {
        updateOperation1().then(updateOperation2)
                    .doOnError(SomeBusinessException.class, e -> status.setRollbackOnly())
    }
})

Cancelar señales

En Reactive Streams, un Suscriptor puede cancelar su Subscription y detener su Publisher. 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

public class SimpleService implements Service {
    private final TransactionalOperator transactionalOperator;
    public SimpleService(ReactiveTransactionManager transactionManager) {
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        // si lo desea, puede establecer explícitamente los parámetros de la transacción aquí
        definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
        definition.setTimeout(30); // 30 segundos
        // y así sucesivamente...
        this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
    }
}
Kotlin

class SimpleService(transactionManager: ReactiveTransactionManager) : Service {
    private val definition = DefaultTransactionDefinition().apply {
        // si lo desea, puede establecer explícitamente los parámetros de la transacción aquí
        isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED timeout = 30 // 30 segundos
        // y así sucesivamente...
    }
    private val transactionalOperator = TransactionalOperator(transactionManager, definition)
} 

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

val def = DefaultTransactionDefinition()
// la configuración explícita del nombre de la transacción sólo se puede realizar mediante programación
def.setName("SomeTxName")
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
val status = txManager.getTransaction(def)
try {
    // ponga su lógica de negocios aquí
} catch (ex: MyException) {
    txManager.rollback (status)
    throw 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 (ex: MyException) {
    txManager.rollback (status)
    throw ex
}
txManager.commit(status)

Usando ReactiveTransactionManager

Cuando se trabaja 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> reactiveTx = 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)) }