DATAMONGO-1311 - Add configuration options for query batch size.

We now allow configuration of the find cursor/find publisher batch sizes using Query.cursorBatchSize(…).
Configuring the batch size gives users more fine grained control over the fetch behavior especially in reactive usage scenarios as the batch size defaults in FindPublisher to the remaining demand. This can cause several roundtrips in cases the remaining demand is small and the emitted elements are dropped rapidly (e.g. using filter(…)).

On the repository level @Meta allows now configuration of the cursor batch size for derived finder methods.

interface PersonRepository extends Repository<Person, Long> {

	@Meta(cursorBatchSize = 100)
	Stream<Person> findAllByLastname(String lastname);
}

Original Pull Request: #575
This commit is contained in:
Mark Paluch
2018-06-27 15:43:10 +02:00
committed by Christoph Strobl
parent f587e1f42a
commit d3976f5199
10 changed files with 132 additions and 26 deletions

View File

@@ -3279,8 +3279,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return cursor;
}
Meta meta = query.getMeta();
if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject())
&& !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()
&& !StringUtils.hasText(query.getHint()) && !meta.hasValues()
&& !query.getCollation().isPresent()) {
return cursor;
}
@@ -3301,18 +3302,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
cursorToUse = cursorToUse.sort(sort);
}
Document meta = new Document();
Document metaDocument = new Document();
if (StringUtils.hasText(query.getHint())) {
meta.put("$hint", query.getHint());
metaDocument.put("$hint", query.getHint());
}
if (query.getMeta().hasValues()) {
if (meta.hasValues()) {
for (Entry<String, Object> entry : query.getMeta().values()) {
meta.put(entry.getKey(), entry.getValue());
for (Entry<String, Object> entry : meta.values()) {
metaDocument.put(entry.getKey(), entry.getValue());
}
for (Meta.CursorOption option : query.getMeta().getFlags()) {
for (Meta.CursorOption option : meta.getFlags()) {
switch (option) {
@@ -3326,9 +3327,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
throw new IllegalArgumentException(String.format("%s is no supported flag.", option));
}
}
if (meta.getCursorBatchSize() != null) {
cursorToUse = cursorToUse.batchSize(meta.getCursorBatchSize());
}
}
cursorToUse = cursorToUse.modifiers(meta);
cursorToUse = cursorToUse.modifiers(metaDocument);
} catch (RuntimeException e) {
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
}

View File

@@ -98,6 +98,7 @@ import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.Meta;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
@@ -971,7 +972,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
List<Document> pipeline = aggregationUtil.createPipeline(aggregation, rootContext);
Assert.isTrue(!options.isExplain(), "Cannot use explain option with streaming!");
Assert.isNull(options.getCursorBatchSize(), "Cannot use batchSize cursor option with streaming!");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName);
@@ -987,6 +987,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
AggregatePublisher<Document> cursor = collection.aggregate(pipeline, Document.class)
.allowDiskUse(options.isAllowDiskUse());
if (options.getCursorBatchSize() != null) {
cursor = cursor.batchSize(options.getCursorBatchSize());
}
if (options.getCollation().isPresent()) {
cursor = cursor.collation(options.getCollation().map(Collation::toMongoCollation).get());
}
@@ -3216,8 +3220,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
findPublisherToUse = query.getCollation().map(Collation::toMongoCollation).map(findPublisher::collation)
.orElse(findPublisher);
Meta meta = query.getMeta();
if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject())
&& !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) {
&& !StringUtils.hasText(query.getHint()) && !meta.hasValues()) {
return findPublisherToUse;
}
@@ -3238,10 +3243,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
modifiers.append("$hint", query.getHint());
}
if (query.getMeta().hasValues()) {
for (Entry<String, Object> entry : query.getMeta().values()) {
if (meta.hasValues()) {
for (Entry<String, Object> entry : meta.values()) {
modifiers.append(entry.getKey(), entry.getValue());
}
if (meta.getCursorBatchSize() != null) {
findPublisherToUse = findPublisherToUse.batchSize(meta.getCursorBatchSize());
}
}
if (!modifiers.isEmpty()) {

View File

@@ -50,6 +50,7 @@ public class Meta {
private final Map<String, Object> values = new LinkedHashMap<String, Object>(2);
private final Set<CursorOption> flags = new LinkedHashSet<CursorOption>();
private Integer cursorBatchSize;
/**
* @return {@literal null} if not set.
@@ -128,6 +129,25 @@ public class Meta {
return getValue(MetaKey.SNAPSHOT.key, false);
}
/**
* @return {@literal null} if not set.
* @since 2.1
*/
@Nullable
public Integer getCursorBatchSize() {
return cursorBatchSize;
}
/**
* Apply the batch size for a query.
*
* @param cursorBatchSize
* @since 2.1
*/
public void setCursorBatchSize(int cursorBatchSize) {
this.cursorBatchSize = cursorBatchSize;
}
/**
* Add {@link CursorOption} influencing behavior of the {@link com.mongodb.DBCursor}.
*
@@ -153,7 +173,7 @@ public class Meta {
* @return
*/
public boolean hasValues() {
return !this.values.isEmpty() || !this.flags.isEmpty();
return !this.values.isEmpty() || !this.flags.isEmpty() || this.cursorBatchSize != null;
}
/**

View File

@@ -347,6 +347,18 @@ public class Query {
return this;
}
/**
* @param batchSize
* @return
* @see Meta#setCursorBatchSize(int)
* @since 2.1
*/
public Query cursorBatchSize(int batchSize) {
meta.setCursorBatchSize(batchSize);
return this;
}
/**
* @return
* @see org.springframework.data.mongodb.core.query.Meta.CursorOption#NO_TIMEOUT

View File

@@ -50,6 +50,14 @@ public @interface Meta {
*/
long maxScanDocuments() default -1;
/**
* Sets the number of documents to return per batch.
*
* @return
* @since 2.1
*/
int cursorBatchSize() default -1;
/**
* Add a comment to the query.
*

View File

@@ -274,6 +274,10 @@ public class MongoQueryMethod extends QueryMethod {
metaAttributes.setMaxScan(meta.maxScanDocuments());
}
if (meta.cursorBatchSize() > 0) {
metaAttributes.setCursorBatchSize(meta.cursorBatchSize());
}
if (StringUtils.hasText(meta.comment())) {
metaAttributes.setComment(meta.comment());
}

View File

@@ -22,7 +22,6 @@ import static org.mockito.Mockito.any;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
import com.mongodb.client.model.ReplaceOptions;
import lombok.Data;
import java.math.BigInteger;
@@ -93,6 +92,7 @@ import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReplaceOptions;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.result.UpdateResult;
@@ -161,7 +161,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
new MongoTemplate(mongo, null);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1968
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1968
public void rejectsNullMongo() {
new MongoTemplate((MongoClient) null, "database");
}
@@ -653,6 +653,17 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
assertThat(updateCaptor.getValue(), isBsonObject().containing("$set.jon", "snow").notContaining("$isolated"));
}
@Test // DATAMONGO-1311
public void executeQueryShouldUseBatchSizeWhenPresent() {
when(findIterable.batchSize(anyInt())).thenReturn(findIterable);
Query query = new Query().cursorBatchSize(1234);
template.find(query, Person.class);
verify(findIterable).batchSize(1234);
}
@Test // DATAMONGO-1518
public void executeQueryShouldUseCollationWhenPresent() {
@@ -969,7 +980,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
return template;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.core.MongoOperationsUnitTests#getOperations()
*/

View File

@@ -22,7 +22,9 @@ import static org.mockito.Mockito.any;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import com.mongodb.client.model.ReplaceOptions;
import com.mongodb.reactivestreams.client.AggregatePublisher;
import lombok.Data;
import org.springframework.data.mongodb.core.query.Query;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -79,6 +81,7 @@ public class ReactiveMongoTemplateUnitTests {
@Mock MongoDatabase db;
@Mock MongoCollection collection;
@Mock FindPublisher findPublisher;
@Mock AggregatePublisher aggregatePublisher;
@Mock Publisher runCommandPublisher;
MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
@@ -95,10 +98,15 @@ public class ReactiveMongoTemplateUnitTests {
when(db.runCommand(any(), any(Class.class))).thenReturn(runCommandPublisher);
when(collection.find(any(Class.class))).thenReturn(findPublisher);
when(collection.find(any(Document.class), any(Class.class))).thenReturn(findPublisher);
when(collection.aggregate(anyList())).thenReturn(aggregatePublisher);
when(collection.aggregate(anyList(), any(Class.class))).thenReturn(aggregatePublisher);
when(findPublisher.projection(any())).thenReturn(findPublisher);
when(findPublisher.limit(anyInt())).thenReturn(findPublisher);
when(findPublisher.collation(any())).thenReturn(findPublisher);
when(findPublisher.first()).thenReturn(findPublisher);
when(aggregatePublisher.allowDiskUse(anyBoolean())).thenReturn(aggregatePublisher);
when(aggregatePublisher.collation(any())).thenReturn(aggregatePublisher);
when(aggregatePublisher.first()).thenReturn(findPublisher);
this.mappingContext = new MongoMappingContext();
this.converter = new MappingMongoConverter(new NoOpDbRefResolver(), mappingContext);
@@ -136,6 +144,17 @@ public class ReactiveMongoTemplateUnitTests {
}).verifyComplete();
}
@Test // DATAMONGO-1311
public void executeQueryShouldUseBatchSizeWhenPresent() {
when(findPublisher.batchSize(anyInt())).thenReturn(findPublisher);
Query query = new Query().cursorBatchSize(1234);
template.find(query, Person.class).subscribe();
verify(findPublisher).batchSize(1234);
}
@Test // DATAMONGO-1518
public void findShouldUseCollationWhenPresent() {

View File

@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.core.aggregation;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyList;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import org.bson.Document;
@@ -80,16 +82,6 @@ public class ReactiveAggregationUnitTests {
template.aggregate(newAggregation(), INPUT_COLLECTION, null);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1646
public void errorsOnCursorBatchSizeUsage() {
template.aggregate(
newAggregation(Product.class, //
project("name", "netPrice")) //
.withOptions(AggregationOptions.builder().cursorBatchSize(10).build()),
INPUT_COLLECTION, TagCount.class).subscribe();
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1646
public void errorsOnExplainUsage() {
@@ -101,6 +93,20 @@ public class ReactiveAggregationUnitTests {
.subscribe();
}
@Test // DATAMONGO-1646, DATAMONGO-1311
public void appliesBatchSizeWhenPresent() {
when(publisher.batchSize(anyInt())).thenReturn(publisher);
AggregationOptions options = AggregationOptions.builder().cursorBatchSize(1234).build();
template.aggregate(newAggregation(Product.class, //
project("name", "netPrice")) //
.withOptions(options),
INPUT_COLLECTION, TagCount.class).subscribe();
verify(publisher).batchSize(1234);
}
@Test // DATAMONGO-1646
public void appliesCollationCorrectlyWhenPresent() {

View File

@@ -146,6 +146,15 @@ public class MongoQueryMethodUnitTests {
assertThat(method.getQueryMetaAttributes().getMaxTimeMsec(), is(100L));
}
@Test // DATAMONGO-1311
public void createsMongoQueryMethodWithBatchSizeCorrectly() throws Exception {
MongoQueryMethod method = queryMethod(PersonRepository.class, "batchSize");
assertThat(method.hasQueryMetaAttributes(), is(true));
assertThat(method.getQueryMetaAttributes().getCursorBatchSize(), is(100));
}
@Test // DATAMONGO-1403
public void createsMongoQueryMethodWithSpellFixedMaxExecutionTimeCorrectly() throws Exception {
@@ -233,6 +242,9 @@ public class MongoQueryMethodUnitTests {
@Meta
List<User> emptyMetaAnnotation();
@Meta(cursorBatchSize = 100)
List<User> batchSize();
@Meta(maxExecutionTimeMs = 100)
List<User> metaWithMaxExecutionTime();