Skip to content

DATAMONGO-1464 Pagination - Optimize out the count query for paging. #379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1464-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
4 changes: 2 additions & 2 deletions spring-data-mongodb-cross-store/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1464-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down Expand Up @@ -48,7 +48,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1464-SNAPSHOT</version>
</dependency>

<dependency>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1464-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-log4j/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1464-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1464-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import java.util.Collections;
import java.util.List;
import java.util.function.Function;

import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Range;
import org.springframework.data.domain.Slice;
Expand All @@ -39,13 +38,23 @@
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.data.repository.support.PageableExecutionUtils.TotalSupplier;
import org.springframework.data.util.CloseableIterator;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.ClassUtils;

import com.mongodb.WriteResult;

/**
* Set of classes to contain query execution strategies. Depending (mostly) on the return type of a
* {@link org.springframework.data.repository.query.QueryMethod} a {@link AbstractMongoQuery} can be executed in various
* flavors.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
interface MongoQueryExecution {

Object execute(Query query, Class<?> type, String collection);
Expand Down Expand Up @@ -108,6 +117,7 @@ public Object execute(Query query, Class<?> type, String collection) {
* {@link MongoQueryExecution} for pagination queries.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
@RequiredArgsConstructor
static final class PagedExecution implements MongoQueryExecution {
Expand All @@ -120,29 +130,27 @@ static final class PagedExecution implements MongoQueryExecution {
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
*/
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public Object execute(Query query, Class<?> type, String collection) {
public Object execute(final Query query, final Class<?> type, final String collection) {

int overallLimit = query.getLimit();
long count = operations.count(query, type, collection);
count = overallLimit != 0 ? Math.min(count, query.getLimit()) : count;

boolean pageableOutOfScope = pageable.getOffset() > count;

if (pageableOutOfScope) {
return new PageImpl<Object>(Collections.emptyList(), pageable, count);
}
final int overallLimit = query.getLimit();

// Apply raw pagination
query = query.with(pageable);
query.with(pageable);

// Adjust limit if page would exceed the overall limit
if (overallLimit != 0 && pageable.getOffset() + pageable.getPageSize() > overallLimit) {
query.limit(overallLimit - pageable.getOffset());
}

List<?> result = operations.find(query, type, collection);
return new PageImpl(result, pageable, count);
return PageableExecutionUtils.getPage(operations.find(query, type, collection), pageable, new TotalSupplier() {

@Override
public long get() {

long count = operations.count(query, type, collection);
return overallLimit != 0 ? Math.min(count, overallLimit) : count;
}
});
}
}

Expand Down Expand Up @@ -229,10 +237,16 @@ private boolean isListOfGeoResult() {
}

TypeInformation<?> componentType = returnType.getComponentType();
return componentType == null ? false : GeoResult.class.equals(componentType.getType());
return componentType != null && GeoResult.class.equals(componentType.getType());
}
}

