DATAMONGO-1920 - Polishing.

Slightly tweak method names. Document MongoDatabaseUtils usage in the context of MongoTransactionManager. Rename SessionSynchronization constants to align with AbstractPlatformTransactionManager. Slightly tweak Javadoc and reference docs for typos.

Original pull request: #554.
This commit is contained in:
Mark Paluch
2018-05-04 12:21:15 +02:00
parent 4cd2935087
commit 0d823df7f3
10 changed files with 125 additions and 95 deletions

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.data.mongodb;
import com.mongodb.ReadPreference;
import com.mongodb.TransactionOptions;
import com.mongodb.WriteConcern;
import org.springframework.lang.Nullable;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronization;
@@ -37,6 +34,7 @@ import com.mongodb.client.MongoDatabase;
* <strong>Note:</strong> Intended for internal usage only.
*
* @author Christoph Strobl
* @author Mark Paluch
* @currentRead Shadow's Edge - Brent Weeks
* @since 2.1
*/
@@ -44,27 +42,27 @@ public class MongoDatabaseUtils {
/**
* Obtain the default {@link MongoDatabase database} form the given {@link MongoDbFactory factory} using
* {@link SessionSynchronization#NATIVE native session synchronization}.
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
* <p />
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() snychronization is active}.
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
* @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
* @return must not be {@literal null}.
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
*/
public static MongoDatabase getDatabase(MongoDbFactory factory) {
return doGetMongoDatabase(null, factory, SessionSynchronization.NATIVE);
return doGetMongoDatabase(null, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
}
/**
* Obtain the default {@link MongoDatabase database} form the given {@link MongoDbFactory factory}.
* <p />
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() snychronization is active}.
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
* @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
* @param sessionSynchronization the synchronization to use. Must not be {@literal null}.
* @return must not be {@literal null}.
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
*/
public static MongoDatabase getDatabase(MongoDbFactory factory, SessionSynchronization sessionSynchronization) {
return doGetMongoDatabase(null, factory, sessionSynchronization);
@@ -72,29 +70,29 @@ public class MongoDatabaseUtils {
/**
* Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDbFactory factory} using
* {@link SessionSynchronization#NATIVE native session synchronization}.
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
* <p />
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() snychronization is active}.
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
* @param dbName the name of the {@link MongoDatabase} to get.
* @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
* @return must not be {@literal null}.
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
*/
public static MongoDatabase getDatabase(String dbName, MongoDbFactory factory) {
return doGetMongoDatabase(dbName, factory, SessionSynchronization.NATIVE);
return doGetMongoDatabase(dbName, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
}
/**
* Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDbFactory factory}.
* <p />
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() snychronization is active}.
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
* @param dbName the name of the {@link MongoDatabase} to get.
* @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
* @param sessionSynchronization the synchronization to use. Must not be {@literal null}.
* @return must not be {@literal null}.
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
*/
public static MongoDatabase getDatabase(String dbName, MongoDbFactory factory,
SessionSynchronization sessionSynchronization) {
@@ -128,21 +126,20 @@ public class MongoDatabaseUtils {
// check for native MongoDB transaction
if (resourceHolder != null && (resourceHolder.hasSession() || resourceHolder.isSynchronizedWithTransaction())) {
resourceHolder.requested();
if (!resourceHolder.hasSession()) {
resourceHolder.setSession(createClientSession(dbFactory));
}
return resourceHolder.getSession();
}
if (SessionSynchronization.NATIVE.equals(sessionSynchronization)) {
if (SessionSynchronization.ON_ACTUAL_TRANSACTION.equals(sessionSynchronization)) {
return null;
}
// init a non native MongoDB transaction by registering a MongoSessionSynchronization
resourceHolder = new MongoResourceHolder(createClientSession(dbFactory), dbFactory);
resourceHolder.requested();
resourceHolder.getSession().startTransaction();
TransactionSynchronizationManager

View File

@@ -43,11 +43,17 @@ import com.mongodb.client.ClientSession;
* {@link TransactionDefinition#isReadOnly() Readonly} transactions operate on a {@link ClientSession} and enable causal
* consistency, and also {@link ClientSession#startTransaction() start}, {@link ClientSession#commitTransaction()
* commit} or {@link ClientSession#abortTransaction() abort} a transaction.
*
* <p />
* Application code is required to retrieve the {@link com.mongodb.client.MongoDatabase} via
* {@link MongoDatabaseUtils#getDatabase(MongoDbFactory)} instead of a standard {@link MongoDbFactory#getDb()} call.
* Spring classes such as {@link org.springframework.data.mongodb.core.MongoTemplate} use this strategy implicitly.
*
* @author Christoph Strobl
* @author Mark Paluch
* @currentRead Shadow's Edge - Brent Weeks
* @since 2.1
* @see <a href="https://www.mongodb.com/transactions">MongoDB Transaction Documentation</a>
* @see MongoDatabaseUtils#getDatabase(MongoDbFactory, SessionSynchronization)
*/
public class MongoTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
@@ -62,7 +68,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
* before using the instance. Use this constructor to prepare a {@link MongoTransactionManager} via a
* {@link org.springframework.beans.factory.BeanFactory}.
* <p />
* Optionally it is possible to set default {@link TransactionOptions transaction options} defining eg.
* Optionally it is possible to set default {@link TransactionOptions transaction options} defining
* {@link com.mongodb.ReadConcern} and {@link com.mongodb.WriteConcern}.
*
* @see #setDbFactory(MongoDbFactory)
@@ -145,7 +151,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
}
resourceHolder.setSynchronizedWithTransaction(true);
TransactionSynchronizationManager.bindResource(dbFactory, resourceHolder);
TransactionSynchronizationManager.bindResource(getRequiredDbFactory(), resourceHolder);
}
/*
@@ -296,7 +302,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
public void afterPropertiesSet() {
getRequiredDbFactory();
}
@@ -345,25 +351,25 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
return "null";
}
String debugString = "[" + ClassUtils.getShortName(session.getClass()) + "@"
+ Integer.toHexString(session.hashCode()) + " ";
String debugString = String.format("[%s@%s ", ClassUtils.getShortName(session.getClass()),
Integer.toHexString(session.hashCode()));
try {
if (session.getServerSession() != null) {
debugString += "id = " + session.getServerSession().getIdentifier() + ", ";
debugString += "causallyConsistent = " + session.isCausallyConsistent() + ", ";
debugString += "txActive = " + session.hasActiveTransaction() + ", ";
debugString += "txNumber = " + session.getServerSession().getTransactionNumber() + ", ";
debugString += "statementId = " + session.getServerSession().getStatementId() + ", ";
debugString += "clusterTime = " + session.getClusterTime();
debugString += String.format("id = %s, ", session.getServerSession().getIdentifier());
debugString += String.format("causallyConsistent = %s, ", session.isCausallyConsistent());
debugString += String.format("txActive = %s, ", session.hasActiveTransaction());
debugString += String.format("txNumber = %d, ", session.getServerSession().getTransactionNumber());
debugString += String.format("statementId = %d, ", session.getServerSession().getStatementId());
debugString += String.format("clusterTime = %s", session.getClusterTime());
} else {
debugString += "id = n/a";
debugString += "causallyConsistent = " + session.isCausallyConsistent() + ", ";
debugString += "txActive = " + session.hasActiveTransaction() + ", ";
debugString += "clusterTime = " + session.getClusterTime();
debugString += String.format("causallyConsistent = %s, ", session.isCausallyConsistent());
debugString += String.format("txActive = %s, ", session.hasActiveTransaction());
debugString += String.format("clusterTime = %s", session.getClusterTime());
}
} catch (RuntimeException e) {
debugString += "error = " + e.getMessage();
debugString += String.format("error = %s", e.getMessage());
}
debugString += "]";
@@ -376,6 +382,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
* {@link MongoTransactionManager}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.1
* @see MongoResourceHolder
*/
@@ -387,22 +394,27 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
this.resourceHolder = resourceHolder;
}
/**
* Set the {@link MongoResourceHolder}.
*
* @param resourceHolder can be {@literal null}.
*/
void setResourceHolder(@Nullable MongoResourceHolder resourceHolder) {
this.resourceHolder = resourceHolder;
}
/**
* @return {@literal true} if a {@link MongoResourceHolder} is set.
*/
boolean hasResourceHolder() {
return resourceHolder != null;
}
void commitTransaction() {
getRequiredSession().commitTransaction();
}
void abortTransaction() {
getRequiredSession().abortTransaction();
}
/**
* Start a MongoDB transaction optionally given {@link TransactionOptions}.
*
* @param options can be {@literal null}
*/
void startTransaction(@Nullable TransactionOptions options) {
ClientSession session = getRequiredSession();
@@ -413,6 +425,23 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
}
}
/**
* Commit the transaction.
*/
void commitTransaction() {
getRequiredSession().commitTransaction();
}
/**
* Rollback (abort) the transaction.
*/
void abortTransaction() {
getRequiredSession().abortTransaction();
}
/**
* Close a {@link ClientSession} without regard to its transactional state.
*/
void closeSession() {
ClientSession session = getRequiredSession();

View File

@@ -20,18 +20,19 @@ package org.springframework.data.mongodb;
* define in which type of transactions to participate if any.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.1
*/
public enum SessionSynchronization {
/**
* Synchronize with native MongoDB transactions as those initiated via {@link MongoTransactionManager}.
* Synchronize with any transaction even with empty transactions and initiate a MongoDB transaction when doing so by
* registering a MongoDB specific {@link org.springframework.transaction.support.ResourceHolderSynchronization}.
*/
NATIVE,
ALWAYS,
/**
* Synchronize with any ongoing transaction and initiate a MongoDB transaction when doing so by registering a MongoDB
* specific {@link org.springframework.transaction.support.ResourceHolderSynchronization}.
* Synchronize with native MongoDB transactions initiated via {@link MongoTransactionManager}.
*/
ANY;
ON_ACTUAL_TRANSACTION;
}

View File

@@ -207,7 +207,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private @Nullable ResourceLoader resourceLoader;
private @Nullable MongoPersistentEntityIndexCreator indexCreator;
private SessionSynchronization sessionSynchronization = SessionSynchronization.NATIVE;
private SessionSynchronization sessionSynchronization = SessionSynchronization.ON_ACTUAL_TRANSACTION;
/**
* Constructor used for a basic template configuration
@@ -580,7 +580,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
/**
* Define if {@link MongoTemplate} should participate in transactions. Default is set to
* {@link SessionSynchronization#NATIVE}.<br />
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION}.<br />
* <strong>NOTE:</strong> MongoDB transactions require at least MongoDB 4.0.
*
* @since 2.1

View File

@@ -81,7 +81,7 @@ public class MongoDatabaseUtilsUnitTests {
@Test // DATAMONGO-1920
public void shouldNotStartSessionWhenNoTransactionOngoing() {
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.NATIVE);
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
verify(dbFactory, never()).getSession(any());
verify(dbFactory, never()).withSession(any(ClientSession.class));
@@ -105,7 +105,7 @@ public class MongoDatabaseUtilsUnitTests {
assertThat(transactionStatus.isNewTransaction()).isTrue();
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isFalse();
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ANY);
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ALWAYS);
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isTrue();
}
@@ -136,7 +136,7 @@ public class MongoDatabaseUtilsUnitTests {
assertThat(transactionStatus.isNewTransaction()).isTrue();
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isFalse();
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ANY);
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ALWAYS);
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isTrue();
@@ -170,7 +170,7 @@ public class MongoDatabaseUtilsUnitTests {
assertThat(transactionStatus.isNewTransaction()).isTrue();
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isFalse();
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.NATIVE);
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isFalse();
@@ -200,7 +200,7 @@ public class MongoDatabaseUtilsUnitTests {
assertThat(transactionStatus.isNewTransaction()).isTrue();
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isTrue();
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.NATIVE);
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
transactionStatus.setRollbackOnly();
}
@@ -226,7 +226,7 @@ public class MongoDatabaseUtilsUnitTests {
assertThat(transactionStatus.isNewTransaction()).isTrue();
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isTrue();
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ANY);
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ALWAYS);
transactionStatus.setRollbackOnly();
}

View File

@@ -94,7 +94,7 @@ public class MongoTemplateTransactionTests {
@Autowired MongoTemplate template;
@Autowired MongoClient client;
List<AfterTransactionAssertion<Persistable<String>>> assertionList;
List<AfterTransactionAssertion<? extends Persistable<?>>> assertionList;
@Before
public void setUp() {
@@ -104,13 +104,12 @@ public class MongoTemplateTransactionTests {
}
@BeforeTransaction
public void xxx() {
public void beforeTransaction() {
createOrReplaceCollection(DB_NAME, COLLECTION_NAME, client);
}
@AfterTransaction
public void verifyDbState() throws InterruptedException {
public void verifyDbState() {
MongoCollection<Document> collection = client.getDatabase(DB_NAME).withReadPreference(ReadPreference.primary())
.getCollection(COLLECTION_NAME);
@@ -162,7 +161,7 @@ public class MongoTemplateTransactionTests {
private AfterTransactionAssertion assertAfterTransaction(Assassin assassin) {
AfterTransactionAssertion assertion = new AfterTransactionAssertion(assassin);
AfterTransactionAssertion<Assassin> assertion = new AfterTransactionAssertion<>(assassin);
assertionList.add(assertion);
return assertion;
}

View File

@@ -103,15 +103,15 @@ public class PersonRepositoryTransactionalTests {
List<Person> all;
List<org.springframework.data.mongodb.test.util.AfterTransactionAssertion<Persistable<String>>> assertionList;
List<AfterTransactionAssertion<? extends Persistable<?>>> assertionList;
@Before
public void setUp() throws InterruptedException {
public void setUp() {
assertionList = new CopyOnWriteArrayList<>();
}
@BeforeTransaction
public void beforeTransaction() throws InterruptedException {
public void beforeTransaction() {
createOrReplaceCollection(DB_NAME, template.getCollectionName(Person.class), client);
@@ -123,7 +123,7 @@ public class PersonRepositoryTransactionalTests {
}
@AfterTransaction
public void verifyDbState() throws InterruptedException {
public void verifyDbState() {
MongoCollection<Document> collection = client.getDatabase(DB_NAME)
.getCollection(template.getCollectionName(Person.class));
@@ -144,6 +144,7 @@ public class PersonRepositoryTransactionalTests {
public void shouldHonorCommitForDerivedQuery() {
repository.removePersonByLastnameUsingAnnotatedQuery(durzo.getLastname());
repository.removePersonByLastnameUsingAnnotatedQuery(durzo.getLastname());
assertAfterTransaction(durzo).isNotPresent();
}
@@ -171,7 +172,7 @@ public class PersonRepositoryTransactionalTests {
private AfterTransactionAssertion assertAfterTransaction(Person person) {
AfterTransactionAssertion assertion = new AfterTransactionAssertion(new Persistable() {
AfterTransactionAssertion assertion = new AfterTransactionAssertion<>(new Persistable<Object>() {
@Nullable
@Override

View File

@@ -27,14 +27,14 @@ import org.springframework.data.domain.Persistable;
public class AfterTransactionAssertion<T extends Persistable> {
private final T persistable;
private boolean presentAfterTransaction;
private boolean expectToBePresent;
public void isPresent() {
presentAfterTransaction = true;
expectToBePresent = true;
}
public void isNotPresent() {
presentAfterTransaction = false;
expectToBePresent = false;
}
public Object getId() {
@@ -42,6 +42,6 @@ public class AfterTransactionAssertion<T extends Persistable> {
}
public boolean shouldBePresent() {
return presentAfterTransaction;
return expectToBePresent;
}
}

View File

@@ -133,14 +133,14 @@ public class MongoVersionRule implements TestRule {
MongoVersion version = description.getAnnotation(MongoVersion.class);
if (version != null) {
Version tmpMinVersion = Version.parse(version.asOf());
if (!tmpMinVersion.equals(ANY) && !tmpMinVersion.equals(DEFAULT_LOW)) {
minVersion = tmpMinVersion;
Version expectedMinVersion = Version.parse(version.asOf());
if (!expectedMinVersion.equals(ANY) && !expectedMinVersion.equals(DEFAULT_LOW)) {
minVersion = expectedMinVersion;
}
Version tmpMaxVersion = Version.parse(version.until());
if (!tmpMaxVersion.equals(ANY) && !tmpMaxVersion.equals(DEFAULT_HIGH)) {
maxVersion = tmpMaxVersion;
Version expectedMaxVersion = Version.parse(version.until());
if (!expectedMaxVersion.equals(ANY) && !expectedMaxVersion.equals(DEFAULT_HIGH)) {
maxVersion = expectedMaxVersion;
}
}
}

View File

@@ -1,15 +1,17 @@
[[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`.
As of version 3.6, MongoDB supports a concept of Sessions. The use of sessions enables MongoDB's 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)`.
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 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`
Let's take a look at a simple session example:
.`ClientSession` with `MongoOperations`
====
[source,java]
----
@@ -42,7 +44,7 @@ session.close() <4>
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.
The reactive counterpart uses the very same building blocks as the imperative one:
.ClientSession with `ReactiveMongoOperations`
====
@@ -74,19 +76,20 @@ template.withSession(session)
<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.
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 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 anymore.
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`.
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.
NOTE: Unless you specify a `MongoTransactionManager` within your application context, transaction support is **DISABLED**. You may use `setSessionSynchronization(ALWAYS)` 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`.
To get full programmatic control over transactions, you may want to use the session callback on `MongoOperations`.
An example of programmatic transaction control within a `SessionCallback` is shown below:
.Programmatic transactions
====
@@ -123,16 +126,16 @@ template.withSession(session)
<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.
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 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`
.Transactions with TransactionTemplate
.Transactions with `TransactionTemplate`
====
[source,java]
----
template.setSessionSynchronization(ANY); <1>
template.setSessionSynchronization(ALWAYS); <1>
// ...
@@ -152,17 +155,17 @@ txTemplate.execute(new TransactionCallbackWithoutResult() {
};
});
----
<1> Manually enable transaction synchronization.
<1> Enable transaction synchronization during Template API configuration. Changing state of `MongoTemplate` during runtime can cause threading/visibility issues.
<2> Create the `TransactionTemplate` using the provided `PlatformTransactionManager`.
<3> Within the callback the `ClientSession` and transaction are already registered.
====
== Transactions with MongoTransactionManager
== 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.
The `MongoTransactionManager` binds a `ClientSession` to the thread. `MongoTemplate` detects those and operates on these resources which are associated with the transaction accordingly. `MongoTemplate` can also participate in other, ongoing transactions.
.Transactions with MongoTransactionManager
.Transactions with `MongoTransactionManager`
====
[source,java]
----
@@ -196,5 +199,5 @@ public class StateService {
<2> Mark methods as transactional.
====
NOTE: `@Transactional(readOnly = true)` advises the `MongoTransactionManager` to also start a transaction adding the
`ClientSession` to outgoing requests.
NOTE: `@Transactional(readOnly = true)` advises `MongoTransactionManager` also to start a transaction adding the
`ClientSession` to outgoing requests.