Browse Source

Document how to create `MongoTemplate` and `MongoTransactionManager` for default transaction participation.

Refine documentation and indicate relationship to MongoDatabaseFactory.

Closes #5019
pull/5026/head
Mark Paluch 5 months ago
parent
commit
ebb7b58c32
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 14
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionManager.java
  2. 17
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java
  3. 13
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  4. 27
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  5. 24
      src/main/antora/modules/ROOT/pages/mongodb/client-session-transactions.adoc

14
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionManager.java

@ -36,16 +36,18 @@ import com.mongodb.client.ClientSession; @@ -36,16 +36,18 @@ import com.mongodb.client.ClientSession;
/**
* A {@link org.springframework.transaction.PlatformTransactionManager} implementation that manages
* {@link ClientSession} based transactions for a single {@link MongoDatabaseFactory}. <br />
* Binds a {@link ClientSession} from the specified {@link MongoDatabaseFactory} to the thread. <br />
* {@link ClientSession} based transactions for a single {@link MongoDatabaseFactory}.
* <p>
* Binds a {@link ClientSession} from the specified {@link MongoDatabaseFactory} to the thread.
* {@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. <br />
* 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(MongoDatabaseFactory)} instead of a standard
* {@link MongoDatabaseFactory#getMongoDatabase()} call. Spring classes such as
* {@link org.springframework.data.mongodb.core.MongoTemplate} use this strategy implicitly. <br />
* By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. One may override
* {@link org.springframework.data.mongodb.core.MongoTemplate} use this strategy implicitly. By default, failure of a
* {@literal commit} operation raises a {@link TransactionSystemException}. One may override
* {@link #doCommit(MongoTransactionObject)} to implement the
* <a href="https://docs.mongodb.com/manual/core/transactions/#retry-commit-operation">Retry Commit Operation</a>
* behavior as outlined in the MongoDB reference manual.
@ -205,7 +207,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager @@ -205,7 +207,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
* By default those labels are ignored, nevertheless one might check for
* {@link MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL transient commit errors labels} and retry the the
* commit. <br />
*
*
* <pre>
* <code>
* int retries = 3;

17
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java

@ -37,20 +37,21 @@ import com.mongodb.reactivestreams.client.ClientSession; @@ -37,20 +37,21 @@ 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}. <br />
* {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory}.
* <p>
* Binds a {@link ClientSession} from the specified
* {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory} to the subscriber
* {@link reactor.util.context.Context}. <br />
* {@link org.springframework.transaction.TransactionDefinition#isReadOnly() Readonly} transactions operate on a
* {@link ClientSession} and enable causal consistency, and also {@link ClientSession#startTransaction() start},
* {@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. <br />
* {@link ClientSession#abortTransaction() abort} a transaction.
* <p>
* 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.
* <br />
* By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. You can override
* 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
* <a href="https://docs.mongodb.com/manual/core/transactions/#retry-commit-operation">Retry Commit Operation</a>
* behavior as outlined in the MongoDB reference manual.

13
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

@ -48,7 +48,6 @@ import org.springframework.dao.OptimisticLockingFailureException; @@ -48,7 +48,6 @@ import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Window;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
@ -160,6 +159,9 @@ import com.mongodb.client.result.UpdateResult; @@ -160,6 +159,9 @@ import com.mongodb.client.result.UpdateResult;
* <p>
* You can also set the default {@link #setReadPreference(ReadPreference) ReadPreference} on the template level to
* generally apply a {@link ReadPreference}.
* <p>
* When using transactions make sure to create this template with the same {@link MongoDatabaseFactory} that is also
* used for {@code MongoTransactionManager} creation.
*
* @author Thomas Risberg
* @author Graeme Rocher
@ -223,6 +225,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -223,6 +225,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
/**
* Constructor used for a basic template configuration.
* <p>
* If you intend to use transactions, make sure to use {@link #MongoTemplate(MongoDatabaseFactory)} or
* {@link #MongoTemplate(MongoDatabaseFactory, MongoConverter)} constructors, otherwise, this template will not
* participate in transactions using the default {@code SessionSynchronization.ON_ACTUAL_TRANSACTION} setting as
* {@code MongoTransactionManager} uses strictly its configured {@link MongoDatabaseFactory} for transaction
* participation.
*
* @param mongoClient must not be {@literal null}.
* @param databaseName must not be {@literal null} or empty.
@ -641,7 +649,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -641,7 +649,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
/**
* Define if {@link MongoTemplate} should participate in transactions. Default is set to
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION}.<br />
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION}.
* <p>
* <strong>NOTE:</strong> MongoDB transactions require at least MongoDB 4.0.
*
* @since 2.1

27
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
*/
package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.core.query.SerializationUtils.serializeToJsonSafely;
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -110,18 +110,7 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext; @@ -110,18 +110,7 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterDeleteEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
import org.springframework.data.mongodb.core.mapping.event.BeforeDeleteEvent;
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent;
import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterConvertCallback;
import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterSaveCallback;
import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeConvertCallback;
import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeSaveCallback;
import org.springframework.data.mongodb.core.mapping.event.*;
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Collation;
@ -189,6 +178,9 @@ import com.mongodb.reactivestreams.client.MongoDatabase; @@ -189,6 +178,9 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
* <p>
* You can also set the default {@link #setReadPreference(ReadPreference) ReadPreference} on the template level to
* generally apply a {@link ReadPreference}.
* <p>
* When using transactions make sure to create this template with the same {@link ReactiveMongoDatabaseFactory} that is
* also used for {@code ReactiveMongoTransactionManager} creation.
*
* @author Mark Paluch
* @author Christoph Strobl
@ -231,6 +223,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -231,6 +223,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
/**
* Constructor used for a basic template configuration.
* <p>
* If you intend to use transactions, make sure to use {@link #ReactiveMongoTemplate(ReactiveMongoDatabaseFactory)} or
* {@link #ReactiveMongoTemplate(ReactiveMongoDatabaseFactory, MongoConverter)} constructors, otherwise, this template
* will not participate in transactions using the default {@code SessionSynchronization.ON_ACTUAL_TRANSACTION} setting
* as {@code ReactiveMongoTransactionManager} uses strictly its configured {@link ReactiveMongoDatabaseFactory} for
* transaction participation.
*
* @param mongoClient must not be {@literal null}.
* @param databaseName must not be {@literal null} or empty.
@ -573,7 +571,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -573,7 +571,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
/**
* Define if {@link ReactiveMongoTemplate} should participate in transactions. Default is set to
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION}.<br />
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION}.
* <p>
* <strong>NOTE:</strong> MongoDB transactions require at least MongoDB 4.0.
*
* @since 2.2

24
src/main/antora/modules/ROOT/pages/mongodb/client-session-transactions.adoc

@ -294,6 +294,11 @@ static class Config extends AbstractMongoClientConfiguration { @@ -294,6 +294,11 @@ static class Config extends AbstractMongoClientConfiguration {
return new MongoTransactionManager(dbFactory);
}
@Bean
MongoTemplate mongoTemplate(MongoDatabaseFactory dbFactory) { <1>
return new MongoTemplate(dbFactory);
}
// ...
}
@ -314,6 +319,7 @@ public class StateService { @@ -314,6 +319,7 @@ public class StateService {
----
<1> Register `MongoTransactionManager` in the application context.
Also, make sure to use the same `MongoDatabaseFactory` when creating `MongoTemplate` to participate in transactions in the scope of the same `MongoDatabaseFactory`.
<2> Mark methods as transactional.
NOTE: `@Transactional(readOnly = true)` advises `MongoTransactionManager` to also start a transaction that adds the
@ -333,6 +339,11 @@ public class Config extends AbstractReactiveMongoConfiguration { @@ -333,6 +339,11 @@ public class Config extends AbstractReactiveMongoConfiguration {
return new ReactiveMongoTransactionManager(factory);
}
@Bean
ReactiveMongoTemplate reactiveMongoTemplate(ReactiveMongoDatabaseFactory dbFactory) { <1>
return new ReactiveMongoTemplate(dbFactory);
}
// ...
}
@ -351,6 +362,7 @@ public class StateService { @@ -351,6 +362,7 @@ public class StateService {
----
<1> Register `ReactiveMongoTransactionManager` in the application context.
Also, make sure to use the same `ReactiveMongoDatabaseFactory` when creating `ReactiveMongoTemplate` to participate in transactions in the scope of the same `ReactiveMongoDatabaseFactory`.
<2> Mark methods as transactional.
NOTE: `@Transactional(readOnly = true)` advises `ReactiveMongoTransactionManager` to also start a transaction that adds the `ClientSession` to outgoing requests.
@ -418,20 +430,20 @@ Please refer to https://docs.mongodb.com/manual/reference/connection-string/#con @@ -418,20 +430,20 @@ Please refer to https://docs.mongodb.com/manual/reference/connection-string/#con
MongoDB does *not* support collection operations, such as collection creation, within a transaction.
This also affects the on the fly collection creation that happens on first usage.
Therefore make sure to have all required structures in place.
Therefore, make sure to have all required structures in place.
*Transient Errors*
MongoDB can add special labels to errors raised during transactional operations.
Those may indicate transient failures that might vanish by merely retrying the operation.
We highly recommend https://github.com/spring-projects/spring-retry[Spring Retry] for those purposes.
Nevertheless one may override `MongoTransactionManager#doCommit(MongoTransactionObject)` to implement a https://docs.mongodb.com/manual/core/transactions/#retry-commit-operation[Retry Commit Operation]
Nevertheless, one may override `MongoTransactionManager#doCommit(MongoTransactionObject)` to implement a https://docs.mongodb.com/manual/core/transactions/#retry-commit-operation[Retry Commit Operation]
behavior as outlined in the MongoDB reference manual.
*Count*
MongoDB `count` operates upon collection statistics which may not reflect the actual situation within a transaction.
The server responds with _error 50851_ when issuing a `count` command inside of a multi-document transaction.
The server responds with _error 50851_ when issuing a `count` command inside a multi-document transaction.
Once `MongoTemplate` detects an active transaction, all exposed `count()` methods are converted and delegated to the aggregation framework using `$match` and `$count` operators, preserving `Query` settings, such as `collation`.
Restrictions apply when using geo commands inside of the aggregation count helper.
@ -453,9 +465,9 @@ The following snippet shows `count` usage inside the session-bound closure: @@ -453,9 +465,9 @@ The following snippet shows `count` usage inside the session-bound closure:
session.startTransaction();
template.withSession(session)
.execute(action -> {
action.count(query(where("state").is("active")), Step.class)
...
.execute(ops -> {
return ops.count(query(where("state").is("active")), Step.class)
});
----
====

Loading…
Cancel
Save