move session and transactions into one

This commit is contained in:
Christoph Strobl
2023-09-06 15:48:59 +02:00
parent d7094416a4
commit 3dcd0a3ca2

View File

@@ -15,13 +15,17 @@ This means that a potential call to `MongoCollection#find()` is delegated to `Mo
NOTE: Methods such as `(Reactive)MongoOperations#getCollection` return native MongoDB Java Driver gateway objects (such as `MongoCollection`) that themselves offer dedicated methods for `ClientSession`. These methods are *NOT* session-proxied. You should provide the `ClientSession` where needed when interacting directly with a `MongoCollection` or `MongoDatabase` and not through one of the `#execute` callbacks on `MongoOperations`.
[[mongo.sessions.sync]]
== Synchronous `ClientSession` support.
[[mongo.sessions.reactive]]
== ClientSession support
The following example shows the usage of a session:
.`ClientSession` with `MongoOperations`
[tabs]
======
Imperative::
+
====
[source,java]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
.causallyConsistent(true)
@@ -49,27 +53,23 @@ session.close() <4>
<2> Use `MongoOperation` methods as before. The `ClientSession` gets applied automatically.
<3> Make sure to close the `ClientSession`.
<4> Close the session.
====
WARNING: When dealing with `DBRef` instances, especially lazily loaded ones, it is essential to *not* close the `ClientSession` before all data is loaded. Otherwise, lazy fetch fails.
[[mongo.sessions.reactive]]
== Reactive `ClientSession` support
The reactive counterpart uses the same building blocks as the imperative one, as the following example shows:
.ClientSession with `ReactiveMongoOperations`
====
[source,java]
Reactive::
+
====
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
.causallyConsistent(true)
.build();
.causallyConsistent(true)
.build();
Publisher<ClientSession> session = client.startSession(sessionOptions); <1>
template.withSession(session)
.execute(action -> {
.execute(action -> {
Query query = query(where("name").is("Durzo Blint"));
return action.findOne(query, Person.class)
@@ -87,13 +87,14 @@ template.withSession(session)
<2> Use `ReactiveMongoOperation` methods as before. The `ClientSession` is obtained and applied automatically.
<3> Make sure to close the `ClientSession`.
<4> Nothing happens until you subscribe. See https://projectreactor.io/docs/core/release/reference/#reactive.subscribe[the Project Reactor Reference Guide] for details.
====
By using a `Publisher` that provides the actual session, you can defer session acquisition to the point of actual subscription.
Still, you need to close the session when done, so as to not pollute the server with stale sessions. Use the `doFinally` hook on `execute` to call `ClientSession#close()` when you no longer need the session.
If you prefer having more control over the session itself, you can obtain the `ClientSession` through the driver and provide it through a `Supplier`.
NOTE: Reactive use of `ClientSession` is limited to Template API usage. There's currently no session integration with reactive repositories.
====
======
[[mongo.transactions]]
== MongoDB Transactions
@@ -104,11 +105,15 @@ NOTE: Unless you specify a `MongoTransactionManager` within your application con
To get full programmatic control over transactions, you may want to use the session callback on `MongoOperations`.
The following example shows programmatic transaction control within a `SessionCallback`:
The following example shows programmatic transaction control:
.Programmatic transactions
[tabs]
======
Imperative::
+
====
[source,java]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ClientSession session = client.startSession(options); <1>
@@ -138,19 +143,58 @@ template.withSession(session)
<3> If everything works out as expected, commit the changes.
<4> Something broke, so roll back everything.
<5> Do not forget to close the session when done.
====
The preceding example lets you have full control over transactional behavior while using the session scoped `MongoOperations` instance within the callback to ensure the session is passed on to every server call.
To avoid some of the overhead that comes with this approach, you can use a `TransactionTemplate` to take away some of the noise of manual transaction flow.
====
Reactive::
+
====
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
Mono<DeleteResult> result = Mono
.from(client.startSession()) <1>
.flatMap(session -> {
session.startTransaction(); <2>
return Mono.from(collection.deleteMany(session, ...)) <3>
.onErrorResume(e -> Mono.from(session.abortTransaction()).then(Mono.error(e))) <4>
.flatMap(val -> Mono.from(session.commitTransaction()).then(Mono.just(val))) <5>
.doFinally(signal -> session.close()); <6>
});
----
<1> First we obviously need to initiate the session.
<2> Once we have the `ClientSession` at hand, start the transaction.
<3> Operate within the transaction by passing on the `ClientSession` to the operation.
<4> If the operations completes exceptionally, we need to stop the transaction and preserve the error.
<5> Or of course, commit the changes in case of success. Still preserving the operations result.
<6> Lastly, we need to make sure to close the session.
The culprit of the above operation is in keeping the main flows `DeleteResult` instead of the transaction outcome
published via either `commitTransaction()` or `abortTransaction()`, which leads to a rather complicated setup.
NOTE: Unless you specify a `ReactiveMongoTransactionManager` within your application context, transaction support is *DISABLED*. You can use `setSessionSynchronization(ALWAYS)` to participate in ongoing non-native MongoDB transactions.
====
======
[[mongo.transactions.transaction-template]]
== Transactions with `TransactionTemplate`
[[mongo.transactions.reactive-operator]]
== Transactions with TransactionTemplate / TransactionalOperator
Spring Data MongoDB transactions support a `TransactionTemplate`. The following example shows how to create and use a `TransactionTemplate`:
Spring Data MongoDB transactions support both `TransactionTemplate` and `TransactionalOperator`.
.Transactions with `TransactionTemplate`
.Transactions with `TransactionTemplate` / `TransactionalOperator`
[tabs]
======
Imperative::
+
====
[source,java]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
template.setSessionSynchronization(ALWAYS); <1>
@@ -175,19 +219,54 @@ txTemplate.execute(new TransactionCallbackWithoutResult() {
<1> Enable transaction synchronization during Template API configuration.
<2> Create the `TransactionTemplate` using the provided `PlatformTransactionManager`.
<3> Within the callback the `ClientSession` and transaction are already registered.
====
CAUTION: Changing state of `MongoTemplate` during runtime (as you might think would be possible in item 1 of the preceding listing) can cause threading and visibility issues.
====
Reactive::
+
====
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
template.setSessionSynchronization(ALWAYS); <1>
// ...
TransactionalOperator rxtx = TransactionalOperator.create(anyTxManager,
new DefaultTransactionDefinition()); <2>
Step step = // ...;
template.insert(step);
Mono<Void> process(step)
.then(template.update(Step.class).apply(Update.set("state", …))
.as(rxtx::transactional) <3>
.then();
----
<1> Enable transaction synchronization for Transactional participation.
<2> Create the `TransactionalOperator` using the provided `ReactiveTransactionManager`.
<3> `TransactionalOperator.transactional(…)` provides transaction management for all upstream operations.
====
======
[[mongo.transactions.tx-manager]]
== Transactions with `MongoTransactionManager`
[[mongo.transactions.reactive-tx-manager]]
== Transactions with MongoTransactionManager & ReactiveMongoTransactionManager
`MongoTransactionManager` is the gateway to the well known Spring transaction support. It lets applications use link:{springDocsUrl}/data-access.html#transaction[the managed transaction features of Spring].
The `MongoTransactionManager` binds a `ClientSession` to the thread. `MongoTemplate` detects the session and operates on these resources which are associated with the transaction accordingly. `MongoTemplate` can also participate in other, ongoing transactions. The following example shows how to create and use transactions with a `MongoTransactionManager`:
`MongoTransactionManager` / `ReactiveMongoTransactionManager` is the gateway to the well known Spring transaction support.
It lets applications use link:{springDocsUrl}/data-access.html#transaction[the managed transaction features of Spring].
The `MongoTransactionManager` binds a `ClientSession` to the thread whereas the `ReactiveMongoTransactionManager` is using the `ReactorContext` for this.
`MongoTemplate` detects the session and operates on these resources which are associated with the transaction accordingly.
`MongoTemplate` can also participate in other, ongoing transactions. The following example shows how to create and use transactions with a `MongoTransactionManager`:
.Transactions with `MongoTransactionManager`
.Transactions with `MongoTransactionManager` / `ReactiveMongoTransactionManager`
[tabs]
======
Imperative::
+
====
[source,java]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@Configuration
static class Config extends AbstractMongoClientConfiguration {
@@ -217,94 +296,15 @@ public class StateService {
----
<1> Register `MongoTransactionManager` in the application context.
<2> Mark methods as transactional.
====
NOTE: `@Transactional(readOnly = true)` advises `MongoTransactionManager` to also start a transaction that adds the
`ClientSession` to outgoing requests.
[[mongo.transactions.reactive]]
== Reactive Transactions
Same as with the reactive `ClientSession` support, the `ReactiveMongoTemplate` offers dedicated methods for operating
within a transaction without having to worry about the committing or stopping actions depending on the operations outcome.
NOTE: Unless you specify a `ReactiveMongoTransactionManager` within your application context, transaction support is *DISABLED*. You can use `setSessionSynchronization(ALWAYS)` to participate in ongoing non-native MongoDB transactions.
Using the plain MongoDB reactive driver API a `delete` within a transactional flow may look like this.
.Native driver support
====
[source,java]
----
Mono<DeleteResult> result = Mono
.from(client.startSession()) <1>
.flatMap(session -> {
session.startTransaction(); <2>
return Mono.from(collection.deleteMany(session, ...)) <3>
.onErrorResume(e -> Mono.from(session.abortTransaction()).then(Mono.error(e))) <4>
.flatMap(val -> Mono.from(session.commitTransaction()).then(Mono.just(val))) <5>
.doFinally(signal -> session.close()); <6>
});
----
<1> First we obviously need to initiate the session.
<2> Once we have the `ClientSession` at hand, start the transaction.
<3> Operate within the transaction by passing on the `ClientSession` to the operation.
<4> If the operations completes exceptionally, we need to stop the transaction and preserve the error.
<5> Or of course, commit the changes in case of success. Still preserving the operations result.
<6> Lastly, we need to make sure to close the session.
`ClientSession` to outgoing requests.
====
The culprit of the above operation is in keeping the main flows `DeleteResult` instead of the transaction outcome
published via either `commitTransaction()` or `abortTransaction()`, which leads to a rather complicated setup.
[[mongo.transactions.reactive-operator]]
== Transactions with `TransactionalOperator`
Spring Data MongoDB transactions support a `TransactionalOperator`. The following example shows how to create and use a `TransactionalOperator`:
.Transactions with `TransactionalOperator`
Reactive::
+
====
[source,java]
----
template.setSessionSynchronization(ALWAYS); <1>
// ...
TransactionalOperator rxtx = TransactionalOperator.create(anyTxManager,
new DefaultTransactionDefinition()); <2>
Step step = // ...;
template.insert(step);
Mono<Void> process(step)
.then(template.update(Step.class).apply(Update.set("state", …))
.as(rxtx::transactional) <3>
.then();
----
<1> Enable transaction synchronization for Transactional participation.
<2> Create the `TransactionalOperator` using the provided `ReactiveTransactionManager`.
<3> `TransactionalOperator.transactional(…)` provides transaction management for all upstream operations.
====
[[mongo.transactions.reactive-tx-manager]]
== Transactions with `ReactiveMongoTransactionManager`
`ReactiveMongoTransactionManager` is the gateway to the well known Spring transaction support.
It allows applications to leverage https://docs.spring.io/spring-framework/docs/{springVersion}/reference/html/data-access.html#transaction[the managed transaction features of Spring].
The `ReactiveMongoTransactionManager` binds a `ClientSession` to the subscriber `Context`.
`ReactiveMongoTemplate` detects the session and operates on these resources which are associated with the transaction accordingly.
`ReactiveMongoTemplate` can also participate in other, ongoing transactions.
The following example shows how to create and use transactions with a `ReactiveMongoTransactionManager`:
.Transactions with `ReactiveMongoTransactionManager`
====
[source,java]
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
@Configuration
public class Config extends AbstractReactiveMongoConfiguration {
@@ -332,9 +332,10 @@ public class StateService {
----
<1> Register `ReactiveMongoTransactionManager` in the application context.
<2> Mark methods as transactional.
====
NOTE: `@Transactional(readOnly = true)` advises `ReactiveMongoTransactionManager` to also start a transaction that adds the `ClientSession` to outgoing requests.
====
======
[[mongo.transactions.behavior]]
== Special behavior inside transactions