diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java
index 2d6a0d49d..17b0f7d92 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java
@@ -19,6 +19,7 @@ package org.springframework.data.mongodb;
import reactor.core.publisher.Mono;
import org.bson.codecs.configuration.CodecRegistry;
+
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.core.MongoExceptionTranslator;
@@ -88,4 +89,16 @@ public interface ReactiveMongoDatabaseFactory extends CodecRegistryProvider {
* @since 2.1
*/
ReactiveMongoDatabaseFactory withSession(ClientSession session);
+
+ /**
+ * Returns if the given {@link ReactiveMongoDatabaseFactory} is bound to a
+ * {@link com.mongodb.reactivestreams.client.ClientSession} that has an
+ * {@link com.mongodb.reactivestreams.client.ClientSession#hasActiveTransaction() active transaction}.
+ *
+ * @return {@literal true} if there's an active transaction, {@literal false} otherwise.
+ * @since 2.2
+ */
+ default boolean isTransactionActive() {
+ return false;
+ }
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java
new file mode 100644
index 000000000..60035bde5
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb;
+
+import reactor.core.publisher.Mono;
+import reactor.util.context.Context;
+
+import org.springframework.lang.Nullable;
+import org.springframework.transaction.NoTransactionException;
+import org.springframework.transaction.reactive.ReactiveResourceSynchronization;
+import org.springframework.transaction.reactive.TransactionSynchronization;
+import org.springframework.transaction.reactive.TransactionSynchronizationManager;
+import org.springframework.transaction.support.ResourceHolderSynchronization;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import com.mongodb.ClientSessionOptions;
+import com.mongodb.reactivestreams.client.ClientSession;
+import com.mongodb.reactivestreams.client.MongoCollection;
+import com.mongodb.reactivestreams.client.MongoDatabase;
+
+/**
+ * Helper class for managing a {@link MongoDatabase} instances via {@link ReactiveMongoDatabaseFactory}. Used for
+ * obtaining {@link ClientSession session bound} resources, such as {@link MongoDatabase} and {@link MongoCollection}
+ * suitable for transactional usage.
+ *
+ * Note: Intended for internal usage only.
+ *
+ * @author Mark Paluch
+ * @since 2.2
+ */
+public class ReactiveMongoDatabaseUtils {
+
+ /**
+ * Check if the {@link ReactiveMongoDatabaseFactory} is actually bound to a
+ * {@link com.mongodb.reactivestreams.client.ClientSession} that has an active transaction, or if a
+ * {@link org.springframework.transaction.reactive.TransactionSynchronization} has been registered for the
+ * {@link ReactiveMongoDatabaseFactory resource} and if the associated
+ * {@link com.mongodb.reactivestreams.client.ClientSession} has an
+ * {@link com.mongodb.reactivestreams.client.ClientSession#hasActiveTransaction() active transaction}.
+ *
+ * @param databaseFactory the resource to check transactions for. Must not be {@literal null}.
+ * @return {@literal true} if the factory has an ongoing transaction.
+ */
+ public static Mono isTransactionActive(ReactiveMongoDatabaseFactory databaseFactory) {
+
+ if (databaseFactory.isTransactionActive()) {
+ return Mono.just(true);
+ }
+
+ return TransactionSynchronizationManager.currentTransaction().map(it -> {
+
+ ReactiveMongoResourceHolder holder = (ReactiveMongoResourceHolder) it.getResource(databaseFactory);
+ return holder != null && holder.hasActiveTransaction();
+ }).onErrorResume(NoTransactionException.class, e -> Mono.just(false));
+ }
+
+ /**
+ * Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory} using
+ * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
+ *
+ * Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
+ * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
+ *
+ * @param factory the {@link ReactiveMongoDatabaseFactory} to get the {@link MongoDatabase} from.
+ * @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
+ */
+ public static Mono getDatabase(ReactiveMongoDatabaseFactory factory) {
+ return doGetMongoDatabase(null, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
+ }
+
+ /**
+ * Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory}.
+ *
+ * Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
+ * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
+ *
+ * @param factory the {@link ReactiveMongoDatabaseFactory} to get the {@link MongoDatabase} from.
+ * @param sessionSynchronization the synchronization to use. Must not be {@literal null}.
+ * @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
+ */
+ public static Mono getDatabase(ReactiveMongoDatabaseFactory factory,
+ SessionSynchronization sessionSynchronization) {
+ return doGetMongoDatabase(null, factory, sessionSynchronization);
+ }
+
+ /**
+ * Obtain the {@link MongoDatabase database} with given name form the given {@link ReactiveMongoDatabaseFactory
+ * factory} using {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
+ *
+ * Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
+ * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
+ *
+ * @param dbName the name of the {@link MongoDatabase} to get.
+ * @param factory the {@link ReactiveMongoDatabaseFactory} to get the {@link MongoDatabase} from.
+ * @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
+ */
+ public static Mono getDatabase(String dbName, ReactiveMongoDatabaseFactory factory) {
+ return doGetMongoDatabase(dbName, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
+ }
+
+ /**
+ * Obtain the {@link MongoDatabase database} with given name form the given {@link ReactiveMongoDatabaseFactory
+ * factory}.
+ *
+ * Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
+ * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
+ *
+ * @param dbName the name of the {@link MongoDatabase} to get.
+ * @param factory the {@link ReactiveMongoDatabaseFactory} to get the {@link MongoDatabase} from.
+ * @param sessionSynchronization the synchronization to use. Must not be {@literal null}.
+ * @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
+ */
+ public static Mono getDatabase(String dbName, ReactiveMongoDatabaseFactory factory,
+ SessionSynchronization sessionSynchronization) {
+ return doGetMongoDatabase(dbName, factory, sessionSynchronization);
+ }
+
+ private static Mono doGetMongoDatabase(@Nullable String dbName, ReactiveMongoDatabaseFactory factory,
+ SessionSynchronization sessionSynchronization) {
+
+ Assert.notNull(factory, "Factory must not be null!");
+
+ return TransactionSynchronizationManager.currentTransaction()
+ .filter(TransactionSynchronizationManager::isSynchronizationActive).flatMap(synchronizationManager -> {
+
+ Mono session = doGetSession(synchronizationManager, factory, sessionSynchronization);
+
+ return session.map(it -> {
+
+ ReactiveMongoDatabaseFactory factoryToUse = factory.withSession(it);
+ return StringUtils.hasText(dbName) ? factoryToUse.getMongoDatabase(dbName)
+ : factoryToUse.getMongoDatabase();
+ });
+
+ }).onErrorResume(NoTransactionException.class, e -> Mono.fromSupplier(() -> {
+ return StringUtils.hasText(dbName) ? factory.getMongoDatabase(dbName) : factory.getMongoDatabase();
+ }));
+ }
+
+ private static Mono doGetSession(TransactionSynchronizationManager synchronizationManager,
+ ReactiveMongoDatabaseFactory dbFactory, SessionSynchronization sessionSynchronization) {
+
+ final ReactiveMongoResourceHolder registeredHolder = (ReactiveMongoResourceHolder) synchronizationManager
+ .getResource(dbFactory);
+
+ // check for native MongoDB transaction
+ if (registeredHolder != null
+ && (registeredHolder.hasSession() || registeredHolder.isSynchronizedWithTransaction())) {
+
+ return createClientSession(dbFactory).map(session -> {
+
+ if (!registeredHolder.hasSession()) {
+ registeredHolder.setSession(session);
+ }
+
+ return registeredHolder.getSession();
+ });
+ }
+
+ if (SessionSynchronization.ON_ACTUAL_TRANSACTION.equals(sessionSynchronization)) {
+ return Mono.empty();
+ }
+
+ // init a non native MongoDB transaction by registering a MongoSessionSynchronization
+ return createClientSession(dbFactory).map(session -> {
+
+ ReactiveMongoResourceHolder newHolder = new ReactiveMongoResourceHolder(session, dbFactory);
+ newHolder.getRequiredSession().startTransaction();
+
+ synchronizationManager
+ .registerSynchronization(new MongoSessionSynchronization(synchronizationManager, newHolder, dbFactory));
+ newHolder.setSynchronizedWithTransaction(true);
+ synchronizationManager.bindResource(dbFactory, newHolder);
+
+ return newHolder.getSession();
+ });
+ }
+
+ private static Mono createClientSession(ReactiveMongoDatabaseFactory dbFactory) {
+ return dbFactory.getSession(ClientSessionOptions.builder().causallyConsistent(true).build());
+ }
+
+ /**
+ * MongoDB specific {@link ResourceHolderSynchronization} for resource cleanup at the end of a transaction when
+ * participating in a non-native MongoDB transaction, such as a R2CBC transaction.
+ *
+ * @author Mark Paluch
+ * @since 2.2
+ */
+ private static class MongoSessionSynchronization
+ extends ReactiveResourceSynchronization {
+
+ private final ReactiveMongoResourceHolder resourceHolder;
+
+ MongoSessionSynchronization(TransactionSynchronizationManager synchronizationManager,
+ ReactiveMongoResourceHolder resourceHolder, ReactiveMongoDatabaseFactory dbFactory) {
+
+ super(resourceHolder, dbFactory, synchronizationManager);
+ this.resourceHolder = resourceHolder;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.reactive.ReactiveResourceSynchronization#shouldReleaseBeforeCompletion()
+ */
+ @Override
+ protected boolean shouldReleaseBeforeCompletion() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.reactive.ReactiveResourceSynchronization#processResourceAfterCommit(java.lang.Object)
+ */
+ @Override
+ protected Mono processResourceAfterCommit(ReactiveMongoResourceHolder resourceHolder) {
+
+ if (isTransactionActive(resourceHolder)) {
+ return Mono.from(resourceHolder.getRequiredSession().commitTransaction());
+ }
+
+ return Mono.empty();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.reactive.ReactiveResourceSynchronization#afterCompletion(int)
+ */
+ @Override
+ public Mono afterCompletion(int status) {
+
+ return Mono.defer(() -> {
+
+ if (status == TransactionSynchronization.STATUS_ROLLED_BACK && isTransactionActive(this.resourceHolder)) {
+ return Mono.from(resourceHolder.getRequiredSession().abortTransaction()).then(super.afterCompletion(status));
+ }
+
+ return super.afterCompletion(status);
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.reactive.ReactiveResourceSynchronization#releaseResource(java.lang.Object, java.lang.Object)
+ */
+ @Override
+ protected Mono releaseResource(ReactiveMongoResourceHolder resourceHolder, Object resourceKey) {
+
+ return Mono.fromRunnable(() -> {
+ if (resourceHolder.hasActiveSession()) {
+ resourceHolder.getRequiredSession().close();
+ }
+ });
+ }
+
+ private boolean isTransactionActive(ReactiveMongoResourceHolder resourceHolder) {
+
+ if (!resourceHolder.hasSession()) {
+ return false;
+ }
+
+ return resourceHolder.getRequiredSession().hasActiveTransaction();
+ }
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java
new file mode 100644
index 000000000..49bc77570
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb;
+
+import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
+import org.springframework.lang.Nullable;
+import org.springframework.transaction.support.ResourceHolderSupport;
+
+import com.mongodb.reactivestreams.client.ClientSession;
+
+/**
+ * MongoDB specific resource holder, wrapping a {@link ClientSession}. {@link MongoTransactionManager} binds instances
+ * of this class to the subscriber context.
+ *
+ * Note: Intended for internal usage only.
+ *
+ * @author Mark Paluch
+ * @author Christoph Strobl
+ * @since 2.2
+ * @see ReactiveMongoTransactionManager
+ * @see ReactiveMongoTemplate
+ */
+class ReactiveMongoResourceHolder extends ResourceHolderSupport {
+
+ private @Nullable ClientSession session;
+ private ReactiveMongoDatabaseFactory databaseFactory;
+
+ /**
+ * Create a new {@link ReactiveMongoResourceHolder} for a given {@link ClientSession session}.
+ *
+ * @param session the associated {@link ClientSession}. Can be {@literal null}.
+ * @param databaseFactory the associated {@link MongoDbFactory}. must not be {@literal null}.
+ */
+ ReactiveMongoResourceHolder(@Nullable ClientSession session, ReactiveMongoDatabaseFactory databaseFactory) {
+
+ this.session = session;
+ this.databaseFactory = databaseFactory;
+ }
+
+ /**
+ * @return the associated {@link ClientSession}. Can be {@literal null}.
+ */
+ @Nullable
+ ClientSession getSession() {
+ return session;
+ }
+
+ /**
+ * @return the required associated {@link ClientSession}.
+ * @throws IllegalStateException if no session is associated.
+ */
+ ClientSession getRequiredSession() {
+
+ ClientSession session = getSession();
+
+ if (session == null) {
+ throw new IllegalStateException("No ClientSession associated");
+ }
+ return session;
+ }
+
+ /**
+ * @return the associated {@link ReactiveMongoDatabaseFactory}.
+ */
+ public ReactiveMongoDatabaseFactory getDatabaseFactory() {
+ return databaseFactory;
+ }
+
+ /**
+ * Set the {@link ClientSession} to guard.
+ *
+ * @param session can be {@literal null}.
+ */
+ public void setSession(@Nullable ClientSession session) {
+ this.session = session;
+ }
+
+ /**
+ * @return {@literal true} if session is not {@literal null}.
+ */
+ boolean hasSession() {
+ return session != null;
+ }
+
+ /**
+ * @return {@literal true} if the session is active and has not been closed.
+ */
+ boolean hasActiveSession() {
+
+ if (!hasSession()) {
+ return false;
+ }
+
+ return hasServerSession() && !getRequiredSession().getServerSession().isClosed();
+ }
+
+ /**
+ * @return {@literal true} if the session has an active transaction.
+ * @see #hasActiveSession()
+ */
+ boolean hasActiveTransaction() {
+
+ if (!hasActiveSession()) {
+ return false;
+ }
+
+ return getRequiredSession().hasActiveTransaction();
+ }
+
+ /**
+ * @return {@literal true} if the {@link ClientSession} has a {@link com.mongodb.session.ServerSession} associated
+ * that is accessible via {@link ClientSession#getServerSession()}.
+ */
+ boolean hasServerSession() {
+
+ try {
+ return getRequiredSession().getServerSession() != null;
+ } catch (IllegalStateException serverSessionClosed) {
+ // ignore
+ }
+
+ return false;
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java
new file mode 100644
index 000000000..3da00bb48
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.lang.Nullable;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionSystemException;
+import org.springframework.transaction.reactive.AbstractReactiveTransactionManager;
+import org.springframework.transaction.reactive.GenericReactiveTransaction;
+import org.springframework.transaction.reactive.TransactionSynchronizationManager;
+import org.springframework.transaction.support.SmartTransactionObject;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+import com.mongodb.ClientSessionOptions;
+import com.mongodb.MongoException;
+import com.mongodb.TransactionOptions;
+import com.mongodb.reactivestreams.client.ClientSession;
+
+/**
+ * A {@link org.springframework.transaction.ReactiveTransactionManager} implementation that manages
+ * {@link com.mongodb.reactivestreams.client.ClientSession} based transactions for a single
+ * {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory}.
+ *
+ * Binds a {@link ClientSession} from the specified
+ * {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory} to the subscriber
+ * {@link reactor.util.context.Context}.
+ *
+ * {@link org.springframework.transaction.TransactionDefinition#isReadOnly() Readonly} transactions operate on a
+ * {@link ClientSession} and enable causal consistency, and also {@link ClientSession#startTransaction() start},
+ * {@link com.mongodb.reactivestreams.client.ClientSession#commitTransaction() commit} or
+ * {@link ClientSession#abortTransaction() abort} a transaction.
+ *
+ * Application code is required to retrieve the {@link com.mongodb.reactivestreams.client.MongoDatabase} via
+ * {@link org.springframework.data.mongodb.ReactiveMongoDatabaseUtils#getDatabase(ReactiveMongoDatabaseFactory)} instead
+ * of a standard {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#getMongoDatabase()} call. Spring
+ * classes such as {@link org.springframework.data.mongodb.core.ReactiveMongoTemplate} use this strategy implicitly.
+ *
+ * By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. You can override
+ * {@link #doCommit(TransactionSynchronizationManager, ReactiveMongoTransactionObject)} to implement the
+ * Retry Commit Operation
+ * behavior as outlined in the MongoDB reference manual.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 2.2
+ * @see MongoDB Transaction Documentation
+ * @see ReactiveMongoDatabaseUtils#getDatabase(ReactiveMongoDatabaseFactory, SessionSynchronization)
+ */
+public class ReactiveMongoTransactionManager extends AbstractReactiveTransactionManager implements InitializingBean {
+
+ private @Nullable ReactiveMongoDatabaseFactory databaseFactory;
+ private @Nullable TransactionOptions options;
+
+ /**
+ * Create a new {@link ReactiveMongoTransactionManager} for bean-style usage.
+ *
+ * Note:The {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory db factory} has to
+ * be {@link #setDatabaseFactory(ReactiveMongoDatabaseFactory)} set} before using the instance. Use this constructor
+ * to prepare a {@link ReactiveMongoTransactionManager} via a {@link org.springframework.beans.factory.BeanFactory}.
+ *
+ * Optionally it is possible to set default {@link TransactionOptions transaction options} defining
+ * {@link com.mongodb.ReadConcern} and {@link com.mongodb.WriteConcern}.
+ *
+ * @see #setDatabaseFactory(ReactiveMongoDatabaseFactory)
+ */
+ public ReactiveMongoTransactionManager() {}
+
+ /**
+ * Create a new {@link ReactiveMongoTransactionManager} obtaining sessions from the given
+ * {@link ReactiveMongoDatabaseFactory}.
+ *
+ * @param databaseFactory must not be {@literal null}.
+ */
+ public ReactiveMongoTransactionManager(ReactiveMongoDatabaseFactory databaseFactory) {
+ this(databaseFactory, null);
+ }
+
+ /**
+ * Create a new {@link ReactiveMongoTransactionManager} obtaining sessions from the given
+ * {@link ReactiveMongoDatabaseFactory} applying the given {@link TransactionOptions options}, if present, when
+ * starting a new transaction.
+ *
+ * @param databaseFactory must not be {@literal null}.
+ * @param options can be {@literal null}.
+ */
+ public ReactiveMongoTransactionManager(ReactiveMongoDatabaseFactory databaseFactory,
+ @Nullable TransactionOptions options) {
+
+ Assert.notNull(databaseFactory, "DbFactory must not be null!");
+
+ this.databaseFactory = databaseFactory;
+ this.options = options;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doGetTransaction(org.springframework.transaction.reactive.TransactionSynchronizationManager)
+ */
+ @Override
+ protected Object doGetTransaction(TransactionSynchronizationManager synchronizationManager)
+ throws TransactionException {
+
+ ReactiveMongoResourceHolder resourceHolder = (ReactiveMongoResourceHolder) synchronizationManager
+ .getResource(getRequiredDatabaseFactory());
+ return new ReactiveMongoTransactionObject(resourceHolder);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#isExistingTransaction(java.lang.Object)
+ */
+ @Override
+ protected boolean isExistingTransaction(Object transaction) throws TransactionException {
+ return extractMongoTransaction(transaction).hasResourceHolder();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doBegin(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object, org.springframework.transaction.TransactionDefinition)
+ */
+ @Override
+ protected Mono doBegin(TransactionSynchronizationManager synchronizationManager, Object transaction,
+ TransactionDefinition definition) throws TransactionException {
+
+ return Mono.defer(() -> {
+
+ ReactiveMongoTransactionObject mongoTransactionObject = extractMongoTransaction(transaction);
+
+ Mono holder = newResourceHolder(definition,
+ ClientSessionOptions.builder().causallyConsistent(true).build());
+
+ return holder.doOnNext(resourceHolder -> {
+
+ mongoTransactionObject.setResourceHolder(resourceHolder);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(
+ String.format("About to start transaction for session %s.", debugString(resourceHolder.getSession())));
+ }
+
+ }).doOnNext(resourceHolder -> {
+
+ mongoTransactionObject.startTransaction(options);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Started transaction for session %s.", debugString(resourceHolder.getSession())));
+ }
+
+ }).onErrorMap(
+ ex -> new TransactionSystemException(String.format("Could not start Mongo transaction for session %s.",
+ debugString(mongoTransactionObject.getSession())), ex))
+ .doOnSuccess(resourceHolder -> {
+
+ synchronizationManager.bindResource(getRequiredDatabaseFactory(), resourceHolder);
+ }).then();
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doSuspend(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object)
+ */
+ @Override
+ protected Mono