/**
* {@link MongoQueryExecution} to execute geo-near queries with paging.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
static final class PagingGeoNearExecution extends GeoNearExecution {

private final MongoOperations operations;
Expand All @@ -249,22 +263,32 @@ public PagingGeoNearExecution(MongoOperations operations, MongoParameterAccessor
this.mongoQuery = query;
}

/**
* Executes the given {@link Query} to return a page.
*
* @param query must not be {@literal null}.
* @param countQuery must not be {@literal null}.
* @return
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.MongoQueryExecution.GeoNearExecution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
*/
@Override
public Object execute(Query query, Class<?> type, String collection) {
public Object execute(Query query, Class<?> type, final String collection) {

GeoResults<Object> geoResults = doExecuteQuery(query, type, collection);

Page<GeoResult<Object>> page = PageableExecutionUtils.getPage(geoResults.getContent(), accessor.getPageable(),
new TotalSupplier() {

ConvertingParameterAccessor parameterAccessor = new ConvertingParameterAccessor(operations.getConverter(),
accessor);
Query countQuery = mongoQuery.applyQueryMetaAttributesWhenPresent(mongoQuery.createCountQuery(parameterAccessor));
long count = operations.count(countQuery, collection);
@Override
public long get() {

ConvertingParameterAccessor parameterAccessor = new ConvertingParameterAccessor(operations.getConverter(),
accessor);
Query countQuery = mongoQuery
.applyQueryMetaAttributesWhenPresent(mongoQuery.createCountQuery(parameterAccessor));

return operations.count(countQuery, collection);
}
});

return new GeoPage<Object>(doExecuteQuery(query, type, collection), accessor.getPageable(), count);
// transform to GeoPage after applying optimization
return new GeoPage<Object>(geoResults, accessor.getPageable(), page.getTotalElements());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2011-2015 the original author or authors.
* Copyright 2011-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,7 +19,6 @@
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
Expand All @@ -32,6 +31,8 @@
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.EntityMetadata;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.data.repository.support.PageableExecutionUtils.TotalSupplier;
import org.springframework.util.Assert;

import com.querydsl.core.types.EntityPath;
Expand All @@ -46,6 +47,7 @@
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
*/
public class QueryDslMongoRepository<T, ID extends Serializable> extends SimpleMongoRepository<T, ID>
implements QueryDslPredicateExecutor<T> {
Expand Down Expand Up @@ -136,13 +138,17 @@ public Iterable<T> findAll(OrderSpecifier<?>... orders) {
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate, org.springframework.data.domain.Pageable)
*/
@Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
public Page<T> findAll(final Predicate predicate, Pageable pageable) {

AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> countQuery = createQueryFor(predicate);
AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> query = createQueryFor(predicate);

return new PageImpl<T>(applyPagination(query, pageable).fetchResults().getResults(), pageable,
countQuery.fetchCount());
return PageableExecutionUtils.getPage(applyPagination(query, pageable).fetchResults().getResults(), pageable, new TotalSupplier() {

@Override
public long get() {
return createQueryFor(predicate).fetchCount();
}
});
}

/*
Expand All @@ -152,11 +158,15 @@ public Page<T> findAll(Predicate predicate, Pageable pageable) {
@Override
public Page<T> findAll(Pageable pageable) {

AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> countQuery = createQuery();
AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> query = createQuery();

return new PageImpl<T>(applyPagination(query, pageable).fetchResults().getResults(), pageable,
countQuery.fetchCount());
return PageableExecutionUtils.getPage(applyPagination(query, pageable).fetchResults().getResults(), pageable, new TotalSupplier() {

@Override
public long get() {
return createQuery().fetchCount();
}
});
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.data.repository.support.PageableExecutionUtils.TotalSupplier;
import org.springframework.util.Assert;

/**
Expand Down Expand Up @@ -266,20 +268,20 @@ public <S extends T> List<S> insert(Iterable<S> entities) {
* @see org.springframework.data.mongodb.repository.MongoRepository#findAllByExample(org.springframework.data.domain.Example, org.springframework.data.domain.Pageable)
*/
@Override
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
public <S extends T> Page<S> findAll(final Example<S> example, Pageable pageable) {

Assert.notNull(example, "Sample must not be null!");

Query q = new Query(new Criteria().alike(example)).with(pageable);
final Query q = new Query(new Criteria().alike(example)).with(pageable);

long count = mongoOperations.count(q, example.getProbeType(), entityInformation.getCollectionName());
List<S> list = mongoOperations.find(q, example.getProbeType(), entityInformation.getCollectionName());
return PageableExecutionUtils.getPage(list, pageable, new TotalSupplier() {

if (count == 0) {
return new PageImpl<S>(Collections.<S> emptyList());
}

return new PageImpl<S>(mongoOperations.find(q, example.getProbeType(), entityInformation.getCollectionName()),
pageable, count);
@Override
public long get() {
return mongoOperations.count(q, example.getProbeType(), entityInformation.getCollectionName());
}
});
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ public void findsPagedPeopleByPredicate() throws Exception {
assertThat(page.isFirst(), is(true));
assertThat(page.isLast(), is(false));
assertThat(page.getNumberOfElements(), is(2));
assertThat(page.getTotalElements(), is(4L));
assertThat(page, hasItems(carter, stefan));
}

Expand Down Expand Up @@ -947,7 +948,7 @@ public void shouldLimitCollectionQueryToMaxResultsWhenPresent() {
}

/**
* @see DATAMONGO-950
* @see DATAMONGO-950, DATAMONGO-1464
*/
@Test
public void shouldNotLimitPagedQueryWhenPageRequestWithinBounds() {
Expand All @@ -956,6 +957,7 @@ public void shouldNotLimitPagedQueryWhenPageRequestWithinBounds() {
new Person("Bob-3", "Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan")));
Page<Person> result = repository.findTop3ByLastnameStartingWith("Dylan", new PageRequest(0, 2));
assertThat(result.getContent().size(), is(2));
assertThat(result.getTotalElements(), is(3L));
}

/**
Expand All @@ -971,19 +973,20 @@ public void shouldLimitPagedQueryWhenPageRequestExceedsUpperBoundary() {
}

/**
* @see DATAMONGO-950
* @see DATAMONGO-950, DATAMONGO-1464
*/
@Test
public void shouldReturnEmptyWhenPageRequestedPageIsTotallyOutOfScopeForLimit() {

repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"),
new Person("Bob-3", "Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan")));
Page<Person> result = repository.findTop3ByLastnameStartingWith("Dylan", new PageRequest(2, 2));
Page<Person> result = repository.findTop3ByLastnameStartingWith("Dylan", new PageRequest(100, 2));
assertThat(result.getContent().size(), is(0));
assertThat(result.getTotalElements(), is(3L));
}

/**
* @see DATAMONGO-996, DATAMONGO-950
* @see DATAMONGO-996, DATAMONGO-950, DATAMONGO-1464
*/
@Test
public void gettingNonFirstPageWorksWithoutLimitBeingSet() {
Expand All @@ -993,6 +996,7 @@ public void gettingNonFirstPageWorksWithoutLimitBeingSet() {
assertThat(slice.getContent(), hasSize(1));
assertThat(slice.hasPrevious(), is(true));
assertThat(slice.hasNext(), is(false));
assertThat(slice.getTotalElements(), is(2L));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -67,6 +67,7 @@
* @author Christoph Strobl
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class AbstractMongoQueryUnitTests {
Expand Down Expand Up @@ -188,7 +189,7 @@ public void metadataShouldBeAddedToQueryCorrectly() {
public void metadataShouldBeAddedToCountQueryCorrectly() {

MongoQueryFake query = createQueryForMethod("findByFirstname", String.class, Pageable.class);
query.execute(new Object[] { "fake", new PageRequest(0, 10) });
query.execute(new Object[] { "fake", new PageRequest(1, 10) });

ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);

Expand Down
Loading