diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
index 79c192106..a71e4f5a8 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
@@ -1144,8 +1144,11 @@ public interface MongoOperations extends FluentMongoOperations {
* {@literal null}.
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
* @return the count of matching documents.
+ * @since 3.4
*/
- long count(Query query, Class> entityClass);
+ default long exactCount(Query query, Class> entityClass) {
+ return exactCount(query, entityClass, getCollectionName(entityClass));
+ }
/**
* Returns the number of documents for the given {@link Query} querying the given collection. The given {@link Query}
@@ -1166,6 +1169,71 @@ public interface MongoOperations extends FluentMongoOperations {
* @param collectionName must not be {@literal null} or empty.
* @return the count of matching documents.
* @see #count(Query, Class, String)
+ * @since 3.4
+ */
+ default long exactCount(Query query, String collectionName) {
+ return exactCount(query, null, collectionName);
+ }
+
+ /**
+ * Returns the number of documents for the given {@link Query} by querying the given collection using the given entity
+ * class to map the given {@link Query}.
+ * NOTE: Query {@link Query#getSkip() offset} and {@link Query#getLimit() limit} can have direct
+ * influence on the resulting number of documents found as those values are passed on to the server and potentially
+ * limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
+ * count all matches.
+ *
+ * This method uses an
+ * {@link com.mongodb.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
+ * aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees
+ * shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use
+ * {@link #estimatedCount(String)} for empty queries instead.
+ *
+ * @param query the {@link Query} class that specifies the criteria used to find documents. Must not be
+ * {@literal null}.
+ * @param entityClass the parametrized type. Can be {@literal null}.
+ * @param collectionName must not be {@literal null} or empty.
+ * @return the count of matching documents.
+ * @since 3.4
+ */
+ long exactCount(Query query, @Nullable Class> entityClass, String collectionName);
+
+ /**
+ * Returns the number of documents for the given {@link Query} by querying the collection of the given entity class.
+ *
+ * NOTE: Query {@link Query#getSkip() offset} and {@link Query#getLimit() limit} can have direct
+ * influence on the resulting number of documents found as those values are passed on to the server and potentially
+ * limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
+ * count all matches.
+ *
+ * This method may choose to use {@link #estimatedCount(Class)} for empty queries instead of running an
+ * {@link com.mongodb.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
+ * aggregation execution} which may have an impact on performance.
+ *
+ * @param query the {@link Query} class that specifies the criteria used to find documents. Must not be
+ * {@literal null}.
+ * @param entityClass class that determines the collection to use. Must not be {@literal null}.
+ * @return the count of matching documents.
+ */
+ long count(Query query, Class> entityClass);
+
+ /**
+ * Returns the number of documents for the given {@link Query} querying the given collection. The given {@link Query}
+ * must solely consist of document field references as we lack type information to map potential property references
+ * onto document fields. Use {@link #count(Query, Class, String)} to get full type specific support.
+ * NOTE: Query {@link Query#getSkip() offset} and {@link Query#getLimit() limit} can have direct
+ * influence on the resulting number of documents found as those values are passed on to the server and potentially
+ * limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
+ * count all matches.
+ *
+ * This method may choose to use {@link #estimatedCount(Class)} for empty queries instead of running an
+ * {@link com.mongodb.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
+ * aggregation execution} which may have an impact on performance.
+ *
+ * @param query the {@link Query} class that specifies the criteria used to find documents.
+ * @param collectionName must not be {@literal null} or empty.
+ * @return the count of matching documents.
+ * @see #count(Query, Class, String)
*/
long count(Query query, String collectionName);
@@ -1206,11 +1274,9 @@ public interface MongoOperations extends FluentMongoOperations {
* limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
* count all matches.
*
- * This method uses an
+ * This method may choose to use {@link #estimatedCount(Class)} for empty queries instead of running an
* {@link com.mongodb.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
- * aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees
- * shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use
- * {@link #estimatedCount(String)} for empty queries instead.
+ * aggregation execution} which may have an impact on performance.
*
* @param query the {@link Query} class that specifies the criteria used to find documents. Must not be
* {@literal null}.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
index 711201073..03b603959 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -185,6 +186,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private SessionSynchronization sessionSynchronization = SessionSynchronization.ON_ACTUAL_TRANSACTION;
+ private CountExecution countExecution = this::doExactCount;
+
/**
* Constructor used for a basic template configuration.
*
@@ -338,6 +341,47 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
this.entityCallbacks = entityCallbacks;
}
+ /**
+ * En-/Disable usage of estimated count.
+ *
+ * @param enabled if {@literal true} {@link MongoCollection#estimatedDocumentCount()} ()} will we used for unpaged,
+ * empty {@link Query queries}.
+ * @since 3.4
+ */
+ public void useEstimatedCount(boolean enabled) {
+ useEstimatedCount(enabled, this::countCanBeEstimated);
+ }
+
+ /**
+ * En-/Disable usage of estimated count based on the given {@link BiPredicate estimationFilter}.
+ *
+ * @param enabled if {@literal true} {@link MongoCollection#estimatedDocumentCount()} will we used for {@link Document
+ * filter queries} that pass the given {@link BiPredicate estimationFilter}.
+ * @param estimationFilter the {@link BiPredicate filter}.
+ * @since 3.4
+ */
+ private void useEstimatedCount(boolean enabled, BiPredicate estimationFilter) {
+
+ if (enabled) {
+
+ this.countExecution = (collectionName, filter, options) -> {
+
+ if (!estimationFilter.test(filter, options)) {
+ return doExactCount(collectionName, filter, options);
+ }
+
+ EstimatedDocumentCountOptions estimatedDocumentCountOptions = new EstimatedDocumentCountOptions();
+ if (options.getMaxTime(TimeUnit.MILLISECONDS) > 0) {
+ estimatedDocumentCountOptions.maxTime(options.getMaxTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
+ }
+
+ return doEstimatedCount(collectionName, estimatedDocumentCountOptions);
+ };
+ } else {
+ this.countExecution = this::doExactCount;
+ }
+ }
+
/**
* Inspects the given {@link ApplicationContext} for {@link MongoPersistentEntityIndexCreator} and those in turn if
* they were registered for the current {@link MappingContext}. If no creator for the current {@link MappingContext}
@@ -969,6 +1013,21 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return count(query, null, collectionName);
}
+ @Override
+ public long exactCount(Query query, @Nullable Class> entityClass, String collectionName) {
+
+ CountContext countContext = queryOperations.countQueryContext(query);
+
+ CountOptions options = countContext.getCountOptions(entityClass);
+ Document mappedQuery = countContext.getMappedQuery(entityClass, mappingContext::getPersistentEntity);
+
+ return doExactCount(collectionName, mappedQuery, options);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.MongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
+ */
public long count(Query query, @Nullable Class> entityClass, String collectionName) {
Assert.notNull(query, "Query must not be null!");
@@ -990,10 +1049,33 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
.debug(String.format("Executing count: %s in collection: %s", serializeToJsonSafely(filter), collectionName));
}
+ return countExecution.countDocuments(collectionName, filter, options);
+ }
+
+ protected long doExactCount(String collectionName, Document filter, CountOptions options) {
return execute(collectionName,
collection -> collection.countDocuments(CountQuery.of(filter).toQueryDocument(), options));
}
+ protected boolean countCanBeEstimated(Document filter, CountOptions options) {
+
+ return
+ // only empty filter for estimatedCount
+ filter.isEmpty() &&
+ // no skip, no limit,...
+ isEmptyOptions(options) &&
+ // transaction active?
+ !MongoDatabaseUtils.isTransactionActive(getMongoDatabaseFactory());
+ }
+
+ private boolean isEmptyOptions(CountOptions options) {
+ return options.getLimit() <= 0 && options.getSkip() <= 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.MongoOperations#estimatedCount(java.lang.String)
+ */
@Override
public long estimatedCount(String collectionName) {
return doEstimatedCount(collectionName, new EstimatedDocumentCountOptions());
@@ -3225,5 +3307,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
// native MongoDB objects that offer methods with ClientSession must not be proxied.
return delegate.getDb();
}
+
+ @Override
+ protected boolean countCanBeEstimated(Document filter, CountOptions options) {
+ return false;
+ }
+ }
+
+ @FunctionalInterface
+ interface CountExecution {
+ long countDocuments(String collection, Document filter, CountOptions options);
}
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
index e51238a72..d19775a90 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
@@ -885,8 +885,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* {@literal null}.
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
* @return the count of matching documents.
+ * @since 3.4
*/
- Mono count(Query query, Class> entityClass);
+ default Mono exactCount(Query query, Class> entityClass) {
+ return exactCount(query, entityClass, getCollectionName(entityClass));
+ }
/**
* Returns the number of documents for the given {@link Query} querying the given collection. The given {@link Query}
@@ -906,8 +909,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* @param collectionName must not be {@literal null} or empty.
* @return the count of matching documents.
* @see #count(Query, Class, String)
+ * @since 3.4
*/
- Mono count(Query query, String collectionName);
+ default Mono exactCount(Query query, String collectionName) {
+ return exactCount(query, null, collectionName);
+ }
/**
* Returns the number of documents for the given {@link Query} by querying the given collection using the given entity
@@ -927,6 +933,66 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* @param entityClass the parametrized type. Can be {@literal null}.
* @param collectionName must not be {@literal null} or empty.
* @return the count of matching documents.
+ * @since 3.4
+ */
+ Mono exactCount(Query query, @Nullable Class> entityClass, String collectionName);
+
+ /**
+ * Returns the number of documents for the given {@link Query} by querying the collection of the given entity class.
+ *
+ * NOTE: Query {@link Query#getSkip() offset} and {@link Query#getLimit() limit} can have direct
+ * influence on the resulting number of documents found as those values are passed on to the server and potentially
+ * limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
+ * count all matches.
+ *
+ * This method may choose to use {@link #estimatedCount(Class)} for empty queries instead of running an
+ * {@link com.mongodb.reactivestreams.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
+ * aggregation execution} which may have an impact on performance.
+ *
+ * @param query the {@link Query} class that specifies the criteria used to find documents. Must not be
+ * {@literal null}.
+ * @param entityClass class that determines the collection to use. Must not be {@literal null}.
+ * @return the count of matching documents.
+ */
+ Mono count(Query query, Class> entityClass);
+
+ /**
+ * Returns the number of documents for the given {@link Query} querying the given collection. The given {@link Query}
+ * must solely consist of document field references as we lack type information to map potential property references
+ * onto document fields. Use {@link #count(Query, Class, String)} to get full type specific support.
+ * NOTE: Query {@link Query#getSkip() offset} and {@link Query#getLimit() limit} can have direct
+ * influence on the resulting number of documents found as those values are passed on to the server and potentially
+ * limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
+ * count all matches.
+ *
+ * This method may choose to use {@link #estimatedCount(Class)} for empty queries instead of running an
+ * {@link com.mongodb.reactivestreams.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
+ * aggregation execution} which may have an impact on performance.
+ *
+ * @param query the {@link Query} class that specifies the criteria used to find documents.
+ * @param collectionName must not be {@literal null} or empty.
+ * @return the count of matching documents.
+ * @see #count(Query, Class, String)
+ */
+ Mono count(Query query, String collectionName);
+
+ /**
+ * Returns the number of documents for the given {@link Query} by querying the given collection using the given entity
+ * class to map the given {@link Query}.
+ * NOTE: Query {@link Query#getSkip() offset} and {@link Query#getLimit() limit} can have direct
+ * influence on the resulting number of documents found as those values are passed on to the server and potentially
+ * limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
+ * count all matches.
+ *
+ * This method may choose to use {@link #estimatedCount(Class)} for empty queries instead of running an
+ * {@link com.mongodb.reactivestreams.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
+ * aggregation execution} which may have an impact on performance.
+ *
+ * @param query the {@link Query} class that specifies the criteria used to find documents. Must not be
+ * {@literal null}.
+ * @param entityClass the parametrized type. Can be {@literal null}.
+ * @param collectionName must not be {@literal null} or empty.
+ * @return the count of matching documents.
*/
Mono count(Query query, @Nullable Class> entityClass, String collectionName);
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
index b37360ab9..9499fac9e 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
@@ -32,6 +32,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -189,6 +190,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
private SessionSynchronization sessionSynchronization = SessionSynchronization.ON_ACTUAL_TRANSACTION;
+ private CountExecution countExecution = this::doExactCount;
+
/**
* Constructor used for a basic template configuration.
*
@@ -363,6 +366,49 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
this.entityCallbacks = entityCallbacks;
}
+ /**
+ * En-/Disable usage of estimated count.
+ *
+ * @param enabled if {@literal true} {@link com.mongodb.client.MongoCollection#estimatedDocumentCount()} ()} will we used for unpaged,
+ * empty {@link Query queries}.
+ * @since 3.4
+ */
+ public void useEstimatedCount(boolean enabled) {
+ useEstimatedCount(enabled, this::countCanBeEstimated);
+ }
+
+ /**
+ * En-/Disable usage of estimated count based on the given {@link BiFunction estimationFilter}.
+ *
+ * @param enabled if {@literal true} {@link com.mongodb.client.MongoCollection#estimatedDocumentCount()} will we used for {@link Document
+ * filter queries} that pass the given {@link BiFunction estimationFilter}.
+ * @param estimationFilter the {@link BiFunction filter}.
+ * @since 3.4
+ */
+ private void useEstimatedCount(boolean enabled, BiFunction> estimationFilter) {
+
+ if (enabled) {
+
+ this.countExecution = (collectionName, filter, options) -> {
+
+ return estimationFilter.apply(filter, options).flatMap(canEstimate -> {
+ if (!canEstimate) {
+ return doExactCount(collectionName, filter, options);
+ }
+
+ EstimatedDocumentCountOptions estimatedDocumentCountOptions = new EstimatedDocumentCountOptions();
+ if (options.getMaxTime(TimeUnit.MILLISECONDS) > 0) {
+ estimatedDocumentCountOptions.maxTime(options.getMaxTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
+ }
+
+ return doEstimatedCount(collectionName, estimatedDocumentCountOptions);
+ });
+ };
+ } else {
+ this.countExecution = this::doExactCount;
+ }
+ }
+
/**
* Inspects the given {@link ApplicationContext} for {@link ReactiveMongoPersistentEntityIndexCreator} and those in
* turn if they were registered for the current {@link MappingContext}. If no creator for the current
@@ -959,6 +1005,21 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
entityClass);
}
+ @Override
+ public Mono exactCount(Query query, @Nullable Class> entityClass, String collectionName) {
+
+ CountContext countContext = queryOperations.countQueryContext(query);
+
+ CountOptions options = countContext.getCountOptions(entityClass);
+ Document mappedQuery = countContext.getMappedQuery(entityClass, mappingContext::getPersistentEntity);
+
+ return doExactCount(collectionName, mappedQuery, options);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class)
+ */
public Mono count(Query query, Class> entityClass) {
Assert.notNull(entityClass, "Entity class must not be null!");
@@ -1006,15 +1067,40 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
protected Mono doCount(String collectionName, Document filter, CountOptions options) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER
+ .debug(String.format("Executing count: %s in collection: %s", serializeToJsonSafely(filter), collectionName));
+ }
+
+ return countExecution.countDocuments(collectionName, filter, options);
+ }
+
+ protected Mono doExactCount(String collectionName, Document filter, CountOptions options) {
+
return createMono(collectionName,
collection -> collection.countDocuments(CountQuery.of(filter).toQueryDocument(), options));
}
protected Mono doEstimatedCount(String collectionName, EstimatedDocumentCountOptions options) {
-
return createMono(collectionName, collection -> collection.estimatedDocumentCount(options));
}
+ protected Mono countCanBeEstimated(Document filter, CountOptions options) {
+
+ if(!filter.isEmpty() || !isEmptyOptions(options)) {
+ return Mono.just(false);
+ }
+ return ReactiveMongoDatabaseUtils.isTransactionActive(getMongoDatabaseFactory()).map(it -> !it);
+ }
+
+ private boolean isEmptyOptions(CountOptions options) {
+ return options.getLimit() <= 0 && options.getSkip() <= 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#insert(reactor.core.publisher.Mono)
+ */
@Override
public Mono insert(Mono extends T> objectToSave) {
@@ -2992,6 +3078,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
// native MongoDB objects that offer methods with ClientSession must not be proxied.
return delegate.getMongoDatabase();
}
+
+ @Override
+ protected Mono countCanBeEstimated(Document filter, CountOptions options) {
+ return Mono.just(false);
+ }
}
class IndexCreatorEventListener implements ApplicationListener> {
@@ -3069,4 +3160,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return collection;
}
}
+
+ @FunctionalInterface
+ interface CountExecution {
+ Mono countDocuments(String collection, Document filter, CountOptions options);
+ }
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
index 1bdf17772..5ce1ac98b 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
@@ -2272,6 +2272,34 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
.granularity(TimeSeriesGranularity.HOURS).toString());
}
+ @Test // GH-3522
+ void usedCountDocumentsForEmptyQueryByDefault() {
+
+ template.count(new Query(), Human.class);
+
+ verify(collection).countDocuments(any(Document.class), any());
+ }
+
+ @Test // GH-3522
+ void delegatesToEstimatedCountForEmptyQueryIfEnabled() {
+
+ template.useEstimatedCount(true);
+
+ template.count(new Query(), Human.class);
+
+ verify(collection).estimatedDocumentCount(any());
+ }
+
+ @Test // GH-3522
+ void stillUsesCountDocumentsForNonEmptyQueryEvenIfEstimationEnabled() {
+
+ template.useEstimatedCount(true);
+
+ template.count(new BasicQuery("{ 'spring' : 'data-mongodb' }"), Human.class);
+
+ verify(collection).countDocuments(any(Document.class), any());
+ }
+
class AutogenerateableId {
@Id BigInteger id;
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
index 55b8edafb..0d9bca468 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
@@ -1424,6 +1424,34 @@ public class ReactiveMongoTemplateUnitTests {
verify(collection).estimatedDocumentCount(any());
}
+ @Test // GH-3522
+ void usedCountDocumentsForEmptyQueryByDefault() {
+
+ template.count(new Query(), Person.class).subscribe();
+
+ verify(collection).countDocuments(any(Document.class), any());
+ }
+
+ @Test // GH-3522
+ void delegatesToEstimatedCountForEmptyQueryIfEnabled() {
+
+ template.useEstimatedCount(true);
+
+ template.count(new Query(), Person.class).subscribe();
+
+ verify(collection).estimatedDocumentCount(any());
+ }
+
+ @Test // GH-3522
+ void stillUsesCountDocumentsForNonEmptyQueryEvenIfEstimationEnabled() {
+
+ template.useEstimatedCount(true);
+
+ template.count(new BasicQuery("{ 'spring' : 'data-mongodb' }"), Person.class).subscribe();
+
+ verify(collection).countDocuments(any(Document.class), any());
+ }
+
@Test // GH-2911
void insertErrorsOnPublisher() {
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveSessionBoundMongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveSessionBoundMongoTemplateUnitTests.java
index 8efee4e21..2384340e9 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveSessionBoundMongoTemplateUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveSessionBoundMongoTemplateUnitTests.java
@@ -220,7 +220,7 @@ public class ReactiveSessionBoundMongoTemplateUnitTests {
verify(database).listCollectionNames(eq(clientSession));
}
- @Test // DATAMONGO-1880
+ @Test // DATAMONGO-1880, GH-3522
public void countShouldUseProxiedCollection() {
template.count(new Query(), Person.class).subscribe();
@@ -228,6 +228,15 @@ public class ReactiveSessionBoundMongoTemplateUnitTests {
verify(collection).countDocuments(eq(clientSession), any(), any(CountOptions.class));
}
+ @Test // GH-3522
+ public void countShouldDelegateToExactCountNoMatterWhat() {
+
+ template.useEstimatedCount(true);
+ template.count(new Query(), Person.class).subscribe();
+
+ verify(collection).countDocuments(eq(clientSession), any(), any(CountOptions.class));
+ }
+
@Test // DATAMONGO-1880
public void createCollectionShouldUseProxiedDatabase() {
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateUnitTests.java
index 6f41e2c9f..d8bf29884 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateUnitTests.java
@@ -219,7 +219,7 @@ public class SessionBoundMongoTemplateUnitTests {
verify(database).listCollectionNames(eq(clientSession));
}
- @Test // DATAMONGO-1880
+ @Test // DATAMONGO-1880, GH-3522
public void countShouldUseProxiedCollection() {
template.count(new Query(), Person.class);
@@ -227,6 +227,15 @@ public class SessionBoundMongoTemplateUnitTests {
verify(collection).countDocuments(eq(clientSession), any(), any(CountOptions.class));
}
+ @Test // DATAMONGO-1880, GH-3522
+ public void countShouldDelegateToExactCountNoMatterWhat() {
+
+ template.useEstimatedCount(true);
+ template.count(new Query(), Person.class);
+
+ verify(collection).countDocuments(eq(clientSession), any(), any(CountOptions.class));
+ }
+
@Test // DATAMONGO-1880
public void createCollectionShouldUseProxiedDatabase() {
diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc
index 98aeaa8e9..a8caa7b0e 100644
--- a/src/main/asciidoc/reference/mongodb.adoc
+++ b/src/main/asciidoc/reference/mongodb.adoc
@@ -2158,6 +2158,12 @@ So in version 2.x `MongoOperations.count()` would use the collection statistics
As of Spring Data MongoDB 3.x any `count` operation uses regardless the existence of filter criteria the aggregation-based count approach via MongoDBs `countDocuments`.
If the application is fine with the limitations of working upon collection statistics `MongoOperations.estimatedCount()` offers an alternative.
+[TIP]
+====
+By setting `MongoTemplate#useEstimatedCount(...)` to `true` _MongoTemplate#count(...)_ operations, that use an empty filter query, will be delegated to `estimatedCount`, as long as there is no transaction active and the template is not bound to a <>.
+It will still be possible to obtain exact numbers via `MongoTemplate#exactCount`, but may speed up things.
+====
+
[NOTE]
====
MongoDBs native `countDocuments` method and the `$match` aggregation, do not support `$near` and `$nearSphere` but require `$geoWithin` along with `$center` or `$centerSphere` which does not support `$minDistance` (see https://jira.mongodb.org/browse/SERVER-37043).