DATAMONGO-1920 - Add support for MongoDB 4.0 transactions (synchronous driver).
MongoTransactionManager is the gateway to the well known Spring transaction support. It allows applications to use managed transaction features of Spring.
The MongoTransactionManager binds a ClientSession to the thread. MongoTemplate automatically detects those and operates on them accordingly.
static class Config extends AbstractMongoConfiguration {
// ...
@Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
}
@Component
public class StateService {
@Transactional
void someBusinessFunction(Step step) {
template.insert(step);
process(step);
template.update(Step.class).apply(update.set("state", // ...
};
});
Original pull request: #554.
This commit is contained in:
committed by
Mark Paluch
parent
6fbb7cec22
commit
4cd2935087
@@ -27,7 +27,7 @@ include::{spring-data-commons-docs}/repositories.adoc[]
|
||||
include::reference/introduction.adoc[]
|
||||
include::reference/mongodb.adoc[]
|
||||
include::reference/reactive-mongodb.adoc[]
|
||||
include::reference/client-session.adoc[]
|
||||
include::reference/client-session-transactions.adoc[]
|
||||
include::reference/mongo-repositories.adoc[]
|
||||
include::reference/reactive-mongo-repositories.adoc[]
|
||||
include::{spring-data-commons-docs}/auditing.adoc[]
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
* <<mongo.jsonSchema,`$jsonSchema` support>> for queries and collection creation.
|
||||
* <<change-streams, Change Stream support>> for imperative and reactive drivers.
|
||||
* Tailable cursors for imperative driver.
|
||||
* <<mongo.sessions, MongoDB Session>> support for the imperative and reactive Template API.
|
||||
* <<mongo.sessions, MongoDB 3.6 Session>> support for the imperative and reactive Template API.
|
||||
* <<mongo.transactions, MongoDB 4.0 Transaction>> support and MongoDB specific transaction manager implementation.
|
||||
|
||||
[[new-features.2-0-0]]
|
||||
== What's new in Spring Data MongoDB 2.0
|
||||
|
||||
200
src/main/asciidoc/reference/client-session-transactions.adoc
Normal file
200
src/main/asciidoc/reference/client-session-transactions.adoc
Normal file
@@ -0,0 +1,200 @@
|
||||
[[mongo.sessions]]
|
||||
= MongoDB Sessions
|
||||
|
||||
As of version 3.6 MongoDB supports a concept of Sessions. The use of sessions enables MongoDBs https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#causal-consistency[Causal Consistency] model guaranteeing to execute operations in an order that respect their causal relationships. Those are split into ``ServerSession``s and ``ClientSession``s. In the following when we speak of session we refer to `ClientSession`.
|
||||
|
||||
WARNING: Operations within a client session are not isolated from operations outside the session.
|
||||
|
||||
Both `MongoOperations` and `ReactiveMongoOperations` provide gateway methods for tying a `ClientSession` to the operations themselves. `MongoCollection` and `MongoDatabase` use session proxy objects implementing MongoDB's collection and and database interfaces so there's no need to add a session on each call. This means that a potential call to `MongoCollection#find()` is delegated to `MongoCollection#find(ClientSession)`.
|
||||
|
||||
NOTE: Methods like `(Reactive)MongoOperations#getCollection` returning native MongoDB Java Driver gateway objects, such as `MongoCollection`, that themselves offer dedicated methods for `ClientSession` are *NOT* be session-proxied. So make sure to provide the `ClientSession` where needed when interacting directly with a `MongoCollection` or `MongoDatabase` and not via one of the `#execute` callbacks on `MongoOperations`.
|
||||
|
||||
.ClientSession with `MongoOperations`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
|
||||
.causallyConsistent(true)
|
||||
.build();
|
||||
|
||||
ClientSession session = client.startSession(sessionOptions); <1>
|
||||
|
||||
template.withSession(() -> session)
|
||||
.execute(action -> {
|
||||
|
||||
Query query = query(where("name").is("Durzo Blint"));
|
||||
Person durzo = action.findOne(query, Person.class); <2>
|
||||
|
||||
Person azoth = new Person("Kylar Stern");
|
||||
azoth.setMaster(durzo);
|
||||
|
||||
action.insert(azoth); <2>
|
||||
|
||||
return azoth;
|
||||
});
|
||||
|
||||
session.close() <4>
|
||||
----
|
||||
<1> Obtain a new session from the server.
|
||||
<2> Use `MongoOperation` methods as before. The `ClientSession` gets applied automatically.
|
||||
<3> Make sure to close the `ClientSession`.
|
||||
====
|
||||
|
||||
WARNING: When dealing with ``DBRef``s, especially lazily loaded ones, it is essential to **not** close the `ClientSession` before all data is loaded. Otherwise, lazy fetch fails.
|
||||
|
||||
The reactive counterpart uses the very same building blocks as the imperative one.
|
||||
|
||||
.ClientSession with `ReactiveMongoOperations`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
|
||||
.causallyConsistent(true)
|
||||
.build();
|
||||
|
||||
Publisher<ClientSession> session = client.startSession(sessionOptions); <1>
|
||||
|
||||
template.withSession(session)
|
||||
.execute(action -> {
|
||||
|
||||
Query query = query(where("name").is("Durzo Blint"));
|
||||
return action.findOne(query, Person.class)
|
||||
.flatMap(durzo -> {
|
||||
|
||||
Person azoth = new Person("Kylar Stern");
|
||||
azoth.setMaster(durzo);
|
||||
|
||||
return action.insert(azoth); <2>
|
||||
});
|
||||
}, ClientSession::close) <3>
|
||||
.subscribe();
|
||||
----
|
||||
<1> Obtain a `Publisher` for new session retrieval.
|
||||
<2> Use `ReactiveMongoOperation` methods as before. The `ClientSession` is obtained and applied automatically.
|
||||
<3> Make sure to close the `ClientSession`.
|
||||
====
|
||||
|
||||
By using a `Publisher` providing the actual session you can defer session acquisition to the point of actual subscription.
|
||||
Still you need to close the session when done in order to not pollute the server with stale sessions. Use the `doFinally` hook on `execute` to call `ClientSession#close()` when you don't need the session any more.
|
||||
In case you prefer having more control over the session itself, you can always obtain the `ClientSession` via the driver and provide it via a `Supplier`.
|
||||
|
||||
|
||||
[[mongo.transactions]]
|
||||
= MongoDB Transactions
|
||||
|
||||
As of version 4 MongoDB supports https://www.mongodb.com/transactions[Transactions]. Transactions are built on top of <<mongo.sessions,Sessions>> and therefore require an active `ClientSession`.
|
||||
|
||||
NOTE: By default, unless you specify a `MongoTransactionManager` within your application context, transaction support is **DISABLED**. You may use `setSessionSynchronization(ANY)` to participate in ongoing non native MongoDB transactions.
|
||||
|
||||
To get full programmatic control over transactions you may want to use the session callback on `MongoOperations`.
|
||||
|
||||
.Programmatic transactions
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientSession session = client.startSession(options); <1>
|
||||
|
||||
template.withSession(session)
|
||||
.execute(action -> {
|
||||
|
||||
session.startTransaction(); <2>
|
||||
|
||||
try {
|
||||
|
||||
Step step = // ...;
|
||||
action.insert(step);
|
||||
|
||||
process(step);
|
||||
|
||||
action.update(Step.class).apply(Update.set("state", // ...
|
||||
|
||||
session.commitTransaction(); <3>
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
session.abortTransaction(); <4>
|
||||
}
|
||||
}, ClientSession::close) <5>
|
||||
.subscribe();
|
||||
----
|
||||
<1> Obtain a new `ClientSession`.
|
||||
<2> Start the transaction.
|
||||
<3> If everything works out as expected, go on and commit the changes.
|
||||
<4> Something broke, just roll back everything.
|
||||
<5> Do not forget to close the session when done.
|
||||
====
|
||||
|
||||
The above example allows you to have full control over transactional behavior while using the session scoped `MongoOperations` instance within the callback to ensure the session is passed on to each and every server call.
|
||||
To avoid some of the overhead that comes with this approach usage of a `TransactionTemplate` can take away some of the noise of manual transaction flow.
|
||||
|
||||
== Transactions with TransactionTemplate
|
||||
|
||||
.Transactions with TransactionTemplate
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
template.setSessionSynchronization(ANY); <1>
|
||||
|
||||
// ...
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(anyTxManager); <2>
|
||||
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) { <3>
|
||||
|
||||
Step step = // ...;
|
||||
template.insert(step);
|
||||
|
||||
process(step);
|
||||
|
||||
template.update(Step.class).apply(Update.set("state", // ...
|
||||
};
|
||||
});
|
||||
----
|
||||
<1> Manually enable transaction synchronization.
|
||||
<2> Create the `TransactionTemplate` using the provided `PlatformTransactionManager`.
|
||||
<3> Within the callback the `ClientSession` and transaction are already registered.
|
||||
====
|
||||
|
||||
== Transactions with MongoTransactionManager
|
||||
|
||||
`MongoTransactionManager` is the gateway to the well known Spring transaction support. It allows applications to use http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/transaction.html[managed transaction features of Spring].
|
||||
The `MongoTransactionManager` binds a `ClientSession` to the thread. `MongoTemplate` automatically detects those and operates on them accordingly. `MongoTemplate` can also participate in other, ongoing transactions.
|
||||
|
||||
.Transactions with MongoTransactionManager
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
static class Config extends AbstractMongoConfiguration {
|
||||
|
||||
@Bean
|
||||
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) { <1>
|
||||
return new MongoTransactionManager(dbFactory);
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
@Component
|
||||
public class StateService {
|
||||
|
||||
@Transactional
|
||||
void someBusinessFunction(Step step) { <2>
|
||||
|
||||
template.insert(step);
|
||||
|
||||
process(step);
|
||||
|
||||
template.update(Step.class).apply(Update.set("state", // ...
|
||||
};
|
||||
});
|
||||
|
||||
----
|
||||
<1> Register `MongoTransactionManager` in the application context.
|
||||
<2> Mark methods as transactional.
|
||||
====
|
||||
|
||||
NOTE: `@Transactional(readOnly = true)` advises the `MongoTransactionManager` to also start a transaction adding the
|
||||
`ClientSession` to outgoing requests.
|
||||
@@ -1,79 +0,0 @@
|
||||
[[mongo.sessions]]
|
||||
= MongoDB Sessions
|
||||
|
||||
As of version 3.6 MongoDB supports a concept of Sessions. The use of sessions enables MongoDBs https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#causal-consistency[Causal Consistency] model guaranteeing to execute operations in an order that respect their causal relationships. Those are split into ``ServerSession``s and ``ClientSession``s. In the following when we speak of session we refer to `ClientSession`.
|
||||
|
||||
WARNING: Operations within a client session are not isolated from operations outside the session.
|
||||
|
||||
Both `MongoOperations` and `ReactiveMongoOperations` provide gateway methods for tying a `ClientSession` to the operations themselves. `MongoCollection` and `MongoDatabase` use session proxy objects implementing MongoDB's collection and and database interfaces so there's no need to add a session on each call. This means that a potential call to `MongoCollection#find()` is delegated to `MongoCollection#find(ClientSession)`.
|
||||
|
||||
NOTE: Methods like `(Reactive)MongoOperations#getCollection` returning native MongoDB Java Driver gateway objects, such as `MongoCollection`, that themselves offer dedicated methods for `ClientSession` are *NOT* be session-proxied. So make sure to provide the `ClientSession` where needed when interacting directly with a `MongoCollection` or `MongoDatabase` and not via one of the `#execute` callbacks on `MongoOperations`.
|
||||
|
||||
.ClientSession with `MongoOperations`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
|
||||
.causallyConsistent(true)
|
||||
.build();
|
||||
|
||||
ClientSession session = client.startSession(sessionOptions); <1>
|
||||
|
||||
template.withSession(() -> session)
|
||||
.execute(action -> {
|
||||
|
||||
Query query = query(where("name").is("Durzo Blint"));
|
||||
Person durzo = action.findOne(query, Person.class); <2>
|
||||
|
||||
Person azoth = new Person("Kylar Stern");
|
||||
azoth.setMaster(durzo);
|
||||
|
||||
action.insert(azoth); <2>
|
||||
|
||||
return azoth;
|
||||
});
|
||||
|
||||
session.close() <4>
|
||||
----
|
||||
<1> Obtain a new session from the server.
|
||||
<2> Use `MongoOperation` methods as before. The `ClientSession` gets applied automatically.
|
||||
<3> Make sure to close the `ClientSession`.
|
||||
====
|
||||
|
||||
WARNING: When dealing with ``DBRef``s, especially lazily loaded ones, it is essential to **not** close the `ClientSession` before all data is loaded. Otherwise, lazy fetch fails.
|
||||
|
||||
The reactive counterpart uses the very same building blocks as the imperative one.
|
||||
|
||||
.ClientSession with `ReactiveMongoOperations`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
|
||||
.causallyConsistent(true)
|
||||
.build();
|
||||
|
||||
Publisher<ClientSession> session = client.startSession(sessionOptions); <1>
|
||||
|
||||
template.withSession(session)
|
||||
.execute(action -> {
|
||||
|
||||
Query query = query(where("name").is("Durzo Blint"));
|
||||
return action.findOne(query, Person.class)
|
||||
.flatMap(durzo -> {
|
||||
|
||||
Person azoth = new Person("Kylar Stern");
|
||||
azoth.setMaster(durzo);
|
||||
|
||||
return action.insert(azoth); <2>
|
||||
});
|
||||
}, ClientSession::close) <3>
|
||||
.subscribe();
|
||||
----
|
||||
<1> Obtain a `Publisher` for new session retrieval.
|
||||
<2> Use `ReactiveMongoOperation` methods as before. The `ClientSession` is obtained and applied automatically.
|
||||
<3> Make sure to close the `ClientSession`.
|
||||
====
|
||||
|
||||
By using a `Publisher` providing the actual session you can defer session acquisition to the point of actual subscription.
|
||||
Still you need to close the session when done in order to not pollute the server with stale sessions. Use the `doFinally` hook on `execute` to call `ClientSession#close()` when you don't need the session any more.
|
||||
In case you prefer having more control over the session itself, you can always obtain the `ClientSession` via the driver and provide it via a `Supplier`.
|
||||
Reference in New Issue
Block a user