Browse Source

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.
pull/3912/merge
Christoph Strobl 4 years ago committed by Mark Paluch
parent
commit
f774e35af2
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 76
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
  2. 85
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  3. 70
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
  4. 90
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  5. 28
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
  6. 28
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
  7. 11
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveSessionBoundMongoTemplateUnitTests.java
  8. 11
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateUnitTests.java
  9. 6
      src/main/asciidoc/reference/mongodb.adoc

76
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java

@ -1179,8 +1179,11 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1179,8 +1179,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}
@ -1201,6 +1204,71 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1201,6 +1204,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}. <br />
* <strong>NOTE:</strong> 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.
* <br />
* 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.
* <br />
* <strong>NOTE:</strong> 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.
* <br />
* 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. <br />
* <strong>NOTE:</strong> 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.
* <br />
* 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);
@ -1241,11 +1309,9 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1241,11 +1309,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.
* <br />
* 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}.

85
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

@ -22,13 +22,13 @@ import java.math.BigDecimal; @@ -22,13 +22,13 @@ 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@ -188,6 +188,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -188,6 +188,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.
*
@ -345,6 +347,47 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -345,6 +347,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<Document, CountOptions> 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}
@ -1106,6 +1149,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1106,6 +1149,17 @@ 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)
@ -1131,10 +1185,29 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1131,10 +1185,29 @@ 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)
@ -3571,5 +3644,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -3571,5 +3644,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);
}
}

70
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java

@ -951,8 +951,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -951,8 +951,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<Long> count(Query query, Class<?> entityClass);
default Mono<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}
@ -973,8 +976,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -973,8 +976,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<Long> count(Query query, String collectionName);
default Mono<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
@ -995,6 +1001,66 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -995,6 +1001,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<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.
* <br />
* <strong>NOTE:</strong> 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.
* <br />
* 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<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. <br />
* <strong>NOTE:</strong> 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.
* <br />
* 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<Long> 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}. <br />
* <strong>NOTE:</strong> 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.
* <br />
* 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<Long> count(Query query, @Nullable Class<?> entityClass, String collectionName);

90
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

@ -33,6 +33,7 @@ import java.util.List; @@ -33,6 +33,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;
@ -190,6 +191,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -190,6 +191,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.
*
@ -369,6 +372,49 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -369,6 +372,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<Document, CountOptions, Mono<Boolean>> 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
@ -1189,6 +1235,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1189,6 +1235,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
entityClass);
}
@Override
public Mono<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.ReactiveMongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class)
@ -1252,15 +1309,36 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1252,15 +1309,36 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
protected Mono<Long> 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<Long> doExactCount(String collectionName, Document filter, CountOptions options) {
return createMono(collectionName,
collection -> collection.countDocuments(CountQuery.of(filter).toQueryDocument(), options));
}
protected Mono<Long> doEstimatedCount(String collectionName, EstimatedDocumentCountOptions options) {
return createMono(collectionName, collection -> collection.estimatedDocumentCount(options));
}
protected Mono<Boolean> 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)
@ -3444,6 +3522,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -3444,6 +3522,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<Boolean> countCanBeEstimated(Document filter, CountOptions options) {
return Mono.just(false);
}
}
class IndexCreatorEventListener implements ApplicationListener<MappingContextEvent<?, ?>> {
@ -3521,4 +3604,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -3521,4 +3604,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return collection;
}
}
@FunctionalInterface
interface CountExecution {
Mono<Long> countDocuments(String collection, Document filter, CountOptions options);
}
}

28
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java

@ -2289,6 +2289,34 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -2289,6 +2289,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;

28
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java

@ -1424,6 +1424,34 @@ public class ReactiveMongoTemplateUnitTests { @@ -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() {

11
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveSessionBoundMongoTemplateUnitTests.java

@ -220,7 +220,7 @@ public class ReactiveSessionBoundMongoTemplateUnitTests { @@ -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 { @@ -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() {

11
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateUnitTests.java

@ -220,7 +220,7 @@ public class SessionBoundMongoTemplateUnitTests { @@ -220,7 +220,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);
@ -228,6 +228,15 @@ public class SessionBoundMongoTemplateUnitTests { @@ -228,6 +228,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() {

6
src/main/asciidoc/reference/mongodb.adoc

@ -2158,6 +2158,12 @@ So in version 2.x `MongoOperations.count()` would use the collection statistics @@ -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 <<mongo.sessions,session>>.
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).

Loading…
Cancel
Save