Document ReactiveTransactionManager support in reference manual

See gh-25030.
This commit is contained in:
Mark Paluch
2020-05-08 12:19:49 +02:00
committed by Sam Brannen
parent 706446a660
commit 7fbdc3ad11
2 changed files with 460 additions and 35 deletions

View File

@@ -151,13 +151,14 @@ configuration file need to change (rather than your code).
=== Understanding the Spring Framework Transaction Abstraction
The key to the Spring transaction abstraction is the notion of a transaction
strategy. A transaction strategy is defined by the
`org.springframework.transaction.PlatformTransactionManager` interface, which the following listing shows:
strategy. A transaction strategy is defined by a `TransactionManager`, specifically
the `org.springframework.transaction.PlatformTransactionManager` interface for
imperative transaction management which the following listing shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
public interface PlatformTransactionManager {
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
@@ -169,7 +170,7 @@ strategy. A transaction strategy is defined by the
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
interface PlatformTransactionManager {
interface PlatformTransactionManager : TransactionManager {
@Throws(TransactionException::class)
fun getTransaction(definition: TransactionDefinition): TransactionStatus
@@ -206,6 +207,44 @@ exists in the current call stack. The implication in this latter case is that, a
Java EE transaction contexts, a `TransactionStatus` is associated with a thread of
execution.
Spring Framework provides a transaction management abstraction for reactive applications
that make use of reactive types or Kotlin Coroutines. The following listing
shows the transaction strategy defined by
`org.springframework.transaction.ReactiveTransactionManager`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
public interface ReactiveTransactionManager extends TransactionManager {
Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;
Mono<Void> commit(ReactiveTransaction status) throws TransactionException;
Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
interface ReactiveTransactionManager : TransactionManager {
@Throws(TransactionException::class)
fun getReactiveTransaction(definition: TransactionDefinition): Mono<ReactiveTransaction>
@Throws(TransactionException::class)
fun commit(status: ReactiveTransaction): Mono<Void>
@Throws(TransactionException::class)
fun rollback(status: ReactiveTransaction): Mono<Void>
}
----
The reactive transaction manager is primarily a service provider interface (SPI),
although you can use it <<transaction-programmatic-rtm, programmatically>> from your
application code. Because `ReactiveTransactionManager` is an interface, it can
be easily mocked or stubbed as necessary.
The `TransactionDefinition` interface specifies:
* Propagation: Typically, all code executed within a transaction scope runs in
@@ -237,45 +276,49 @@ familiar, as they are common to all transaction APIs. The following listing show
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
public interface TransactionStatus extends SavepointManager {
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
@Override
boolean isNewTransaction();
boolean hasSavepoint();
@Override
void setRollbackOnly();
@Override
boolean isRollbackOnly();
void flush();
@Override
boolean isCompleted();
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
interface TransactionStatus : SavepointManager {
interface TransactionStatus : TransactionExecution, SavepointManager, Flushable {
fun isNewTransaction(): Boolean
override fun isNewTransaction(): Boolean
fun hasSavepoint(): Boolean
fun setRollbackOnly()
override fun setRollbackOnly()
fun isRollbackOnly(): Boolean
override fun isRollbackOnly(): Boolean
fun flush()
fun isCompleted(): Boolean
override fun isCompleted(): Boolean
}
----
Regardless of whether you opt for declarative or programmatic transaction management in
Spring, defining the correct `PlatformTransactionManager` implementation is absolutely
Spring, defining the correct `TransactionManager` implementation is absolutely
essential. You typically define this implementation through dependency injection.
`PlatformTransactionManager` implementations normally require knowledge of the
`TransactionManager` implementations normally require knowledge of the
environment in which they work: JDBC, JTA, Hibernate, and so on. The following examples
show how you can define a local `PlatformTransactionManager` implementation (in this case,
with plain JDBC.)
@@ -402,7 +445,7 @@ and so forth) should now be clear. This section describes how the application co
(directly or indirectly, by using a persistence API such as JDBC, Hibernate, or JPA)
ensures that these resources are created, reused, and cleaned up properly. The section
also discusses how transaction synchronization is (optionally) triggered through the
relevant `PlatformTransactionManager`.
relevant `TransactionManager`.
[[tx-resource-synchronization-high]]
@@ -542,11 +585,22 @@ transaction support are that this support is enabled
<<core.adoc#aop-understanding-aop-proxies, via AOP proxies>> and that the transactional
advice is driven by metadata (currently XML- or annotation-based). The combination of
AOP with transactional metadata yields an AOP proxy that uses a `TransactionInterceptor`
in conjunction with an appropriate `PlatformTransactionManager` implementation to drive
in conjunction with an appropriate `TransactionManager` implementation to drive
transactions around method invocations.
NOTE: Spring AOP is covered in <<core.adoc#aop, the AOP section>>.
Spring Frameworks's `TransactionInterceptor` provides transaction management for imperative
and reactive programming models. The interceptor detects the desired flavor of transaction
management by inspecting the method return type. Methods returning a reactive type such
as `Publisher` or Kotlin `Flow` (or a subtype of those) qualify for reactive transaction
management. All other return types including `void` use the code path for imperative
transaction management.
Transaction management flavors impacts which transaction manager required.
Imperative transactions require a `PlatformTransactionManager` while reactive transactions
use `ReactiveTransactionManager` implementations.
The following images shows a Conceptual view of calling a method on a transactional proxy:
image::images/tx.png[]
@@ -707,7 +761,7 @@ configuration is explained in detail in the next few paragraphs:
<property name="password" value="tiger"/>
</bean>
<!-- similarly, don't forget the PlatformTransactionManager -->
<!-- similarly, don't forget the TransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
@@ -723,12 +777,12 @@ bean, transactional. The transaction semantics to apply are encapsulated in the
starting with `get`, are to execute in the context of a read-only transaction, and all
other methods are to execute with the default transaction semantics`". The
`transaction-manager` attribute of the `<tx:advice/>` tag is set to the name of the
`PlatformTransactionManager` bean that is going to drive the transactions (in this
`TransactionManager` bean that is going to drive the transactions (in this
case, the `txManager` bean).
TIP: You can omit the `transaction-manager` attribute in the transactional advice
(`<tx:advice/>`) if the bean name of the `PlatformTransactionManager` that you want to
wire in has the name `transactionManager`. If the `PlatformTransactionManager` bean that
(`<tx:advice/>`) if the bean name of the `TransactionManager` that you want to
wire in has the name `transactionManager`. If the `TransactionManager` bean that
you want to wire in has any other name, you must use the `transaction-manager`
attribute explicitly, as in the preceding example.
@@ -826,6 +880,128 @@ insertFoo(..) method of the DefaultFooService class have been truncated for clar
at Boot.main(Boot.java:11)
----
To use reactive transaction management the code has to use reactive types.
NOTES: Spring Framework uses `ReactiveAdapterRegistry` to determine whether a
method return type is reactive.
The following listing shows the previously used `FooService` but this time the code is using reactive types:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
// the reactive service interface that we want to make transactional
package x.y.service;
public interface FooService {
Flux<Foo> getFoo(String fooName);
Publisher<Foo> getFoo(String fooName, String barName);
Mono<Void> insertFoo(Foo foo);
Mono<Void> updateFoo(Foo foo);
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
// the reactive service interface that we want to make transactional
package x.y.service
interface FooService {
fun getFoo(fooName: String): Flow<Foo>
fun getFoo(fooName: String, barName: String): Publisher<Foo>
fun insertFoo(foo: Foo) : Mono<Void>
fun updateFoo(foo: Foo)
}
----
The following example shows an implementation of the preceding interface:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
package x.y.service;
public class DefaultFooService implements FooService {
@Override
public Flux<Foo> getFoo(String fooName) {
// ...
}
@Override
public Publisher<Foo> getFoo(String fooName, String barName) {
// ...
}
@Override
public Mono<Void> insertFoo(Foo foo) {
// ...
}
@Override
public Mono<Void> updateFoo(Foo foo) {
// ...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
package x.y.service
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Flow<Foo> {
// ...
}
override fun getFoo(fooName: String, barName: String): Publisher<Foo> {
// ...
}
override fun insertFoo(foo: Foo): Mono<Void> {
// ...
}
override fun updateFoo(foo: Foo): Mono<Void> {
// ...
}
}
----
Imperative and reactive transaction management shares the same semantics for
transaction boundary and transaction attribute definitions. The main difference
to imperative transactions is the deferred nature. `TransactionInterceptor`
decorates the returned reactive type with a transactional operator
to begin and cleanup the transaction. Therefore, calling a transactional
reactive method defers the actual transaction management to subscription
type that activates processing of the reactive type.
Another aspect of reactive transaction management relates to data escaping
which is a natural consequence of the programming model.
Method return values of imperative transactions are returned from transactional methods
upon successful termination of a method so that partially computed results
do not escape the method closure.
Reactive transaction methods return a reactive wrapper type which represent a
computation sequence along with a promise to begin and complete computation.
A `Publisher` can emit data while an transaction is ongoing but not necessarily
completed. Therefore, methods that depend upon successful completion of an entire
transaction need to ensure completion and buffer results in the calling code.
[[transaction-declarative-rolling-back]]
==== Rolling Back a Declarative Transaction
@@ -1220,10 +1396,62 @@ In XML configuration, the `<tx:annotation-driven/>` tag provides similar conveni
TIP: You can omit the `transaction-manager` attribute in the `<tx:annotation-driven/>` tag
if the bean name of the `PlatformTransactionManager` that you want to wire in has the name,
`transactionManager`. If the `PlatformTransactionManager` bean that you want to
`transactionManager`. If the `TransactionManager` bean that you want to
dependency-inject has any other name, you have to use the `transaction-manager` attribute,
as in the preceding example.
Reactive transactional methods use reactive return types in contrast to imperative programming
arrangements as the following listing shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
// the reactive service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {
Publisher<Foo> getFoo(String fooName) {
// ...
}
Mono<Foo> getFoo(String fooName, String barName) {
// ...
}
Mono<Void> insertFoo(Foo foo) {
// ...
}
Mono<Void> updateFoo(Foo foo) {
// ...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
// the reactive service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Flow<Foo> {
// ...
}
override fun getFoo(fooName: String, barName: String): Mono<Foo> {
// ...
}
override fun insertFoo(foo: Foo): Mono<Void> {
// ...
}
override fun updateFoo(foo: Foo): Mono<Void> {
// ...
}
}
----
.Method visibility and `@Transactional`
****
When you use proxies, you should apply the `@Transactional` annotation only to methods
@@ -1438,11 +1666,12 @@ name of the transaction would be: `com.example.BusinessService.handlePayment`.
Most Spring applications need only a single transaction manager, but there may be
situations where you want multiple independent transaction managers in a single
application. You can use the `value` attribute of the `@Transactional` annotation to
optionally specify the identity of the `PlatformTransactionManager` to be used.
This can either be the bean name or the qualifier value of the transaction manager bean.
For example, using the qualifier notation, you can combine the following Java code with
the following transaction manager bean declarations in the application context:
application. You can use the `value` or `transactionManager` attribute of the
`@Transactional` annotation to optionally specify the identity of the
`TransactionManager` to be used. This can either be the bean name or the
qualifier value of the transaction manager bean. For example, using the qualifier
notation, you can combine the following Java code with the following transaction manager
bean declarations in the application context:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
@@ -1454,6 +1683,9 @@ the following transaction manager bean declarations in the application context:
@Transactional("account")
public void doSomething() { ... }
@Transactional("reactive-account")
public Mono<Void> doSomethingReactive() { ... }
}
----
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
@@ -1470,6 +1702,11 @@ the following transaction manager bean declarations in the application context:
fun doSomething() {
// ...
}
@Transactional("reactive-account")
fun doSomethingReactive(): Mono<Void> {
// ...
}
}
----
@@ -1488,12 +1725,17 @@ The following listing shows the bean declarations:
...
<qualifier value="account"/>
</bean>
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
...
<qualifier value="reactive-account"/>
</bean>
----
In this case, the two methods on `TransactionalService` run under separate transaction
managers, differentiated by the `order` and `account` qualifiers. The default
In this case, the individual methods on `TransactionalService` run under separate transaction
managers, differentiated by the `order`, `account`, and `reactive-account` qualifiers. The default
`<tx:annotation-driven>` target bean name, `transactionManager`, is still used if no
specifically qualified `PlatformTransactionManager` bean is found.
specifically qualified `TransactionManager` bean is found.
[[tx-custom-attributes]]
===== Custom Shortcut Annotations
@@ -1838,7 +2080,7 @@ declarative approach:
</tx:attributes>
</tx:advice>
<!-- other <bean/> definitions such as a DataSource and a PlatformTransactionManager here -->
<!-- other <bean/> definitions such as a DataSource and a TransactionManager here -->
</beans>
----
@@ -1918,11 +2160,12 @@ AspectJ in the Spring Framework>> for a discussion of load-time weaving with Asp
The Spring Framework provides two means of programmatic transaction management, by using:
* The `TransactionTemplate`.
* A `PlatformTransactionManager` implementation directly.
* The `TransactionTemplate` or `TransactionalOperator`.
* A `TransactionManager` implementation directly.
The Spring team generally recommends the `TransactionTemplate` for programmatic
transaction management. The second approach is similar to using the JTA
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.
@@ -2110,11 +2353,147 @@ 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 `TransactionOperator`
The `TransactionOperator` 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 `TransactionOperator` 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 execute in a transactional context and that explicitly uses the
`TransactionOperator` resembles the next example:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
public class SimpleService implements Service {
// single TransactionOperator shared amongst all methods in this instance
private final TransactionalOperator transactionalOperator;
// use constructor-injection to supply the ReactiveTransactionManager
public SimpleService(ReactiveTransactionManager transactionManager) {
this.transactionOperator = TransactionalOperator.create(transactionManager);
}
public Mono<Object> someServiceMethod() {
// the code in this method executes in a transactional context
Mono<Object> update = updateOperation1();
return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
// 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 `TransactionStatus` object, as follows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
transactionalOperator.execute(new TransactionCallback<>() {
public Mono<Object> doInTransaction(ReactiveTransaction status) {
return updateOperation1().then(updateOperation2)
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
}
}
});
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
transactionalOperator.execute(object : TransactionCallback() {
override fun doInTransactionWithoutResult(status: ReactiveTransaction) {
updateOperation1().then(updateOperation2)
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly())
}
})
----
[[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 the
<<transaction-declarative-txadvice-settings,default transactional settings>>. The
following example shows customization of the transactional settings for
a specific `TransactionalOperator:`
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
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);
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
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 section explains programmatic usage of imperative and reactive transaction managers.
[[transaction-programmatic-ptm]]
==== Using the `PlatformTransactionManager`
===== Using the `PlatformTransactionManager`
You can also use the `org.springframework.transaction.PlatformTransactionManager`
For imperative transactions, you can use the `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
@@ -2158,6 +2537,52 @@ transactions, roll back, and commit. The following example shows how to do so:
----
[[transaction-programmatic-rtm]]
===== Using the `ReactiveTransactionManager`
When working with reactive transactions, you can use
`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:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
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 = ...; // execute your business logic here
return tx.then(txManager.commit(status))
.onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
});
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
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 = ... // execute your business logic here
tx.then(txManager.commit(status))
.onErrorResume { ex -> txManager.rollback(status).then(Mono.error(ex)) }
}
----
[[tx-decl-vs-prog]]
=== Choosing Between Programmatic and Declarative Transaction Management

View File

@@ -30,9 +30,9 @@ Alef Arendsen, Darren Davison, Dmitriy Kopylenko, Mark Pollack, Thierry Templier
Vervaet, Portia Tung, Ben Hale, Adrian Colyer, John Lewis, Costin Leau, Mark Fisher, Sam
Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clement, Dave
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
Nicoll, Sebastien Deleuze, Jay Bryant
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
Copyright © 2002 - 2019 Pivotal, Inc. All Rights Reserved.
Copyright © 2002 - 2020 Pivotal, Inc. All Rights Reserved.
Copies of this document may be made for your own use and for distribution to others,
provided that you do not charge any fee for such copies and further provided that each