Spring Framework ofrece dos formas de administrar transacciones mediante programación: usando:
TransactionTemplate
oTransactionalOperator
.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.
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:
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();
}
});
}
}
// 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:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});
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:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessException ex) {
status.setRollbackOnly();
}
}
});
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:
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...
}
}
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.
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:
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);
}
}
// 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:
transactionalOperator.execute(new TransactionCallback<>() {
public Mono<Object> doInTransaction(ReactiveTransaction status) {
return updateOperation1().then(updateOperation2)
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
}
}
});
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:
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);
}
}
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:
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)
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:
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)));
});
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)) }
GO TO FULL VERSION