From d16013aa6b34696c5400378e0da5fa555d75f7dd Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 27 Jan 2022 12:30:34 +0100 Subject: [PATCH] Allow to estimate document count. This commit introduce an option that allows users to opt in on using estimatedDocumentCount instead of countDocuments in case the used filter query is empty. To still be able to retrieve the exact number of matching documents we also introduced MongoTemplate#exactCount. Closes: #3522 Original pull request: #3951. --- .../data/mongodb/core/MongoOperations.java | 76 +++++++++++++- .../data/mongodb/core/MongoTemplate.java | 92 +++++++++++++++++ .../mongodb/core/ReactiveMongoOperations.java | 70 ++++++++++++- .../mongodb/core/ReactiveMongoTemplate.java | 98 ++++++++++++++++++- .../mongodb/core/MongoTemplateUnitTests.java | 28 ++++++ .../core/ReactiveMongoTemplateUnitTests.java | 28 ++++++ ...iveSessionBoundMongoTemplateUnitTests.java | 11 ++- .../SessionBoundMongoTemplateUnitTests.java | 11 ++- src/main/asciidoc/reference/mongodb.adoc | 6 ++ 9 files changed, 410 insertions(+), 10 deletions(-) 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 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).