DATAMONGO-1880 - Polishing.
Turn instance methods into static ones where applicable. Avoid parameter type array cloning where possible. Add reference to rework stack-trace inspection in order to throw ClientSessionException. Migrate MongoPersistentEntityIndexCreatorUnitTests to AssertJ. Add tests to verify simple session proxy wrapping on subsequent MongoDbFactory.withSession(…) calls. Guard ClientSession tests with replica set rule. Remove unused code. Add non-null guards. Add missing Nullable annotations. Slightly tweak Javadoc and reference documentation. Original pull request: #536.
This commit is contained in:
@@ -95,7 +95,7 @@ public interface MongoDbFactory extends CodecRegistryProvider {
|
||||
* Obtain a {@link ClientSession} bound instance of {@link MongoDbFactory} returning {@link MongoDatabase} instances
|
||||
* that are aware and bound to the given session.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @param session must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
|
||||
@@ -83,10 +83,9 @@ public interface ReactiveMongoDatabaseFactory extends CodecRegistryProvider {
|
||||
* Obtain a {@link ClientSession} bound instance of {@link ReactiveMongoDatabaseFactory} returning
|
||||
* {@link MongoDatabase} instances that are aware and bound to the given session.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @param session must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
ReactiveMongoDatabaseFactory withSession(ClientSession session);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.mongodb.WriteConcern;
|
||||
@@ -40,6 +40,10 @@ import com.mongodb.session.ClientSession;
|
||||
* like (eg. {@link com.mongodb.reactivestreams.client.MongoCollection#withWriteConcern(WriteConcern)} and decorate them
|
||||
* if not already proxied.
|
||||
*
|
||||
* @param <D> Type of the actual Mongo Database.
|
||||
* @param <C> Type of the actual Mongo Collection.
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
@@ -47,8 +51,8 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
private static final MethodCache METHOD_CACHE = new MethodCache();
|
||||
|
||||
private final ClientSession session;
|
||||
private final BiFunction collectionDecorator;
|
||||
private final BiFunction databaseDecorator;
|
||||
private final ClientSessionOperator collectionDecorator;
|
||||
private final ClientSessionOperator databaseDecorator;
|
||||
private final Object target;
|
||||
private final Class<?> targetType;
|
||||
private final Class<?> collectionType;
|
||||
@@ -56,20 +60,27 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
|
||||
/**
|
||||
* Create a new SessionAwareMethodInterceptor for given target.
|
||||
*
|
||||
*
|
||||
* @param session the {@link ClientSession} to be used on invocation.
|
||||
* @param target the original target object.
|
||||
* @param databaseType the MongoDB database type
|
||||
* @param databaseDecorator a {@link BiFunction} used to create the proxy for an imperative / reactive
|
||||
* @param databaseDecorator a {@link ClientSessionOperator} used to create the proxy for an imperative / reactive
|
||||
* {@code MongoDatabase}.
|
||||
* @param collectionType the MongoDB collection type.
|
||||
* @param collectionCallback a {@link BiFunction} used to create the proxy for an imperative / reactive
|
||||
* @param collectionDecorator a {@link ClientSessionOperator} used to create the proxy for an imperative / reactive
|
||||
* {@code MongoCollection}.
|
||||
* @param <T>
|
||||
* @param <T> target object type.
|
||||
*/
|
||||
public <T> SessionAwareMethodInterceptor(ClientSession session, T target, Class<D> databaseType,
|
||||
BiFunction<ClientSession, D, D> databaseDecorator, Class<C> collectionType,
|
||||
BiFunction<ClientSession, C, C> collectionDecorator) {
|
||||
ClientSessionOperator<D> databaseDecorator, Class<C> collectionType,
|
||||
ClientSessionOperator<C> collectionDecorator) {
|
||||
|
||||
Assert.notNull(session, "ClientSession must not be null!");
|
||||
Assert.notNull(target, "Target must not be null!");
|
||||
Assert.notNull(databaseType, "Database type must not be null!");
|
||||
Assert.notNull(databaseDecorator, "Database ClientSessionOperator must not be null!");
|
||||
Assert.notNull(collectionType, "Collection type must not be null!");
|
||||
Assert.notNull(collectionDecorator, "Collection ClientSessionOperator must not be null!");
|
||||
|
||||
this.session = session;
|
||||
this.target = target;
|
||||
@@ -85,10 +96,11 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
* (non-Javadoc)
|
||||
* @see org.aopalliance.intercept.MethodInterceptor(org.aopalliance.intercept.MethodInvocation)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
|
||||
|
||||
if (requiresDecoration(methodInvocation)) {
|
||||
if (requiresDecoration(methodInvocation.getMethod())) {
|
||||
|
||||
Object target = methodInvocation.proceed();
|
||||
if (target instanceof Proxy) {
|
||||
@@ -98,43 +110,47 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
return decorate(target);
|
||||
}
|
||||
|
||||
if (!requiresSession(methodInvocation)) {
|
||||
if (!requiresSession(methodInvocation.getMethod())) {
|
||||
return methodInvocation.proceed();
|
||||
}
|
||||
|
||||
Optional<Method> targetMethod = METHOD_CACHE.lookup(methodInvocation.getMethod(), targetType);
|
||||
|
||||
return !targetMethod.isPresent() ? methodInvocation.proceed()
|
||||
: ReflectionUtils.invokeMethod(targetMethod.get(), target, prependSessionToArguments(methodInvocation));
|
||||
: ReflectionUtils.invokeMethod(targetMethod.get(), target,
|
||||
prependSessionToArguments(session, methodInvocation));
|
||||
}
|
||||
|
||||
private boolean requiresDecoration(MethodInvocation methodInvocation) {
|
||||
private boolean requiresDecoration(Method method) {
|
||||
|
||||
return ClassUtils.isAssignable(databaseType, methodInvocation.getMethod().getReturnType())
|
||||
|| ClassUtils.isAssignable(collectionType, methodInvocation.getMethod().getReturnType());
|
||||
return ClassUtils.isAssignable(databaseType, method.getReturnType())
|
||||
|| ClassUtils.isAssignable(collectionType, method.getReturnType());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object decorate(Object target) {
|
||||
|
||||
return ClassUtils.isAssignable(databaseType, target.getClass()) ? databaseDecorator.apply(session, target)
|
||||
: collectionDecorator.apply(session, target);
|
||||
}
|
||||
|
||||
private boolean requiresSession(MethodInvocation methodInvocation) {
|
||||
private static boolean requiresSession(Method method) {
|
||||
|
||||
if (ObjectUtils.isEmpty(methodInvocation.getMethod().getParameterTypes())
|
||||
|| !ClassUtils.isAssignable(ClientSession.class, methodInvocation.getMethod().getParameterTypes()[0])) {
|
||||
if (method.getParameterCount() == 0
|
||||
|| !ClassUtils.isAssignable(ClientSession.class, method.getParameterTypes()[0])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Object[] prependSessionToArguments(MethodInvocation invocation) {
|
||||
private static Object[] prependSessionToArguments(ClientSession session, MethodInvocation invocation) {
|
||||
|
||||
Object[] args = new Object[invocation.getArguments().length + 1];
|
||||
|
||||
args[0] = session;
|
||||
System.arraycopy(invocation.getArguments(), 0, args, 1, invocation.getArguments().length);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -148,6 +164,13 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
|
||||
private final ConcurrentReferenceHashMap<MethodClassKey, Optional<Method>> cache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
/**
|
||||
* Lookup the target {@link Method}.
|
||||
*
|
||||
* @param method
|
||||
* @param targetClass
|
||||
* @return
|
||||
*/
|
||||
Optional<Method> lookup(Method method, Class<?> targetClass) {
|
||||
|
||||
return cache.computeIfAbsent(new MethodClassKey(method, targetClass),
|
||||
@@ -165,9 +188,24 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
return ReflectionUtils.findMethod(targetType, sourceMethod.getName(), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the cache contains an entry for {@link Method} and {@link Class}.
|
||||
*
|
||||
* @param method
|
||||
* @param targetClass
|
||||
* @return
|
||||
*/
|
||||
boolean contains(Method method, Class<?> targetClass) {
|
||||
return cache.containsKey(new MethodClassKey(method, targetClass));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an operation upon two operands of the same type, producing a result of the same type as the operands
|
||||
* accepting {@link ClientSession}. This is a specialization of {@link BiFunction} for the case where the operands and
|
||||
* the result are all of the same type.
|
||||
*
|
||||
* @param <T> the type of the operands and result of the operator
|
||||
*/
|
||||
public interface ClientSessionOperator<T> extends BiFunction<ClientSession, T, T> {}
|
||||
}
|
||||
|
||||
@@ -97,12 +97,15 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
* Creates a new {@link DefaultIndexOperations}.
|
||||
*
|
||||
* @param mongoOperations must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null} or empty.
|
||||
* @param type can be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName, @Nullable Class<?> type) {
|
||||
|
||||
Assert.notNull(mongoOperations, "MongoOperations must not be null!");
|
||||
Assert.hasText(collectionName, "Collection name must not be null or empty!");
|
||||
|
||||
this.mongoOperations = mongoOperations;
|
||||
this.mapper = new QueryMapper(mongoOperations.getConverter());
|
||||
this.collectionName = collectionName;
|
||||
|
||||
@@ -132,6 +132,8 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
return new UncategorizedMongoDbException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
// may interfere with OmitStackTraceInFastThrow (enabled by default).
|
||||
// see https://jira.spring.io/browse/DATAMONGO-1905
|
||||
if (ex instanceof IllegalStateException) {
|
||||
for (StackTraceElement elm : ex.getStackTrace()) {
|
||||
if (elm.getClassName().contains("ClientSession")) {
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.Cursor;
|
||||
@@ -156,8 +157,8 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
<T> T execute(String collectionName, CollectionCallback<T> action);
|
||||
|
||||
/**
|
||||
* Obtain a session bound instance of {@link SessionScoped} binding a new {@link ClientSession} with given
|
||||
* {@literal sessionOptions} to each and every command issued against MongoDB.
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding a new {@link ClientSession}
|
||||
* with given {@literal sessionOptions} to each and every command issued against MongoDB.
|
||||
*
|
||||
* @param sessionOptions must not be {@literal null}.
|
||||
* @return new instance of {@link SessionScoped}. Never {@literal null}.
|
||||
@@ -166,17 +167,19 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
SessionScoped withSession(ClientSessionOptions sessionOptions);
|
||||
|
||||
/**
|
||||
* Obtain a session bound instance of {@link SessionScoped} binding the {@link ClientSession} provided by the given
|
||||
* {@link Supplier} to each and every command issued against MongoDB.
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding the {@link ClientSession}
|
||||
* provided by the given {@link Supplier} to each and every command issued against MongoDB.
|
||||
* <p />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle.
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use the
|
||||
* {@link SessionScoped#execute(SessionCallback, Consumer)} hook to potentially close the {@link ClientSession}.
|
||||
*
|
||||
* @param sessionProvider must not be {@literal null}.
|
||||
* @param onComplete a simple hook called when done .Must not be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
default SessionScoped withSession(Supplier<ClientSession> sessionProvider) {
|
||||
|
||||
Assert.notNull(sessionProvider, "SessionProvider must not be null!");
|
||||
|
||||
return new SessionScoped() {
|
||||
|
||||
private final Object lock = new Object();
|
||||
@@ -201,7 +204,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of MongoOperations.
|
||||
* Obtain a {@link ClientSession} bound instance of {@link MongoOperations}.
|
||||
* <p />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle.
|
||||
*
|
||||
|
||||
@@ -71,16 +71,7 @@ import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||
import org.springframework.data.mongodb.core.aggregation.Fields;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.JsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.convert.UpdateMapper;
|
||||
import org.springframework.data.mongodb.core.convert.*;
|
||||
import org.springframework.data.mongodb.core.index.IndexOperations;
|
||||
import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
|
||||
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
|
||||
@@ -141,16 +132,7 @@ import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoCursor;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.MongoIterable;
|
||||
import com.mongodb.client.model.CountOptions;
|
||||
import com.mongodb.client.model.CreateCollectionOptions;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import com.mongodb.client.model.FindOneAndDeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
import com.mongodb.client.model.ReturnDocument;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.client.model.ValidationAction;
|
||||
import com.mongodb.client.model.ValidationLevel;
|
||||
import com.mongodb.client.model.*;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import com.mongodb.session.ClientSession;
|
||||
@@ -273,7 +255,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
this.updateMapper = that.updateMapper;
|
||||
this.schemaMapper = that.schemaMapper;
|
||||
this.projectionFactory = that.projectionFactory;
|
||||
|
||||
this.mappingContext = that.mappingContext;
|
||||
}
|
||||
|
||||
@@ -522,10 +503,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
*/
|
||||
public <T> T execute(DbCallback<T> action) {
|
||||
|
||||
Assert.notNull(action, "DbCallbackmust not be null!");
|
||||
Assert.notNull(action, "DbCallback must not be null!");
|
||||
|
||||
try {
|
||||
MongoDatabase db = prepareDatabase(this.getDbInternal());
|
||||
MongoDatabase db = prepareDatabase(this.doGetDatabase());
|
||||
return action.doInDB(db);
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -552,7 +533,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(callback, "CollectionCallback must not be null!");
|
||||
|
||||
try {
|
||||
MongoCollection<Document> collection = getAndPrepareCollection(getDbInternal(), collectionName);
|
||||
MongoCollection<Document> collection = getAndPrepareCollection(doGetDatabase(), collectionName);
|
||||
return callback.doInCollection(collection);
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -565,6 +546,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
*/
|
||||
@Override
|
||||
public SessionScoped withSession(ClientSessionOptions options) {
|
||||
|
||||
Assert.notNull(options, "ClientSessionOptions must not be null!");
|
||||
|
||||
return withSession(() -> mongoDbFactory.getSession(options));
|
||||
}
|
||||
|
||||
@@ -574,6 +558,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
*/
|
||||
@Override
|
||||
public MongoTemplate withSession(ClientSession session) {
|
||||
|
||||
Assert.notNull(session, "ClientSession must not be null!");
|
||||
|
||||
return new SessionBoundMongoTemplate(session, MongoTemplate.this);
|
||||
}
|
||||
|
||||
@@ -1819,7 +1806,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
String mapFunc = replaceWithResourceIfNecessary(mapFunction);
|
||||
String reduceFunc = replaceWithResourceIfNecessary(reduceFunction);
|
||||
MongoCollection<Document> inputCollection = getAndPrepareCollection(getDbInternal(), inputCollectionName);
|
||||
MongoCollection<Document> inputCollection = getAndPrepareCollection(doGetDatabase(), inputCollectionName);
|
||||
|
||||
// MapReduceOp
|
||||
MapReduceIterable<Document> result = inputCollection.mapReduce(mapFunc, reduceFunc, Document.class);
|
||||
@@ -2252,10 +2239,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
public MongoDatabase getDb() {
|
||||
return getDbInternal();
|
||||
return doGetDatabase();
|
||||
}
|
||||
|
||||
protected MongoDatabase getDbInternal() {
|
||||
protected MongoDatabase doGetDatabase() {
|
||||
return mongoDbFactory.getDb();
|
||||
}
|
||||
|
||||
@@ -2605,7 +2592,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
try {
|
||||
T result = objectCallback
|
||||
.doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDbInternal(), collectionName)));
|
||||
.doWith(collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName)));
|
||||
return result;
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -2640,7 +2627,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
try {
|
||||
|
||||
FindIterable<Document> iterable = collectionCallback
|
||||
.doInCollection(getAndPrepareCollection(getDbInternal(), collectionName));
|
||||
.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
|
||||
if (preparer != null) {
|
||||
iterable = preparer.prepare(iterable);
|
||||
@@ -2676,7 +2663,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
try {
|
||||
FindIterable<Document> iterable = collectionCallback
|
||||
.doInCollection(getAndPrepareCollection(getDbInternal(), collectionName));
|
||||
.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
|
||||
if (preparer != null) {
|
||||
iterable = preparer.prepare(iterable);
|
||||
@@ -3437,8 +3424,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
/**
|
||||
* @param session must not be {@literal null}.
|
||||
* @param mongoDbFactory must not be {@literal null}.
|
||||
* @param mongoConverter must not be {@literal null}.
|
||||
* @param that must not be {@literal null}.
|
||||
*/
|
||||
SessionBoundMongoTemplate(ClientSession session, MongoTemplate that) {
|
||||
|
||||
|
||||
@@ -147,11 +147,12 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Flux<T> execute(String collectionName, ReactiveCollectionCallback<T> action);
|
||||
|
||||
/**
|
||||
* Obtain a session bound instance of {@link SessionScoped} binding the {@link ClientSession} provided by the given
|
||||
* {@link Supplier} to each and every command issued against MongoDB.
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding the {@link ClientSession}
|
||||
* provided by the given {@link Supplier} to each and every command issued against MongoDB.
|
||||
* <p />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use
|
||||
* {@link #withSession(Supplier, Consumer)} to provide a hook for processing the {@link ClientSession} when done.
|
||||
* {@link ReactiveSessionScoped#execute(ReactiveSessionCallback, Consumer)} to provide a hook for processing the
|
||||
* {@link ClientSession} when done.
|
||||
*
|
||||
* @param sessionProvider must not be {@literal null}.
|
||||
* @return new instance of {@link SessionScoped}. Never {@literal null}.
|
||||
@@ -165,8 +166,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a session bound instance of {@link SessionScoped} binding a new {@link ClientSession} with given
|
||||
* {@literal sessionOptions} to each and every command issued against MongoDB.
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding a new {@link ClientSession}
|
||||
* with given {@literal sessionOptions} to each and every command issued against MongoDB.
|
||||
*
|
||||
* @param sessionOptions must not be {@literal null}.
|
||||
* @return new instance of {@link SessionScoped}. Never {@literal null}.
|
||||
@@ -175,11 +176,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
ReactiveSessionScoped withSession(ClientSessionOptions sessionOptions);
|
||||
|
||||
/**
|
||||
* Obtain a session bound instance of {@link SessionScoped} binding the {@link ClientSession} provided by the given
|
||||
* {@link Supplier} to each and every command issued against MongoDB.
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding the {@link ClientSession}
|
||||
* provided by the given {@link Supplier} to each and every command issued against MongoDB.
|
||||
* <p />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use the
|
||||
* {@litera onComplete} hook to potentially close the {@link ClientSession}.
|
||||
* {@literal onComplete} hook to potentially close the {@link ClientSession}.
|
||||
*
|
||||
* @param sessionProvider must not be {@literal null}.
|
||||
* @return new instance of {@link SessionScoped}. Never {@literal null}.
|
||||
@@ -202,6 +203,15 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of {@link ReactiveMongoOperations}.
|
||||
* <p />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle.
|
||||
*
|
||||
* @param session must not be {@literal null}.
|
||||
* @return {@link ClientSession} bound instance of {@link ReactiveMongoOperations}.
|
||||
* @since 2.1
|
||||
*/
|
||||
ReactiveMongoOperations withSession(ClientSession session);
|
||||
|
||||
/**
|
||||
|
||||
@@ -105,16 +105,7 @@ import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.CursorType;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import com.mongodb.DBRef;
|
||||
import com.mongodb.Mongo;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.*;
|
||||
import com.mongodb.client.model.CountOptions;
|
||||
import com.mongodb.client.model.CreateCollectionOptions;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
@@ -430,9 +421,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
@Override
|
||||
public ReactiveSessionScoped withSession(Publisher<ClientSession> sessionProvider) {
|
||||
|
||||
return new ReactiveSessionScoped() {
|
||||
Mono<ClientSession> cachedSession = Mono.from(sessionProvider).cache();
|
||||
|
||||
private final Mono<ClientSession> cachedSession = Mono.from(sessionProvider).cache();
|
||||
return new ReactiveSessionScoped() {
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> execute(ReactiveSessionCallback<T> action, Consumer<ClientSession> doFinally) {
|
||||
@@ -474,7 +465,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
Assert.notNull(callback, "ReactiveDatabaseCallback must not be null!");
|
||||
|
||||
return Flux.defer(() -> callback.doInDB(prepareDatabase(getDbInternal()))).onErrorMap(translateException());
|
||||
return Flux.defer(() -> callback.doInDB(prepareDatabase(doGetDatabase()))).onErrorMap(translateException());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -488,7 +479,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
Assert.notNull(callback, "ReactiveDatabaseCallback must not be null!");
|
||||
|
||||
return Mono.defer(() -> Mono.from(callback.doInDB(prepareDatabase(getDbInternal()))))
|
||||
return Mono.defer(() -> Mono.from(callback.doInDB(prepareDatabase(doGetDatabase()))))
|
||||
.onErrorMap(translateException());
|
||||
}
|
||||
|
||||
@@ -505,7 +496,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Assert.notNull(callback, "ReactiveDatabaseCallback must not be null!");
|
||||
|
||||
Mono<MongoCollection<Document>> collectionPublisher = Mono
|
||||
.fromCallable(() -> getAndPrepareCollection(getDbInternal(), collectionName));
|
||||
.fromCallable(() -> getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
|
||||
return collectionPublisher.flatMapMany(callback::doInCollection).onErrorMap(translateException());
|
||||
}
|
||||
@@ -524,7 +515,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Assert.notNull(callback, "ReactiveCollectionCallback must not be null!");
|
||||
|
||||
Mono<MongoCollection<Document>> collectionPublisher = Mono
|
||||
.fromCallable(() -> getAndPrepareCollection(getDbInternal(), collectionName));
|
||||
.fromCallable(() -> getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
|
||||
return collectionPublisher.flatMap(collection -> Mono.from(callback.doInCollection(collection)))
|
||||
.onErrorMap(translateException());
|
||||
@@ -622,10 +613,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
public MongoDatabase getMongoDatabase() {
|
||||
return getDbInternal();
|
||||
return doGetDatabase();
|
||||
}
|
||||
|
||||
protected MongoDatabase getDbInternal() {
|
||||
protected MongoDatabase doGetDatabase() {
|
||||
return mongoDatabaseFactory.getMongoDatabase();
|
||||
}
|
||||
|
||||
@@ -2400,7 +2391,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Assert.notNull(action, "MongoDatabaseCallback must not be null!");
|
||||
|
||||
try {
|
||||
MongoDatabase db = this.getDbInternal();
|
||||
MongoDatabase db = this.doGetDatabase();
|
||||
return action.doInDatabase(db);
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -3003,8 +2994,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
/**
|
||||
* @param session must not be {@literal null}.
|
||||
* @param mongoDbFactory must not be {@literal null}.
|
||||
* @param mongoConverter must not be {@literal null}.
|
||||
* @param that must not be {@literal null}.
|
||||
*/
|
||||
ReactiveSessionBoundMongoTemplate(ClientSession session, ReactiveMongoTemplate that) {
|
||||
|
||||
|
||||
@@ -19,10 +19,12 @@ import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
/**
|
||||
* Callback interface for executing operations within a {@link com.mongodb.session.ClientSession} reactively.
|
||||
* Callback interface for executing operations within a {@link com.mongodb.session.ClientSession} using reactive
|
||||
* infrastructure.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
* @see com.mongodb.session.ClientSession
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveSessionCallback<T> {
|
||||
@@ -31,15 +33,15 @@ public interface ReactiveSessionCallback<T> {
|
||||
* Execute operations against a MongoDB instance via session bound {@link ReactiveMongoOperations}. The session is
|
||||
* inferred directly into the operation so that no further interaction is necessary.
|
||||
* <p />
|
||||
* Please note that only Spring Data specific abstractions like {@link ReactiveMongoOperations#find(Query, Class)} and
|
||||
* Please note that only Spring Data-specific abstractions like {@link ReactiveMongoOperations#find(Query, Class)} and
|
||||
* others are enhanced with the {@link com.mongodb.session.ClientSession}. When obtaining plain MongoDB gateway
|
||||
* objects like {@link com.mongodb.reactivestreams.client.MongoCollection} or
|
||||
* {@link om.mongodb.reactivestreams.client.MongoDatabase} via eg.
|
||||
* {@link com.mongodb.reactivestreams.client.MongoDatabase} via eg.
|
||||
* {@link ReactiveMongoOperations#getCollection(String)} we leave responsibility for
|
||||
* {@link com.mongodb.session.ClientSession} again up to the caller.
|
||||
*
|
||||
* @param operations will never be {@literal null}.
|
||||
* @return can be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Publisher<T> doInSession(ReactiveMongoOperations operations);
|
||||
}
|
||||
|
||||
@@ -26,12 +26,13 @@ import com.mongodb.session.ClientSession;
|
||||
* {@link ReactiveSessionCallback}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface ReactiveSessionScoped {
|
||||
|
||||
/**
|
||||
* Executes the given {@link ReactiveSessionCallback} within the {@link com.mongodb.session.ClientSession}
|
||||
* Executes the given {@link ReactiveSessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
@@ -45,13 +46,15 @@ public interface ReactiveSessionScoped {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link ReactiveSessionCallback} within the {@link com.mongodb.session.ClientSession}
|
||||
* Executes the given {@link ReactiveSessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
*
|
||||
* @param action callback object that specifies the MongoDB action the callback action. Must not be {@literal null}.
|
||||
* @param doFinally
|
||||
* @param doFinally callback object that accepts {@link ClientSession} after invoking {@link ReactiveSessionCallback}.
|
||||
* This {@link Consumer} is guaranteed to be notified in any case (successful and exceptional outcome of
|
||||
* {@link ReactiveSessionCallback}).
|
||||
* @param <T> return type.
|
||||
* @return a result object returned by the action. Can be {@literal null}.
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
* @see com.mongodb.session.ClientSession
|
||||
*/
|
||||
public interface SessionCallback<T> {
|
||||
|
||||
@@ -30,7 +31,7 @@ public interface SessionCallback<T> {
|
||||
* Execute operations against a MongoDB instance via session bound {@link MongoOperations}. The session is inferred
|
||||
* directly into the operation so that no further interaction is necessary.
|
||||
* <p />
|
||||
* Please note that only Spring Data specific abstractions like {@link MongoOperations#find(Query, Class)} and others
|
||||
* Please note that only Spring Data-specific abstractions like {@link MongoOperations#find(Query, Class)} and others
|
||||
* are enhanced with the {@link com.mongodb.session.ClientSession}. When obtaining plain MongoDB gateway objects like
|
||||
* {@link com.mongodb.client.MongoCollection} or {@link com.mongodb.client.MongoDatabase} via eg.
|
||||
* {@link MongoOperations#getCollection(String)} we leave responsibility for {@link com.mongodb.session.ClientSession}
|
||||
|
||||
@@ -27,12 +27,13 @@ import com.mongodb.session.ClientSession;
|
||||
* The very same bound {@link ClientSession} is used for all invocations of {@code execute} on the instance.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface SessionScoped {
|
||||
|
||||
/**
|
||||
* Executes the given {@link SessionCallback} within the {@link com.mongodb.session.ClientSession}
|
||||
* Executes the given {@link SessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
@@ -47,13 +48,15 @@ public interface SessionScoped {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link SessionCallback} within the {@link com.mongodb.session.ClientSession}
|
||||
* Executes the given {@link SessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
*
|
||||
* @param action callback object that specifies the MongoDB action the callback action. Must not be {@literal null}.
|
||||
* @param doFinally
|
||||
* @param doFinally callback object that accepts {@link ClientSession} after invoking {@link SessionCallback}. This
|
||||
* {@link Consumer} is guaranteed to be notified in any case (successful and exceptional outcome of
|
||||
* {@link SessionCallback}).
|
||||
* @param <T> return type.
|
||||
* @return a result object returned by the action. Can be {@literal null}.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
@@ -174,20 +176,15 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
|
||||
/**
|
||||
* {@link ClientSession} bound {@link MongoDbFactory} decorating the database with a
|
||||
* {@link SessionAwareMethodInterceptor}.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@Value
|
||||
static class ClientSessionBoundMongoDbFactory implements MongoDbFactory {
|
||||
|
||||
private final ClientSession session;
|
||||
private final MongoDbFactory delegate;
|
||||
|
||||
ClientSessionBoundMongoDbFactory(ClientSession session, MongoDbFactory delegate) {
|
||||
|
||||
this.session = session;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
ClientSession session;
|
||||
MongoDbFactory delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -265,7 +262,7 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
|
||||
factory.addAdvice(new SessionAwareMethodInterceptor<>(session, target, MongoDatabase.class, this::proxyDatabase,
|
||||
MongoCollection.class, this::proxyCollection));
|
||||
|
||||
return (T) factory.getProxy();
|
||||
return targetType.cast(factory.getProxy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.Value;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
@@ -162,16 +163,11 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@Value
|
||||
static class ClientSessionBoundMongoDbFactory implements ReactiveMongoDatabaseFactory {
|
||||
|
||||
private final ClientSession session;
|
||||
private final ReactiveMongoDatabaseFactory delegate;
|
||||
|
||||
ClientSessionBoundMongoDbFactory(ClientSession session, ReactiveMongoDatabaseFactory delegate) {
|
||||
|
||||
this.session = session;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
ClientSession session;
|
||||
ReactiveMongoDatabaseFactory delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -240,7 +236,7 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
|
||||
factory.addAdvice(new SessionAwareMethodInterceptor<>(session, target, MongoDatabase.class, this::proxyDatabase,
|
||||
MongoCollection.class, this::proxyCollection));
|
||||
|
||||
return (T) factory.getProxy();
|
||||
return targetType.cast(factory.getProxy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public interface DbRefResolver {
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
|
||||
Object resolveDbRef(MongoPersistentProperty property, @Nullable DBRef dbref, DbRefResolverCallback callback,
|
||||
DbRefProxyHandler proxyHandler);
|
||||
|
||||
/**
|
||||
@@ -59,7 +59,8 @@ public interface DbRefResolver {
|
||||
* @param id will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
DBRef createDbRef(org.springframework.data.mongodb.core.mapping.DBRef annotation, MongoPersistentEntity<?> entity,
|
||||
DBRef createDbRef(@Nullable org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
MongoPersistentEntity<?> entity,
|
||||
Object id);
|
||||
|
||||
/**
|
||||
|
||||
@@ -85,24 +85,12 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
this.objenesis = new ObjenesisStd(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultDbRefResolver} with the given {@link MongoDbFactory}.
|
||||
*
|
||||
* @param mongoDbFactory must not be {@literal null}.
|
||||
*/
|
||||
private DefaultDbRefResolver(DefaultDbRefResolver delegate) {
|
||||
|
||||
this.mongoDbFactory = delegate.mongoDbFactory;
|
||||
this.exceptionTranslator = delegate.exceptionTranslator;
|
||||
this.objenesis = delegate.objenesis;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#resolveDbRef(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.convert.DbRefResolverCallback)
|
||||
*/
|
||||
@Override
|
||||
public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
|
||||
public Object resolveDbRef(MongoPersistentProperty property, @Nullable DBRef dbref, DbRefResolverCallback callback,
|
||||
DbRefProxyHandler handler) {
|
||||
|
||||
Assert.notNull(property, "Property must not be null!");
|
||||
@@ -121,7 +109,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#created(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public DBRef createDbRef(org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
public DBRef createDbRef(@Nullable org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
MongoPersistentEntity<?> entity, Object id) {
|
||||
|
||||
if (annotation != null && StringUtils.hasText(annotation.db())) {
|
||||
|
||||
@@ -40,6 +40,8 @@ import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SessionAwareMethodInterceptor}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -131,7 +133,7 @@ public class SessionAwareMethodInterceptorUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void proxiesNewDbInstanceReturnedByMethdod() {
|
||||
public void proxiesNewDbInstanceReturnedByMethod() {
|
||||
|
||||
MongoDatabase otherDb = mock(MongoDatabase.class);
|
||||
when(targetDatabase.withCodecRegistry(any())).thenReturn(otherDb);
|
||||
@@ -145,7 +147,7 @@ public class SessionAwareMethodInterceptorUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void proxiesNewCollectionInstanceReturnedByMethdod() {
|
||||
public void proxiesNewCollectionInstanceReturnedByMethod() {
|
||||
|
||||
MongoCollection otherCollection = mock(MongoCollection.class);
|
||||
when(targetCollection.withCodecRegistry(any())).thenReturn(otherCollection);
|
||||
@@ -176,7 +178,7 @@ public class SessionAwareMethodInterceptorUnitTests {
|
||||
factory.addAdvice(new SessionAwareMethodInterceptor<>(session, target, MongoDatabase.class, this::proxyDatabase,
|
||||
MongoCollection.class, this::proxyCollection));
|
||||
|
||||
return (T) factory.getProxy();
|
||||
return targetType.cast(factory.getProxy());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.test.util.MongoVersionRule;
|
||||
import org.springframework.data.mongodb.test.util.ReplicaSet;
|
||||
import org.springframework.data.util.Version;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
@@ -31,10 +33,12 @@ import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ClientSessionTests {
|
||||
|
||||
public static @ClassRule MongoVersionRule REQUIRES_AT_LEAST_3_6_0 = MongoVersionRule.atLeast(Version.parse("3.6.0"));
|
||||
public static @ClassRule TestRule replSet = ReplicaSet.required();
|
||||
|
||||
MongoTemplate template;
|
||||
MongoClient client;
|
||||
|
||||
@@ -27,8 +27,10 @@ import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.test.util.MongoVersionRule;
|
||||
import org.springframework.data.mongodb.test.util.ReplicaSet;
|
||||
import org.springframework.data.util.Version;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
@@ -38,10 +40,12 @@ import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ReactiveClientSessionTests {
|
||||
|
||||
public static @ClassRule MongoVersionRule REQUIRES_AT_LEAST_3_6_0 = MongoVersionRule.atLeast(Version.parse("3.6.0"));
|
||||
public static @ClassRule TestRule replSet = ReplicaSet.required();
|
||||
|
||||
MongoClient client;
|
||||
ReactiveMongoTemplate template;
|
||||
|
||||
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.anyString;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import org.bson.Document;
|
||||
import org.bson.codecs.BsonValueCodec;
|
||||
import org.bson.codecs.configuration.CodecRegistry;
|
||||
@@ -42,7 +41,6 @@ import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate.ReactiveSessionBoundMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory.ClientSessionBoundMongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
@@ -57,13 +55,17 @@ import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.reactivestreams.client.AggregatePublisher;
|
||||
import com.mongodb.reactivestreams.client.DistinctPublisher;
|
||||
import com.mongodb.reactivestreams.client.FindPublisher;
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ReactiveSessionBoundMongoTemplate}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||
public class ReactiveSessionBoundMongoTemplateUnitTests {
|
||||
|
||||
|
||||
@@ -108,6 +108,14 @@ public class SessionBoundMongoTemplateTests {
|
||||
@Override
|
||||
protected MongoCollection<Document> prepareCollection(MongoCollection<Document> collection) {
|
||||
|
||||
injectCollectionSpy(collection);
|
||||
|
||||
return super.prepareCollection(collection);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "ConstantConditions", "unchecked" })
|
||||
private void injectCollectionSpy(MongoCollection<Document> collection) {
|
||||
|
||||
InvocationHandler handler = Proxy.getInvocationHandler(collection);
|
||||
|
||||
Advised advised = (Advised) ReflectionTestUtils.getField(handler, "advised");
|
||||
@@ -123,8 +131,6 @@ public class SessionBoundMongoTemplateTests {
|
||||
ReflectionTestUtils.setField(advice, "target", spiedCollection);
|
||||
}
|
||||
}
|
||||
|
||||
return super.prepareCollection(collection);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -294,5 +300,4 @@ public class SessionBoundMongoTemplateTests {
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ import com.mongodb.session.ClientSession;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||
public class SessionBoundMongoTemplateUnitTests {
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.test.util.ReflectionTestUtils.*;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.junit.Rule;
|
||||
@@ -28,26 +30,29 @@ import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.authentication.UserCredentials;
|
||||
import org.springframework.aop.framework.AopProxyUtils;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import com.mongodb.Mongo;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientURI;
|
||||
import com.mongodb.MongoURI;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SimpleMongoDbFactory}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SimpleMongoDbFactoryUnitTests {
|
||||
|
||||
public @Rule ExpectedException expectedException = ExpectedException.none();
|
||||
@Mock MongoClient mongo;
|
||||
@Mock ClientSession clientSession;
|
||||
@Mock MongoDatabase database;
|
||||
|
||||
@Test // DATADOC-254
|
||||
public void rejectsIllegalDatabaseNames() {
|
||||
@@ -82,6 +87,22 @@ public class SimpleMongoDbFactoryUnitTests {
|
||||
assertThat(getField(factory, "databaseName").toString(), is("myDataBase"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void cascadedWithSessionUsesRootFactory() {
|
||||
|
||||
when(mongo.getDatabase("foo")).thenReturn(database);
|
||||
|
||||
MongoDbFactory factory = new SimpleMongoDbFactory(mongo, "foo");
|
||||
MongoDbFactory wrapped = factory.withSession(clientSession).withSession(clientSession);
|
||||
|
||||
InvocationHandler invocationHandler = Proxy.getInvocationHandler(wrapped.getDb());
|
||||
|
||||
Object singletonTarget = AopProxyUtils
|
||||
.getSingletonTarget(ReflectionTestUtils.getField(invocationHandler, "advised"));
|
||||
|
||||
assertThat(singletonTarget, is(sameInstance(database)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void rejectsDatabaseName(String databaseName) {
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.aop.framework.AopProxyUtils;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SimpleReactiveMongoDatabaseFactory}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SimpleReactiveMongoDatabaseFactoryUnitTests {
|
||||
|
||||
@Mock MongoClient mongoClient;
|
||||
@Mock ClientSession clientSession;
|
||||
@Mock MongoDatabase database;
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void cascadedWithSessionUsesRootFactory() {
|
||||
|
||||
when(mongoClient.getDatabase("foo")).thenReturn(database);
|
||||
|
||||
ReactiveMongoDatabaseFactory factory = new SimpleReactiveMongoDatabaseFactory(mongoClient, "foo");
|
||||
ReactiveMongoDatabaseFactory wrapped = factory.withSession(clientSession).withSession(clientSession);
|
||||
|
||||
InvocationHandler invocationHandler = Proxy.getInvocationHandler(wrapped.getMongoDatabase());
|
||||
|
||||
Object singletonTarget = AopProxyUtils
|
||||
.getSingletonTarget(ReflectionTestUtils.getField(invocationHandler, "advised"));
|
||||
|
||||
assertThat(singletonTarget, is(sameInstance(database)));
|
||||
}
|
||||
}
|
||||
@@ -15,30 +15,23 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.index;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.hamcrest.core.IsEqual;
|
||||
import org.hamcrest.number.IsCloseTo;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mapping.context.MappingContextEvent;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.DefaultIndexOperations;
|
||||
import org.springframework.data.mongodb.core.MongoExceptionTranslator;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
@@ -83,7 +76,8 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
||||
|
||||
when(factory.getDb()).thenReturn(db);
|
||||
when(factory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator());
|
||||
when(db.getCollection(collectionCaptor.capture(), Mockito.eq(org.bson.Document.class))).thenReturn((MongoCollection) collection);
|
||||
when(db.getCollection(collectionCaptor.capture(), eq(org.bson.Document.class)))
|
||||
.thenReturn((MongoCollection) collection);
|
||||
|
||||
mongoTemplate = new MongoTemplate(factory);
|
||||
|
||||
@@ -97,11 +91,10 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
||||
|
||||
new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate);
|
||||
|
||||
assertThat(keysCaptor.getValue(), is(notNullValue()));
|
||||
assertThat(keysCaptor.getValue().keySet(), hasItem("fieldname"));
|
||||
assertThat(optionsCaptor.getValue().getName(), is("indexName"));
|
||||
assertThat(optionsCaptor.getValue().isBackground(), is(false));
|
||||
assertThat(optionsCaptor.getValue().getExpireAfter(TimeUnit.SECONDS), nullValue());
|
||||
assertThat(keysCaptor.getValue()).isNotNull().containsKey("fieldname");
|
||||
assertThat(optionsCaptor.getValue().getName()).isEqualTo("indexName");
|
||||
assertThat(optionsCaptor.getValue().isBackground()).isFalse();
|
||||
assertThat(optionsCaptor.getValue().getExpireAfter(TimeUnit.SECONDS)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -128,8 +121,8 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
||||
mappingContext.initialize();
|
||||
|
||||
MongoPersistentEntityIndexCreator creator = new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate);
|
||||
assertThat(creator.isIndexCreatorFor(mappingContext), is(true));
|
||||
assertThat(creator.isIndexCreatorFor(new MongoMappingContext()), is(false));
|
||||
assertThat(creator.isIndexCreatorFor(mappingContext)).isTrue();
|
||||
assertThat(creator.isIndexCreatorFor(new MongoMappingContext())).isFalse();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-554
|
||||
@@ -138,11 +131,10 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
||||
MongoMappingContext mappingContext = prepareMappingContext(AnotherPerson.class);
|
||||
new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate);
|
||||
|
||||
assertThat(keysCaptor.getValue(), is(notNullValue()));
|
||||
assertThat(keysCaptor.getValue().keySet(), hasItem("lastname"));
|
||||
assertThat(optionsCaptor.getValue().getName(), is("lastname"));
|
||||
assertThat(optionsCaptor.getValue().isBackground(), IsEqual.<Object> equalTo(true));
|
||||
assertThat(optionsCaptor.getValue().getExpireAfter(TimeUnit.SECONDS), nullValue());
|
||||
assertThat(keysCaptor.getValue()).isNotNull().containsKey("lastname");
|
||||
assertThat(optionsCaptor.getValue().getName()).isEqualTo("lastname");
|
||||
assertThat(optionsCaptor.getValue().isBackground()).isTrue();
|
||||
assertThat(optionsCaptor.getValue().getExpireAfter(TimeUnit.SECONDS)).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-544
|
||||
@@ -151,9 +143,8 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
||||
MongoMappingContext mappingContext = prepareMappingContext(Milk.class);
|
||||
new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate);
|
||||
|
||||
assertThat(keysCaptor.getValue(), is(notNullValue()));
|
||||
assertThat(keysCaptor.getValue().keySet(), hasItem("expiry"));
|
||||
assertThat(optionsCaptor.getValue().getExpireAfter(TimeUnit.SECONDS), IsEqual.<Object> equalTo(60L));
|
||||
assertThat(keysCaptor.getValue()).isNotNull().containsKey("expiry");
|
||||
assertThat(optionsCaptor.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(60);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-899
|
||||
@@ -162,13 +153,13 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
||||
MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class);
|
||||
new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate);
|
||||
|
||||
assertThat(keysCaptor.getValue(), equalTo(new org.bson.Document().append("company.address.location", "2d")));
|
||||
assertThat(keysCaptor.getValue()).isEqualTo(new org.bson.Document("company.address.location", "2d"));
|
||||
|
||||
IndexOptions opts = optionsCaptor.getValue();
|
||||
assertThat(opts.getName(), is(equalTo("company.address.location")));
|
||||
assertThat(opts.getMin(), IsCloseTo.closeTo(-180, 0));
|
||||
assertThat(opts.getMax(), IsCloseTo.closeTo(180, 0));
|
||||
assertThat(opts.getBits(), is(26));
|
||||
assertThat(opts.getName()).isEqualTo("company.address.location");
|
||||
assertThat(opts.getMin()).isCloseTo(-180d, offset(0d));
|
||||
assertThat(opts.getMax()).isCloseTo(180d, offset(0d));
|
||||
assertThat(opts.getBits()).isEqualTo(26);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-827
|
||||
@@ -177,10 +168,8 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
||||
MongoMappingContext mappingContext = prepareMappingContext(EntityWithGeneratedIndexName.class);
|
||||
new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate);
|
||||
|
||||
assertThat(keysCaptor.getValue().containsKey("name"), is(false));
|
||||
assertThat(keysCaptor.getValue().keySet(), hasItem("lastname"));
|
||||
|
||||
assertThat(optionsCaptor.getValue().getName(), nullValue());
|
||||
assertThat(keysCaptor.getValue()).doesNotContainKey("name").containsKey("lastname");
|
||||
assertThat(optionsCaptor.getValue().getName()).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-367
|
||||
@@ -191,8 +180,8 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
||||
|
||||
ArgumentCaptor<String> collectionNameCapturer = ArgumentCaptor.forClass(String.class);
|
||||
|
||||
verify(db, times(1)).getCollection(collectionNameCapturer.capture(), Mockito.any());
|
||||
assertThat(collectionNameCapturer.getValue(), equalTo("wrapper"));
|
||||
verify(db, times(1)).getCollection(collectionNameCapturer.capture(), any());
|
||||
assertThat(collectionNameCapturer.getValue()).isEqualTo("wrapper");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-367
|
||||
@@ -203,15 +192,15 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
||||
|
||||
ArgumentCaptor<String> collectionNameCapturer = ArgumentCaptor.forClass(String.class);
|
||||
|
||||
verify(db, times(1)).getCollection(collectionNameCapturer.capture(), Mockito.any());
|
||||
assertThat(collectionNameCapturer.getValue(), equalTo("indexedDocumentWrapper"));
|
||||
verify(db, times(1)).getCollection(collectionNameCapturer.capture(), any());
|
||||
assertThat(collectionNameCapturer.getValue()).isEqualTo("indexedDocumentWrapper");
|
||||
}
|
||||
|
||||
@Test(expected = DataAccessException.class) // DATAMONGO-1125
|
||||
public void createIndexShouldUsePersistenceExceptionTranslatorForNonDataIntegrityConcerns() {
|
||||
|
||||
doThrow(new MongoException(6, "HostUnreachable")).when(collection).createIndex(Mockito.any(org.bson.Document.class),
|
||||
Mockito.any(IndexOptions.class));
|
||||
doThrow(new MongoException(6, "HostUnreachable")).when(collection).createIndex(any(org.bson.Document.class),
|
||||
any(IndexOptions.class));
|
||||
|
||||
MongoMappingContext mappingContext = prepareMappingContext(Person.class);
|
||||
|
||||
@@ -221,8 +210,8 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
||||
@Test(expected = ClassCastException.class) // DATAMONGO-1125
|
||||
public void createIndexShouldNotConvertUnknownExceptionTypes() {
|
||||
|
||||
doThrow(new ClassCastException("o_O")).when(collection).createIndex(Mockito.any(org.bson.Document.class),
|
||||
Mockito.any(IndexOptions.class));
|
||||
doThrow(new ClassCastException("o_O")).when(collection).createIndex(any(org.bson.Document.class),
|
||||
any(IndexOptions.class));
|
||||
|
||||
MongoMappingContext mappingContext = prepareMappingContext(Person.class);
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ public class ReplicaSet implements TestRule {
|
||||
}
|
||||
|
||||
if (!runsAsReplicaSet()) {
|
||||
throw new AssumptionViolatedException("Not runnig in repl set mode");
|
||||
throw new AssumptionViolatedException("Not running in repl set mode");
|
||||
}
|
||||
base.evaluate();
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ As of version 3.6 MongoDB supports a concept of Sessions. The use of sessions en
|
||||
|
||||
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. Within the callback all operations on `MongoCollection` and `MongoDatabase` are called with the provided session via a `Proxy` without the need to add it manually. 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 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` will *NOT* be wrapped by the `Proxy`. So please make sure to provide the `ClientSession` where needed when interacting directly with a `MongoCollection` or `MongoDatabase` and not via one of the `#excute` callbacks on `MongoOperations`.
|
||||
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.
|
||||
.ClientSession with `MongoOperations`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@@ -37,14 +37,14 @@ session.close() <4>
|
||||
----
|
||||
<1> Obtain a new session from the server.
|
||||
<2> Use `MongoOperation` methods as before. The `ClientSession` gets applied automatically.
|
||||
<3> Important! Do not forget to close the session.
|
||||
<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.
|
||||
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.
|
||||
.ClientSession with `ReactiveMongoOperations`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@@ -66,14 +66,14 @@ template.withSession(session)
|
||||
|
||||
return action.insert(azoth); <2>
|
||||
});
|
||||
}, ClientSession::close) <4>
|
||||
}, ClientSession::close) <3>
|
||||
.subscribe();
|
||||
----
|
||||
<1> Obtain a `Publisher` for new session retrieval.
|
||||
<2> Use `MongoOperation` methods as before. The `ClientSession` is obtained and applied automatically.
|
||||
<3> Important! Do not forget to close the session.
|
||||
<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`.
|
||||
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