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:
Christoph Strobl
2019-08-20 13:45:26 +02:00
committed by Mark Paluch
parent b2e3e3fb8e
commit 9048ec83af
8 changed files with 331 additions and 86 deletions

View File

@@ -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;
}
}

View File

@@ -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();
}
}
/**

View File

@@ -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;
}
}

View File

@@ -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;
}
}
/**

View File

@@ -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 {

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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 {