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)) }