Marcin Wisnicki
Marcin Wisnicki

Reputation: 4701

Pagination with mongoTemplate

I have a Query with Pageable:

Query query = new Query().with(new PageRequests(page, size))

How can I execute it with MongoTemplate ? I don't see a single method returning Page<T>.

Upvotes: 40

Views: 58235

Answers (8)

anandsingh
anandsingh

Reputation: 11

To execute a Query with Pageable using MongoTemplate, you can use the find(Query, Class, String) method and then convert the result to a Page. Here's how you can do it:

public Page<YourEntity> executeQuery(Query query, Pageable pageable) {
    long totalCount = mongoTemplate.count(query, YourEntity.class);
    query.with(pageable);
    List<YourEntity> resultList = mongoTemplate.find(query, YourEntity.class);
    return PageableExecutionUtils.getPage(resultList, pageable, () -> totalCount);
}

Upvotes: 0

supik
supik

Reputation: 623

All answers so far are executing the query twice. This is not normally what you want. Here's a solution using aggregations to run it only once.

public class Result<T> {
    int totalCount;
    List<T> objects;
}

public <T, R extends Result<T>> R executePaged(Class<T> inputType,
                                               Criteria criteria,
                                               Pageable pageable,
                                               Class<R> resultType) {
    var match = Aggregation.match(criteria);
    var facets = Aggregation
            .facet(
                    Aggregation.sort(pageable.getSort()),
                    Aggregation.skip(pageable.getOffset()),
                    Aggregation.limit(pageable.getPageSize())
            ).as("objects")
            .and(
                    Aggregation.count().as("count")
            ).as("countFacet");
    var project = Aggregation
            .project("objects")
            .and("$countFacet.count").arrayElementAt(0).as("totalCount");

    var aggregation = Aggregation.newAggregation(match, facets, project);
    return mongoTemplate.aggregate(aggregation, inputType, resultType)
            .getUniqueMappedResult();
}

Notes:

  • Extend and parameterize Result<T> to pass as resultType, otherwise the deserializer doesn't know what class to use
  • pageable must be created with a Sort
  • If desired, you can still create a PageImpl afterwards

Example:

class Person {
    String name;
    Date birthdate;
}

class PersonResult extends Result<Person>{}

PersonResult result = executePaged(Person.class,
      Criteria.where("name").is("John"),
      PageRequest.of(3, 50).withSort(Sort.by("birthdate")),
      PersonResult.class);

System.out.println("Total #Johns: " + result.totalCount);
System.out.println("Johns on this page: " + result.objects);

Upvotes: 6

keemsisi
keemsisi

Reputation: 419

None of the solutions provided here worked in my own case. I tried using this solution below from a medium post and it has never returned the paged results but returns ALL the results which is not what I am expecting

return PageableExecutionUtils.getPage(
        mongoTemplate.find(query, ClassName.class),
        pageable,
        () -> mongoTemplate.count(query.skip(0).limit(0), ClassName.class)
);

So I found out a better way to go about it and it worked in my case:

return PageableExecutionUtils.getPage(
            mongoTemplate.find(query.with(pageable), ClassName.class),
            pageable,
            () -> mongoTemplate.count(query, ClassName.class));

Upvotes: 1

Esaldino Fonseca
Esaldino Fonseca

Reputation: 329

By default, spring mongo template has no method to find by page. It searches, and returns the whole list of records. I Tried this, and It worke:

Pageable pageable = new PageRequests(0, 10);                              
Query query = new Query(criteria); 
query.with(pageable);   
List<User> lusers = mt.find(query, User.class);   
Page<User> pu = new PageImpl<>(lusers, pageable, mongoTemplate.count(newQuery(criteria), User.class));

Upvotes: 4

velastiqui
velastiqui

Reputation: 79

return type Mono<Page<Myobject>>...

return this.myobjectRepository.count()
        .flatMap(ptiCount -> {
          return this.myobjectRepository.findAll(pageable.getSort())
            .buffer(pageable.getPageSize(),(pageable.getPageNumber() + 1))
            .elementAt(pageable.getPageNumber(), new ArrayList<>())
            .map(ptis -> new PageImpl<Myobject>(ptis, pageable, ptiCount));
        });

Upvotes: -1

d0x
d0x

Reputation: 11601

It's true that the MongoTemplate doesn't have findXXX with Pageables.

But you can use the Spring Repository PageableExecutionUtils for that.

In your example it would look like this:

Pageable pageable = new PageRequests(page, size);
Query query = new Query().with(pageable);
List<XXX> list = mongoTemplate.find(query, XXX.class);
return PageableExecutionUtils.getPage(
                       list, 
                       pageable, 
                       () -> mongoTemplate.count(Query.of(query).limit(-1).skip(-1), XXX.class));

Like in the original Spring Data Repository, the PageableExecutionUtils will do a count request and wrap it into a nice Page for you.

Here you can see that spring is doing the same.

Upvotes: 87

Razzlero
Razzlero

Reputation: 1193

Based on d0x's answer and looking at the spring code. I'm using this variation which works off the spring-boot-starter-data-mongodb dependency without needing to add spring data commons.

@Autowired
private MongoOperations mongoOperations;

@Override
public Page<YourObjectType> searchCustom(Pageable pageable) {
    Query query = new Query().with(pageable);
    // Build your query here

    List<YourObjectType> list = mongoOperations.find(query, YourObjectType.class);
    long count = mongoOperations.count(query, YourObjectType.class);
    Page<YourObjectType> resultPage = new PageImpl<YourObjectType>(list , pageable, count);
    return resultPage;
}

Upvotes: 22

Ori Dar
Ori Dar

Reputation: 19020

MongoTemplate does not have methods to return Page. The find() methods return an ordinary List.

with(new PageRequests(page, size) is used internally to adjust skip and limit with a MongoDB query (proceeded by a count query I think)

Page can be used in conjunction with MongoDB repositories which is a specialized case of Spring data repositories.

Thus, you'll have to use MongoRepository's Page findAll(Pageable pageable) for paginated results (actually inherited from PagingAndSortingRepository).

Upvotes: 20

Related Questions