You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
16 KiB
496 lines
16 KiB
[[transaction-programmatic]] |
|
= Programmatic Transaction Management |
|
|
|
The Spring Framework provides two means of programmatic transaction management, by using: |
|
|
|
* The `TransactionTemplate` or `TransactionalOperator`. |
|
* A `TransactionManager` implementation directly. |
|
|
|
The Spring team generally recommends the `TransactionTemplate` for programmatic |
|
transaction management in imperative flows and `TransactionalOperator` for reactive code. |
|
The second approach is similar to using the JTA `UserTransaction` API, although exception |
|
handling is less cumbersome. |
|
|
|
|
|
[[tx-prog-template]] |
|
== Using the `TransactionTemplate` |
|
|
|
The `TransactionTemplate` adopts the same approach as other Spring templates, such as |
|
the `JdbcTemplate`. It uses a callback approach (to free application code from having to |
|
do the boilerplate acquisition and release transactional resources) and results in |
|
code that is intention driven, in that your code focuses solely on what |
|
you want to do. |
|
|
|
NOTE: As the examples that follow show, using the `TransactionTemplate` absolutely |
|
couples you to Spring's transaction infrastructure and APIs. Whether or not programmatic |
|
transaction management is suitable for your development needs is a decision that you |
|
have to make yourself. |
|
|
|
Application code that must run in a transactional context and that explicitly uses the |
|
`TransactionTemplate` resembles the next example. You, as an application |
|
developer, can write a `TransactionCallback` implementation (typically expressed as an |
|
anonymous inner class) that contains the code that you need to run in the context of |
|
a transaction. You can then pass an instance of your custom `TransactionCallback` to the |
|
`execute(..)` method exposed on the `TransactionTemplate`. The following example shows how to do so: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
public class SimpleService implements Service { |
|
|
|
// single TransactionTemplate shared amongst all methods in this instance |
|
private final TransactionTemplate transactionTemplate; |
|
|
|
// use constructor-injection to supply the PlatformTransactionManager |
|
public SimpleService(PlatformTransactionManager transactionManager) { |
|
this.transactionTemplate = new TransactionTemplate(transactionManager); |
|
} |
|
|
|
public Object someServiceMethod() { |
|
return transactionTemplate.execute(new TransactionCallback() { |
|
// the code in this method runs in a transactional context |
|
public Object doInTransaction(TransactionStatus status) { |
|
updateOperation1(); |
|
return resultOfUpdateOperation2(); |
|
} |
|
}); |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
// use constructor-injection to supply the PlatformTransactionManager |
|
class SimpleService(transactionManager: PlatformTransactionManager) : Service { |
|
|
|
// single TransactionTemplate shared amongst all methods in this instance |
|
private val transactionTemplate = TransactionTemplate(transactionManager) |
|
|
|
fun someServiceMethod() = transactionTemplate.execute<Any?> { |
|
updateOperation1() |
|
resultOfUpdateOperation2() |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
|
|
If there is no return value, you can use the convenient `TransactionCallbackWithoutResult` class |
|
with an anonymous class, as follows: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
transactionTemplate.execute(new TransactionCallbackWithoutResult() { |
|
protected void doInTransactionWithoutResult(TransactionStatus status) { |
|
updateOperation1(); |
|
updateOperation2(); |
|
} |
|
}); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
transactionTemplate.execute(object : TransactionCallbackWithoutResult() { |
|
override fun doInTransactionWithoutResult(status: TransactionStatus) { |
|
updateOperation1() |
|
updateOperation2() |
|
} |
|
}) |
|
---- |
|
====== |
|
|
|
|
|
Code within the callback can roll the transaction back by calling the |
|
`setRollbackOnly()` method on the supplied `TransactionStatus` object, as follows: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
transactionTemplate.execute(new TransactionCallbackWithoutResult() { |
|
|
|
protected void doInTransactionWithoutResult(TransactionStatus status) { |
|
try { |
|
updateOperation1(); |
|
updateOperation2(); |
|
} catch (SomeBusinessException ex) { |
|
status.setRollbackOnly(); |
|
} |
|
} |
|
}); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
transactionTemplate.execute(object : TransactionCallbackWithoutResult() { |
|
|
|
override fun doInTransactionWithoutResult(status: TransactionStatus) { |
|
try { |
|
updateOperation1() |
|
updateOperation2() |
|
} catch (ex: SomeBusinessException) { |
|
status.setRollbackOnly() |
|
} |
|
} |
|
}) |
|
---- |
|
====== |
|
|
|
[[tx-prog-template-settings]] |
|
=== Specifying Transaction Settings |
|
|
|
You can specify transaction settings (such as the propagation mode, the isolation level, |
|
the timeout, and so forth) on the `TransactionTemplate` either programmatically or in |
|
configuration. By default, `TransactionTemplate` instances have the |
|
xref:data-access/transaction/declarative/txadvice-settings.adoc[default transactional settings]. The |
|
following example shows the programmatic customization of the transactional settings for |
|
a specific `TransactionTemplate:` |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
public class SimpleService implements Service { |
|
|
|
private final TransactionTemplate transactionTemplate; |
|
|
|
public SimpleService(PlatformTransactionManager transactionManager) { |
|
this.transactionTemplate = new TransactionTemplate(transactionManager); |
|
|
|
// the transaction settings can be set here explicitly if so desired |
|
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); |
|
this.transactionTemplate.setTimeout(30); // 30 seconds |
|
// and so forth... |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
class SimpleService(transactionManager: PlatformTransactionManager) : Service { |
|
|
|
private val transactionTemplate = TransactionTemplate(transactionManager).apply { |
|
// the transaction settings can be set here explicitly if so desired |
|
isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED |
|
timeout = 30 // 30 seconds |
|
// and so forth... |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
The following example defines a `TransactionTemplate` with some custom transactional |
|
settings by using Spring XML configuration: |
|
|
|
[source,xml,indent=0,subs="verbatim,quotes"] |
|
---- |
|
<bean id="sharedTransactionTemplate" |
|
class="org.springframework.transaction.support.TransactionTemplate"> |
|
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/> |
|
<property name="timeout" value="30"/> |
|
</bean> |
|
---- |
|
|
|
You can then inject the `sharedTransactionTemplate` |
|
into as many services as are required. |
|
|
|
Finally, instances of the `TransactionTemplate` class are thread-safe, in that instances |
|
do not maintain any conversational state. `TransactionTemplate` instances do, however, |
|
maintain configuration state. So, while a number of classes may share a single instance |
|
of a `TransactionTemplate`, if a class needs to use a `TransactionTemplate` with |
|
different settings (for example, a different isolation level), you need to create |
|
two distinct `TransactionTemplate` instances. |
|
|
|
[[tx-prog-operator]] |
|
== Using the `TransactionalOperator` |
|
|
|
The `TransactionalOperator` follows an operator design that is similar to other reactive |
|
operators. It uses a callback approach (to free application code from having to do the |
|
boilerplate acquisition and release transactional resources) and results in code that is |
|
intention driven, in that your code focuses solely on what you want to do. |
|
|
|
NOTE: As the examples that follow show, using the `TransactionalOperator` absolutely |
|
couples you to Spring's transaction infrastructure and APIs. Whether or not programmatic |
|
transaction management is suitable for your development needs is a decision that you have |
|
to make yourself. |
|
|
|
Application code that must run in a transactional context and that explicitly uses |
|
the `TransactionalOperator` resembles the next example: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
public class SimpleService implements Service { |
|
|
|
// single TransactionalOperator shared amongst all methods in this instance |
|
private final TransactionalOperator transactionalOperator; |
|
|
|
// use constructor-injection to supply the ReactiveTransactionManager |
|
public SimpleService(ReactiveTransactionManager transactionManager) { |
|
this.transactionalOperator = TransactionalOperator.create(transactionManager); |
|
} |
|
|
|
public Mono<Object> someServiceMethod() { |
|
|
|
// the code in this method runs in a transactional context |
|
|
|
Mono<Object> update = updateOperation1(); |
|
|
|
return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional); |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
// use constructor-injection to supply the ReactiveTransactionManager |
|
class SimpleService(transactionManager: ReactiveTransactionManager) : Service { |
|
|
|
// single TransactionalOperator shared amongst all methods in this instance |
|
private val transactionalOperator = TransactionalOperator.create(transactionManager) |
|
|
|
suspend fun someServiceMethod() = transactionalOperator.executeAndAwait<Any?> { |
|
updateOperation1() |
|
resultOfUpdateOperation2() |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
`TransactionalOperator` can be used in two ways: |
|
|
|
* Operator-style using Project Reactor types (`mono.as(transactionalOperator::transactional)`) |
|
* Callback-style for every other case (`transactionalOperator.execute(TransactionCallback<T>)`) |
|
|
|
Code within the callback can roll the transaction back by calling the `setRollbackOnly()` |
|
method on the supplied `ReactiveTransaction` object, as follows: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
transactionalOperator.execute(new TransactionCallback<>() { |
|
|
|
public Mono<Object> doInTransaction(ReactiveTransaction status) { |
|
return updateOperation1().then(updateOperation2) |
|
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly()); |
|
} |
|
} |
|
}); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
transactionalOperator.execute(object : TransactionCallback() { |
|
|
|
override fun doInTransactionWithoutResult(status: ReactiveTransaction) { |
|
updateOperation1().then(updateOperation2) |
|
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly()) |
|
} |
|
}) |
|
---- |
|
====== |
|
|
|
[[tx-prog-operator-cancel]] |
|
=== Cancel Signals |
|
|
|
In Reactive Streams, a `Subscriber` can cancel its `Subscription` and stop its |
|
`Publisher`. Operators in Project Reactor, as well as in other libraries, such as `next()`, |
|
`take(long)`, `timeout(Duration)`, and others can issue cancellations. There is no way to |
|
know the reason for the cancellation, whether it is due to an error or a simply lack of |
|
interest to consume further. Since version 5.3 cancel signals lead to a roll back. |
|
As a result it is important to consider the operators used downstream from a transaction |
|
`Publisher`. In particular in the case of a `Flux` or other multi-value `Publisher`, |
|
the full output must be consumed to allow the transaction to complete. |
|
|
|
|
|
[[tx-prog-operator-settings]] |
|
=== Specifying Transaction Settings |
|
|
|
You can specify transaction settings (such as the propagation mode, the isolation level, |
|
the timeout, and so forth) for the `TransactionalOperator`. By default, |
|
`TransactionalOperator` instances have |
|
xref:data-access/transaction/declarative/txadvice-settings.adoc[default transactional settings]. The |
|
following example shows customization of the transactional settings for a specific |
|
`TransactionalOperator:` |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
public class SimpleService implements Service { |
|
|
|
private final TransactionalOperator transactionalOperator; |
|
|
|
public SimpleService(ReactiveTransactionManager transactionManager) { |
|
DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); |
|
|
|
// the transaction settings can be set here explicitly if so desired |
|
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); |
|
definition.setTimeout(30); // 30 seconds |
|
// and so forth... |
|
|
|
this.transactionalOperator = TransactionalOperator.create(transactionManager, definition); |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
class SimpleService(transactionManager: ReactiveTransactionManager) : Service { |
|
|
|
private val definition = DefaultTransactionDefinition().apply { |
|
// the transaction settings can be set here explicitly if so desired |
|
isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED |
|
timeout = 30 // 30 seconds |
|
// and so forth... |
|
} |
|
private val transactionalOperator = TransactionalOperator(transactionManager, definition) |
|
} |
|
---- |
|
====== |
|
|
|
[[transaction-programmatic-tm]] |
|
== Using the `TransactionManager` |
|
|
|
The following sections explain programmatic usage of imperative and reactive transaction |
|
managers. |
|
|
|
[[transaction-programmatic-ptm]] |
|
=== Using the `PlatformTransactionManager` |
|
|
|
For imperative transactions, you can use a |
|
`org.springframework.transaction.PlatformTransactionManager` directly to manage your |
|
transaction. To do so, pass the implementation of the `PlatformTransactionManager` you |
|
use to your bean through a bean reference. Then, by using the `TransactionDefinition` and |
|
`TransactionStatus` objects, you can initiate transactions, roll back, and commit. The |
|
following example shows how to do so: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); |
|
// explicitly setting the transaction name is something that can be done only programmatically |
|
def.setName("SomeTxName"); |
|
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); |
|
|
|
TransactionStatus status = txManager.getTransaction(def); |
|
try { |
|
// put your business logic here |
|
} catch (MyException ex) { |
|
txManager.rollback(status); |
|
throw ex; |
|
} |
|
txManager.commit(status); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
val def = DefaultTransactionDefinition() |
|
// explicitly setting the transaction name is something that can be done only programmatically |
|
def.setName("SomeTxName") |
|
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED |
|
|
|
val status = txManager.getTransaction(def) |
|
try { |
|
// put your business logic here |
|
} catch (ex: MyException) { |
|
txManager.rollback(status) |
|
throw ex |
|
} |
|
|
|
txManager.commit(status) |
|
---- |
|
====== |
|
|
|
|
|
[[transaction-programmatic-rtm]] |
|
=== Using the `ReactiveTransactionManager` |
|
|
|
When working with reactive transactions, you can use a |
|
`org.springframework.transaction.ReactiveTransactionManager` directly to manage your |
|
transaction. To do so, pass the implementation of the `ReactiveTransactionManager` you |
|
use to your bean through a bean reference. Then, by using the `TransactionDefinition` and |
|
`ReactiveTransaction` objects, you can initiate transactions, roll back, and commit. The |
|
following example shows how to do so: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); |
|
// explicitly setting the transaction name is something that can be done only programmatically |
|
def.setName("SomeTxName"); |
|
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); |
|
|
|
Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def); |
|
|
|
reactiveTx.flatMap(status -> { |
|
|
|
Mono<Object> tx = ...; // put your business logic here |
|
|
|
return tx.then(txManager.commit(status)) |
|
.onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex))); |
|
}); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
val def = DefaultTransactionDefinition() |
|
// explicitly setting the transaction name is something that can be done only programmatically |
|
def.setName("SomeTxName") |
|
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED |
|
|
|
val reactiveTx = txManager.getReactiveTransaction(def) |
|
reactiveTx.flatMap { status -> |
|
|
|
val tx = ... // put your business logic here |
|
|
|
tx.then(txManager.commit(status)) |
|
.onErrorResume { ex -> txManager.rollback(status).then(Mono.error(ex)) } |
|
} |
|
---- |
|
====== |
|
|
|
|
|
|