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).