DATAMONGO-2344 - Fix slaveOK query option not applied correctly.
Since MongoDB 3.6 the slaveOk option translates to the primaryPreferred ReadPreference that is now again applied when executing a find operation. Original pull request: #779.
This commit is contained in:
committed by
Mark Paluch
parent
b2e3e3fb8e
commit
9048ec83af
@@ -15,9 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import org.bson.Document;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.client.FindIterable;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
|
||||
/**
|
||||
* Simple callback interface to allow customization of a {@link FindIterable}.
|
||||
@@ -25,7 +31,14 @@ import com.mongodb.client.FindIterable;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
interface CursorPreparer {
|
||||
interface CursorPreparer extends ReadPreferenceAware {
|
||||
|
||||
/**
|
||||
* Default {@link CursorPreparer} just passing on the given {@link FindIterable}.
|
||||
*
|
||||
* @since 2.2
|
||||
*/
|
||||
CursorPreparer NO_OP_PREPARER = (iterable -> iterable);
|
||||
|
||||
/**
|
||||
* Prepare the given cursor (apply limits, skips and so on). Returns the prepared cursor.
|
||||
@@ -33,4 +46,37 @@ interface CursorPreparer {
|
||||
* @param cursor
|
||||
*/
|
||||
FindIterable<Document> prepare(FindIterable<Document> cursor);
|
||||
|
||||
/**
|
||||
* Apply query specific settings to {@link MongoCollection} and initate a find operation returning a
|
||||
* {@link FindIterable} via the given {@link Function find} function.
|
||||
*
|
||||
* @param collection must not be {@literal null}.
|
||||
* @param find must not be {@literal null}.
|
||||
* @return
|
||||
* @throws IllegalArgumentException if one of the required arguments is {@literal null}.
|
||||
* @since 2.2
|
||||
*/
|
||||
default FindIterable<Document> initiateFind(MongoCollection<Document> collection,
|
||||
Function<MongoCollection<Document>, FindIterable<Document>> find) {
|
||||
|
||||
Assert.notNull(collection, "Collection must not be null!");
|
||||
Assert.notNull(find, "Find function must not be null!");
|
||||
|
||||
if (hasReadPreferences()) {
|
||||
collection = collection.withReadPreference(getReadPreference());
|
||||
}
|
||||
|
||||
return prepare(find.apply(collection));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ReadPreference} to apply or {@literal null} if none defined.
|
||||
* @since 2.2
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
default ReadPreference getReadPreference() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import com.mongodb.ReadPreference;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -267,6 +268,11 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
|
||||
this.limit = Optional.of(limit);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadPreference getReadPreference() {
|
||||
return delegate.getReadPreference();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,14 +15,36 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.reactivestreams.client.FindPublisher;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
|
||||
/**
|
||||
* Simple callback interface to allow customization of a {@link FindPublisher}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
interface FindPublisherPreparer {
|
||||
interface FindPublisherPreparer extends ReadPreferenceAware {
|
||||
|
||||
/**
|
||||
* Default {@link FindPublisherPreparer} just passing on the given {@link FindPublisher}.
|
||||
*
|
||||
* @since 2.2
|
||||
*/
|
||||
FindPublisherPreparer NO_OP_PREPARER = new FindPublisherPreparer() {
|
||||
|
||||
@Override
|
||||
public <T> FindPublisher<T> prepare(FindPublisher<T> findPublisher) {
|
||||
return findPublisher;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare the given cursor (apply limits, skips and so on). Returns the prepared cursor.
|
||||
@@ -30,4 +52,37 @@ interface FindPublisherPreparer {
|
||||
* @param findPublisher must not be {@literal null}.
|
||||
*/
|
||||
<T> FindPublisher<T> prepare(FindPublisher<T> findPublisher);
|
||||
|
||||
/**
|
||||
* Apply query specific settings to {@link MongoCollection} and initate a find operation returning a
|
||||
* {@link FindPublisher} via the given {@link Function find} function.
|
||||
*
|
||||
* @param collection must not be {@literal null}.
|
||||
* @param find must not be {@literal null}.
|
||||
* @return
|
||||
* @throws IllegalArgumentException if one of the required arguments is {@literal null}.
|
||||
* @since 2.2
|
||||
*/
|
||||
default FindPublisher<Document> initiateFind(MongoCollection<Document> collection,
|
||||
Function<MongoCollection<Document>, FindPublisher<Document>> find) {
|
||||
|
||||
Assert.notNull(collection, "Collection must not be null!");
|
||||
Assert.notNull(find, "Find function must not be null!");
|
||||
|
||||
if (hasReadPreferences()) {
|
||||
collection = collection.withReadPreference(getReadPreference());
|
||||
}
|
||||
|
||||
return prepare(find.apply(collection));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ReadPreference} to apply or {@literal null} if none defined.
|
||||
* @since 2.2
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
default ReadPreference getReadPreference() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ import org.springframework.data.mongodb.core.mapreduce.MapReduceResults;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Meta;
|
||||
import org.springframework.data.mongodb.core.query.Meta.CursorOption;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
@@ -447,8 +448,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Document mappedFields = getMappedFieldsObject(query.getFieldsObject(), persistentEntity, returnType);
|
||||
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity);
|
||||
|
||||
FindIterable<Document> cursor = new QueryCursorPreparer(query, entityType)
|
||||
.prepare(collection.find(mappedQuery, Document.class).projection(mappedFields));
|
||||
FindIterable<Document> cursor = new QueryCursorPreparer(query, entityType).initiateFind(collection,
|
||||
col -> col.find(mappedQuery, Document.class).projection(mappedFields));
|
||||
|
||||
return new CloseableIterableCursorAdapter<>(cursor, exceptionTranslator,
|
||||
new ProjectingReadCallback<>(mongoConverter, entityType, returnType, collectionName));
|
||||
@@ -538,8 +539,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
sortObject, fieldsObject, collectionName);
|
||||
}
|
||||
|
||||
this.executeQueryInternal(new FindCallback(queryObject, fieldsObject, null), preparer, documentCallbackHandler,
|
||||
collectionName);
|
||||
this.executeQueryInternal(new FindCallback(queryObject, fieldsObject, null),
|
||||
preparer != null ? preparer : CursorPreparer.NO_OP_PREPARER, documentCallbackHandler, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -777,8 +778,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(mode, "BulkMode must not be null!");
|
||||
Assert.hasText(collectionName, "Collection name must not be null or empty!");
|
||||
|
||||
DefaultBulkOperations operations = new DefaultBulkOperations(this, collectionName, new BulkOperationContext(mode,
|
||||
Optional.ofNullable(getPersistentEntity(entityType)), queryMapper, updateMapper, eventPublisher, entityCallbacks));
|
||||
DefaultBulkOperations operations = new DefaultBulkOperations(this, collectionName,
|
||||
new BulkOperationContext(mode, Optional.ofNullable(getPersistentEntity(entityType)), queryMapper, updateMapper,
|
||||
eventPublisher, entityCallbacks));
|
||||
|
||||
operations.setExceptionTranslator(exceptionTranslator);
|
||||
operations.setDefaultWriteConcern(writeConcern);
|
||||
@@ -814,8 +816,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
if (ObjectUtils.isEmpty(query.getSortObject())) {
|
||||
|
||||
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
operations.forType(entityClass).getCollation(query).map(Collation::toMongoCollation).orElse(null),
|
||||
entityClass);
|
||||
new QueryCursorPreparer(query, entityClass), entityClass);
|
||||
} else {
|
||||
query.limit(1);
|
||||
List<T> results = find(query, entityClass, collectionName);
|
||||
@@ -933,6 +934,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
serializeToJsonSafely(mappedQuery), field, collectionName);
|
||||
}
|
||||
|
||||
QueryCursorPreparer preparer = new QueryCursorPreparer(query, entityClass);
|
||||
if (preparer.hasReadPreferences()) {
|
||||
collection = collection.withReadPreference(preparer.getReadPreference());
|
||||
}
|
||||
|
||||
DistinctIterable<T> iterable = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
|
||||
|
||||
return operations.forType(entityClass) //
|
||||
@@ -1007,8 +1013,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(collectionName, "CollectionName must not be null!");
|
||||
Assert.notNull(returnType, "ReturnType must not be null!");
|
||||
|
||||
String collection = StringUtils.hasText(collectionName) ? collectionName
|
||||
: getCollectionName(domainType);
|
||||
String collection = StringUtils.hasText(collectionName) ? collectionName : getCollectionName(domainType);
|
||||
String distanceField = operations.nearQueryDistanceFieldName(domainType);
|
||||
|
||||
Aggregation $geoNear = TypedAggregation.newAggregation(domainType, Aggregation.geoNear(near, distanceField))
|
||||
@@ -1039,8 +1044,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
|
||||
return findAndModify(query, update, new FindAndModifyOptions(), entityClass,
|
||||
getCollectionName(entityClass));
|
||||
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, getCollectionName(entityClass));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -1296,8 +1300,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
Assert.notNull(batchToSave, "BatchToSave must not be null!");
|
||||
|
||||
return (Collection<T>) doInsertBatch(getCollectionName(entityClass), batchToSave,
|
||||
this.mongoConverter);
|
||||
return (Collection<T>) doInsertBatch(getCollectionName(entityClass), batchToSave, this.mongoConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1795,7 +1798,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
return executeFindMultiInternal(
|
||||
new FindCallback(new Document(), new Document(),
|
||||
operations.forType(entityClass).getCollation().map(Collation::toMongoCollation).orElse(null)),
|
||||
null, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName), collectionName);
|
||||
CursorPreparer.NO_OP_PREPARER, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName),
|
||||
collectionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2435,7 +2439,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @return the {@link List} of converted objects.
|
||||
*/
|
||||
protected <T> T doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass) {
|
||||
return doFindOne(collectionName, query, fields, null, entityClass);
|
||||
return doFindOne(collectionName, query, fields, CursorPreparer.NO_OP_PREPARER, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2446,12 +2450,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @param query the query document that specifies the criteria used to find a record.
|
||||
* @param fields the document that specifies the fields to be returned.
|
||||
* @param entityClass the parameterized type of the returned list.
|
||||
* @param preparer the preparer used to modify the cursor on execution.
|
||||
* @return the {@link List} of converted objects.
|
||||
* @since 2.2
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
protected <T> T doFindOne(String collectionName, Document query, Document fields,
|
||||
@Nullable com.mongodb.client.model.Collation collation, Class<T> entityClass) {
|
||||
protected <T> T doFindOne(String collectionName, Document query, Document fields, CursorPreparer preparer,
|
||||
Class<T> entityClass) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
Document mappedQuery = queryMapper.getMappedObject(query, entity);
|
||||
@@ -2462,7 +2467,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
mappedFields, entityClass, collectionName);
|
||||
}
|
||||
|
||||
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, collation),
|
||||
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, preparer),
|
||||
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
@@ -2513,8 +2518,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName);
|
||||
}
|
||||
|
||||
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null), preparer, objectCallback,
|
||||
collectionName);
|
||||
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null),
|
||||
preparer != null ? preparer : CursorPreparer.NO_OP_PREPARER, objectCallback, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2736,6 +2741,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
DocumentCallback<T> objectCallback, String collectionName) {
|
||||
|
||||
try {
|
||||
|
||||
T result = objectCallback
|
||||
.doWith(collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName)));
|
||||
return result;
|
||||
@@ -2763,7 +2769,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @return
|
||||
*/
|
||||
private <T> List<T> executeFindMultiInternal(CollectionCallback<FindIterable<Document>> collectionCallback,
|
||||
@Nullable CursorPreparer preparer, DocumentCallback<T> objectCallback, String collectionName) {
|
||||
CursorPreparer preparer, DocumentCallback<T> objectCallback, String collectionName) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -2771,14 +2777,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
try {
|
||||
|
||||
FindIterable<Document> iterable = collectionCallback
|
||||
.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
|
||||
if (preparer != null) {
|
||||
iterable = preparer.prepare(iterable);
|
||||
}
|
||||
|
||||
cursor = iterable.iterator();
|
||||
cursor = preparer
|
||||
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
|
||||
.iterator();
|
||||
|
||||
List<T> result = new ArrayList<>();
|
||||
|
||||
@@ -2800,21 +2801,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
private void executeQueryInternal(CollectionCallback<FindIterable<Document>> collectionCallback,
|
||||
@Nullable CursorPreparer preparer, DocumentCallbackHandler callbackHandler, String collectionName) {
|
||||
CursorPreparer preparer, DocumentCallbackHandler callbackHandler, String collectionName) {
|
||||
|
||||
try {
|
||||
|
||||
MongoCursor<Document> cursor = null;
|
||||
|
||||
try {
|
||||
FindIterable<Document> iterable = collectionCallback
|
||||
.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
|
||||
if (preparer != null) {
|
||||
iterable = preparer.prepare(iterable);
|
||||
}
|
||||
|
||||
cursor = iterable.iterator();
|
||||
cursor = preparer
|
||||
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
|
||||
.iterator();
|
||||
|
||||
while (cursor.hasNext()) {
|
||||
callbackHandler.processDocument(cursor.next());
|
||||
@@ -2908,21 +2905,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
private final Document query;
|
||||
private final Optional<Document> fields;
|
||||
private final @Nullable com.mongodb.client.model.Collation collation;
|
||||
private final CursorPreparer cursorPreparer;
|
||||
|
||||
public FindOneCallback(Document query, Document fields, @Nullable com.mongodb.client.model.Collation collation) {
|
||||
FindOneCallback(Document query, Document fields, CursorPreparer preparer) {
|
||||
|
||||
this.query = query;
|
||||
this.fields = Optional.of(fields).filter(it -> !ObjectUtils.isEmpty(fields));
|
||||
this.collation = collation;
|
||||
this.cursorPreparer = preparer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
|
||||
FindIterable<Document> iterable = collection.find(query, Document.class);
|
||||
if (collation != null) {
|
||||
iterable = iterable.collation(collation);
|
||||
}
|
||||
FindIterable<Document> iterable = cursorPreparer.initiateFind(collection, col -> col.find(query, Document.class));
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
||||
@@ -3318,6 +3313,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
case PARTIAL:
|
||||
cursorToUse = cursorToUse.partial(true);
|
||||
break;
|
||||
case SLAVE_OK:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("%s is no supported flag.", option));
|
||||
}
|
||||
@@ -3330,6 +3327,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
return cursorToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadPreference getReadPreference() {
|
||||
return query.getMeta().getFlags().contains(CursorOption.SLAVE_OK) ? ReadPreference.primaryPreferred() : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -102,6 +102,7 @@ import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeSaveCal
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.query.Meta;
|
||||
import org.springframework.data.mongodb.core.query.Meta.CursorOption;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
@@ -803,7 +804,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
if (ObjectUtils.isEmpty(query.getSortObject())) {
|
||||
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
|
||||
operations.forType(entityClass).getCollation(query).orElse(null));
|
||||
new QueryFindPublisherPreparer(query, entityClass));
|
||||
}
|
||||
|
||||
query.limit(1);
|
||||
@@ -891,7 +892,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
String idKey = operations.getIdPropertyName(entityClass);
|
||||
|
||||
return doFindOne(collectionName, new Document(idKey, id), null, entityClass, null);
|
||||
return doFindOne(collectionName, new Document(idKey, id), null, entityClass, (Collation) null);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -932,6 +933,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
serializeToJsonSafely(mappedQuery), field, collectionName);
|
||||
}
|
||||
|
||||
FindPublisherPreparer preparer = new QueryFindPublisherPreparer(query, entityClass);
|
||||
if (preparer.hasReadPreferences()) {
|
||||
collection = collection.withReadPreference(preparer.getReadPreference());
|
||||
}
|
||||
|
||||
DistinctPublisher<T> publisher = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
|
||||
return operations.forType(entityClass).getCollation(query) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
@@ -1981,7 +1987,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAll(java.lang.Class, java.lang.String)
|
||||
*/
|
||||
public <T> Flux<T> findAll(Class<T> entityClass, String collectionName) {
|
||||
return executeFindMultiInternal(new FindCallback(null), null,
|
||||
return executeFindMultiInternal(new FindCallback(null), FindPublisherPreparer.NO_OP_PREPARER,
|
||||
new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
@@ -2035,8 +2041,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
LOGGER.debug(String.format("find for class: %s in collection: %s", entityClass, collectionName));
|
||||
|
||||
return executeFindMultiInternal(
|
||||
collection -> new FindCallback(null).doInCollection(collection).cursorType(CursorType.TailableAwait), null,
|
||||
new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName), collectionName);
|
||||
collection -> new FindCallback(null).doInCollection(collection).cursorType(CursorType.TailableAwait),
|
||||
FindPublisherPreparer.NO_OP_PREPARER, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName),
|
||||
collectionName);
|
||||
}
|
||||
|
||||
return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
|
||||
@@ -2327,6 +2334,29 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
protected <T> Mono<T> doFindOne(String collectionName, Document query, @Nullable Document fields,
|
||||
Class<T> entityClass, @Nullable Collation collation) {
|
||||
|
||||
return doFindOne(collectionName, query, fields, entityClass, new FindPublisherPreparer() {
|
||||
@Override
|
||||
public <T> FindPublisher<T> prepare(FindPublisher<T> findPublisher) {
|
||||
return collation != null ? findPublisher.collation(collation.toMongoCollation()) : findPublisher;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter.
|
||||
* The query document is specified as a standard {@link Document} and so is the fields specification.
|
||||
*
|
||||
* @param collectionName name of the collection to retrieve the objects from.
|
||||
* @param query the query document that specifies the criteria used to find a record.
|
||||
* @param fields the document that specifies the fields to be returned.
|
||||
* @param entityClass the parameterized type of the returned list.
|
||||
* @param preparer the preparer modifying collection and publisher to fit the needs.
|
||||
* @return the {@link List} of converted objects.
|
||||
* @since 2.2
|
||||
*/
|
||||
protected <T> Mono<T> doFindOne(String collectionName, Document query, @Nullable Document fields,
|
||||
Class<T> entityClass, FindPublisherPreparer preparer) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
Document mappedQuery = queryMapper.getMappedObject(query, entity);
|
||||
Document mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity);
|
||||
@@ -2336,7 +2366,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
serializeToJsonSafely(query), mappedFields, entityClass, collectionName));
|
||||
}
|
||||
|
||||
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, collation),
|
||||
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, preparer),
|
||||
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
@@ -2706,13 +2736,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
@Nullable FindPublisherPreparer preparer, DocumentCallback<T> objectCallback, String collectionName) {
|
||||
|
||||
return createFlux(collectionName, collection -> {
|
||||
|
||||
FindPublisher<Document> findPublisher = collectionCallback.doInCollection(collection);
|
||||
|
||||
if (preparer != null) {
|
||||
findPublisher = preparer.prepare(findPublisher);
|
||||
}
|
||||
return Flux.from(findPublisher).map(objectCallback::doWith);
|
||||
return Flux.from(preparer.initiateFind(collection, collectionCallback::doInCollection))
|
||||
.map(objectCallback::doWith);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2784,37 +2809,36 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Risberg
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
private static class FindOneCallback implements ReactiveCollectionCallback<Document> {
|
||||
|
||||
private final Document query;
|
||||
private final Optional<Document> fields;
|
||||
private final Optional<Collation> collation;
|
||||
private final FindPublisherPreparer preparer;
|
||||
|
||||
FindOneCallback(Document query, @Nullable Document fields, @Nullable Collation collation) {
|
||||
FindOneCallback(Document query, @Nullable Document fields, FindPublisherPreparer preparer) {
|
||||
this.query = query;
|
||||
this.fields = Optional.ofNullable(fields);
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
this.preparer = preparer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<Document> doInCollection(MongoCollection<Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
|
||||
FindPublisher<Document> publisher = collection.find(query, Document.class);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
||||
LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query),
|
||||
serializeToJsonSafely(fields.orElseGet(Document::new)), collection.getNamespace().getFullName());
|
||||
}
|
||||
|
||||
FindPublisher<Document> publisher = preparer.initiateFind(collection, col -> col.find(query, Document.class));
|
||||
|
||||
if (fields.isPresent()) {
|
||||
publisher = publisher.projection(fields.get());
|
||||
}
|
||||
|
||||
publisher = collation.map(Collation::toMongoCollation).map(publisher::collation).orElse(publisher);
|
||||
|
||||
return publisher.limit(1).first();
|
||||
}
|
||||
}
|
||||
@@ -3221,6 +3245,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
return findPublisherToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadPreference getReadPreference() {
|
||||
return query.getMeta().getFlags().contains(CursorOption.SLAVE_OK) ? ReadPreference.primaryPreferred() : null;
|
||||
}
|
||||
}
|
||||
|
||||
class TailingQueryFindPublisherPreparer extends QueryFindPublisherPreparer {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* https://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 org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.ReadPreference;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.2
|
||||
*/
|
||||
interface ReadPreferenceAware {
|
||||
|
||||
/**
|
||||
* @return {@literal true} if a {@link ReadPreference} is set.
|
||||
*/
|
||||
default boolean hasReadPreferences() {
|
||||
return getReadPreference() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ReadPreference} to apply or {@literal null} if none set.
|
||||
*/
|
||||
@Nullable
|
||||
ReadPreference getReadPreference();
|
||||
}
|
||||
@@ -935,7 +935,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Test // DATAMONGO-1733
|
||||
public void appliesFieldsWhenInterfaceProjectionIsClosedAndQueryDoesNotDefineFields() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonProjection.class, null);
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonProjection.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document("firstname", 1)));
|
||||
}
|
||||
@@ -943,7 +944,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Test // DATAMONGO-1733
|
||||
public void doesNotApplyFieldsWhenInterfaceProjectionIsClosedAndQueryDefinesFields() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, PersonProjection.class, null);
|
||||
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, PersonProjection.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document("bar", 1)));
|
||||
}
|
||||
@@ -951,7 +953,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Test // DATAMONGO-1733
|
||||
public void doesNotApplyFieldsWhenInterfaceProjectionIsOpen() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonSpELProjection.class, null);
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonSpELProjection.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document()));
|
||||
}
|
||||
@@ -959,7 +962,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Test // DATAMONGO-1733, DATAMONGO-2041
|
||||
public void appliesFieldsToDtoProjection() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, Jedi.class, null);
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, Jedi.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document("firstname", 1)));
|
||||
}
|
||||
@@ -967,7 +971,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Test // DATAMONGO-1733
|
||||
public void doesNotApplyFieldsToDtoProjectionWhenQueryDefinesFields() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, Jedi.class, null);
|
||||
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, Jedi.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document("bar", 1)));
|
||||
}
|
||||
@@ -975,7 +980,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Test // DATAMONGO-1733
|
||||
public void doesNotApplyFieldsWhenTargetIsNotAProjection() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, Person.class, null);
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, Person.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document()));
|
||||
}
|
||||
@@ -983,7 +989,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Test // DATAMONGO-1733
|
||||
public void doesNotApplyFieldsWhenTargetExtendsDomainType() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonExtended.class, null);
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonExtended.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document()));
|
||||
}
|
||||
@@ -1637,6 +1644,37 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
Assertions.assertThat(ReflectionTestUtils.getField(template, "entityCallbacks")).isSameAs(callbacks);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2344
|
||||
public void slaveOkQueryOptionShouldApplyPrimaryPreferredReadPreferenceForFind() {
|
||||
|
||||
template.find(new Query().slaveOk(), AutogenerateableId.class);
|
||||
|
||||
verify(collection).withReadPreference(eq(ReadPreference.primaryPreferred()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2344
|
||||
public void slaveOkQueryOptionShouldApplyPrimaryPreferredReadPreferenceForFindOne() {
|
||||
|
||||
template.findOne(new Query().slaveOk(), AutogenerateableId.class);
|
||||
|
||||
verify(collection).withReadPreference(eq(ReadPreference.primaryPreferred()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2344
|
||||
public void slaveOkQueryOptionShouldApplyPrimaryPreferredReadPreferenceForFindDistinct() {
|
||||
|
||||
template.findDistinct(new Query().slaveOk(), "name", AutogenerateableId.class, String.class);
|
||||
|
||||
verify(collection).withReadPreference(eq(ReadPreference.primaryPreferred()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2344
|
||||
public void slaveOkQueryOptionShouldApplyPrimaryPreferredReadPreferenceForStream() {
|
||||
|
||||
template.stream(new Query().slaveOk(), AutogenerateableId.class);
|
||||
verify(collection).withReadPreference(eq(ReadPreference.primaryPreferred()));
|
||||
}
|
||||
|
||||
class AutogenerateableId {
|
||||
|
||||
@Id BigInteger id;
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
@@ -65,6 +64,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.client.model.CountOptions;
|
||||
import com.mongodb.client.model.CreateCollectionOptions;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
@@ -121,6 +121,7 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
when(db.getCollection(any(), any())).thenReturn(collection);
|
||||
when(db.runCommand(any(), any(Class.class))).thenReturn(runCommandPublisher);
|
||||
when(db.createCollection(any(), any(CreateCollectionOptions.class))).thenReturn(runCommandPublisher);
|
||||
when(collection.withReadPreference(any())).thenReturn(collection);
|
||||
when(collection.find(any(Class.class))).thenReturn(findPublisher);
|
||||
when(collection.find(any(Document.class), any(Class.class))).thenReturn(findPublisher);
|
||||
when(collection.aggregate(anyList())).thenReturn(aggregatePublisher);
|
||||
@@ -324,8 +325,8 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
@Test // DATAMONGO-1719
|
||||
public void appliesFieldsWhenInterfaceProjectionIsClosedAndQueryDoesNotDefineFields() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonProjection.class, null)
|
||||
.subscribe();
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonProjection.class,
|
||||
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
|
||||
|
||||
verify(findPublisher).projection(eq(new Document("firstname", 1)));
|
||||
}
|
||||
@@ -333,8 +334,8 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
@Test // DATAMONGO-1719
|
||||
public void doesNotApplyFieldsWhenInterfaceProjectionIsClosedAndQueryDefinesFields() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, PersonProjection.class, null)
|
||||
.subscribe();
|
||||
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, PersonProjection.class,
|
||||
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
|
||||
|
||||
verify(findPublisher).projection(eq(new Document("bar", 1)));
|
||||
}
|
||||
@@ -342,8 +343,8 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
@Test // DATAMONGO-1719
|
||||
public void doesNotApplyFieldsWhenInterfaceProjectionIsOpen() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonSpELProjection.class, null)
|
||||
.subscribe();
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonSpELProjection.class,
|
||||
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
|
||||
|
||||
verify(findPublisher, never()).projection(any());
|
||||
}
|
||||
@@ -351,7 +352,8 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
@Test // DATAMONGO-1719, DATAMONGO-2041
|
||||
public void appliesFieldsToDtoProjection() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, Jedi.class, null).subscribe();
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, Jedi.class,
|
||||
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
|
||||
|
||||
verify(findPublisher).projection(eq(new Document("firstname", 1)));
|
||||
}
|
||||
@@ -359,7 +361,8 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
@Test // DATAMONGO-1719
|
||||
public void doesNotApplyFieldsToDtoProjectionWhenQueryDefinesFields() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, Jedi.class, null).subscribe();
|
||||
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, Jedi.class,
|
||||
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
|
||||
|
||||
verify(findPublisher).projection(eq(new Document("bar", 1)));
|
||||
}
|
||||
@@ -367,7 +370,8 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
@Test // DATAMONGO-1719
|
||||
public void doesNotApplyFieldsWhenTargetIsNotAProjection() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, Person.class, null).subscribe();
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, Person.class,
|
||||
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
|
||||
|
||||
verify(findPublisher, never()).projection(any());
|
||||
}
|
||||
@@ -375,7 +379,8 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
@Test // DATAMONGO-1719
|
||||
public void doesNotApplyFieldsWhenTargetExtendsDomainType() {
|
||||
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonExtended.class, null).subscribe();
|
||||
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonExtended.class,
|
||||
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
|
||||
|
||||
verify(findPublisher, never()).projection(any());
|
||||
}
|
||||
@@ -804,6 +809,30 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
Assertions.assertThat(ReflectionTestUtils.getField(template, "entityCallbacks")).isSameAs(callbacks);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2344
|
||||
public void slaveOkQueryOptionShouldApplyPrimaryPreferredReadPreferenceForFind() {
|
||||
|
||||
template.find(new Query().slaveOk(), AutogenerateableId.class).subscribe();
|
||||
|
||||
verify(collection).withReadPreference(eq(ReadPreference.primaryPreferred()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2344
|
||||
public void slaveOkQueryOptionShouldApplyPrimaryPreferredReadPreferenceForFindOne() {
|
||||
|
||||
template.findOne(new Query().slaveOk(), AutogenerateableId.class).subscribe();
|
||||
|
||||
verify(collection).withReadPreference(eq(ReadPreference.primaryPreferred()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2344
|
||||
public void slaveOkQueryOptionShouldApplyPrimaryPreferredReadPreferenceForFindDistinct() {
|
||||
|
||||
template.findDistinct(new Query().slaveOk(), "name", AutogenerateableId.class, String.class).subscribe();
|
||||
|
||||
verify(collection).withReadPreference(eq(ReadPreference.primaryPreferred()));
|
||||
}
|
||||
|
||||
@Data
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collection = "star-wars")
|
||||
static class Person {
|
||||
|
||||
Reference in New Issue
Block a user