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.
This commit is contained in:
committed by
Mark Paluch
parent
dab5473740
commit
d16013aa6b
@@ -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}. <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);
|
||||
|
||||
@@ -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.
|
||||
* <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}.
|
||||
|
||||
@@ -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<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}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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}
|
||||
@@ -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<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
|
||||
@@ -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<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);
|
||||
|
||||
|
||||
@@ -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<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
|
||||
@@ -959,6 +1005,21 @@ 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)
|
||||
*/
|
||||
public Mono<Long> 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<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)
|
||||
*/
|
||||
@Override
|
||||
public <T> Mono<T> 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<Boolean> countCanBeEstimated(Document filter, CountOptions options) {
|
||||
return Mono.just(false);
|
||||
}
|
||||
}
|
||||
|
||||
class IndexCreatorEventListener implements ApplicationListener<MappingContextEvent<?, ?>> {
|
||||
@@ -3069,4 +3160,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface CountExecution {
|
||||
Mono<Long> countDocuments(String collection, Document filter, CountOptions options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user