mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-06 15:09:34 +00:00
This commit fixes syntax errors in Java, Kotlin, and XML examples in the reference manual, such as Java syntax in Kotlin examples, unbalanced braces and parentheses, and unclosed XML elements. Closes gh-37392
494 lines
16 KiB
Plaintext
494 lines
16 KiB
Plaintext
[[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"]
|
|
----
|
|
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"]
|
|
----
|
|
// 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"]
|
|
----
|
|
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
|
|
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
|
updateOperation1();
|
|
updateOperation2();
|
|
}
|
|
});
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
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"]
|
|
----
|
|
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"]
|
|
----
|
|
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"]
|
|
----
|
|
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"]
|
|
----
|
|
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"]
|
|
----
|
|
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"]
|
|
----
|
|
// 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"]
|
|
----
|
|
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"]
|
|
----
|
|
transactionalOperator.execute(object : TransactionCallback<Any> {
|
|
|
|
override fun doInTransaction(status: ReactiveTransaction): Mono<Any> {
|
|
return updateOperation1().then(updateOperation2)
|
|
.doOnError(SomeBusinessException::class.java) { 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"]
|
|
----
|
|
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"]
|
|
----
|
|
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"]
|
|
----
|
|
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"]
|
|
----
|
|
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"]
|
|
----
|
|
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"]
|
|
----
|
|
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)) }
|
|
}
|
|
----
|
|
======
|