Is there any way to implement pagination in spring webflux and spring data reactive

23.1k views Asked by At

I'm trying to understand reactive part of spring 5. I have created simple rest endpoint for finding all entities using spring web-flux and spring data reactive (mongo) but don't see any way how to implement pagination.

Here is my simple example in Kotlin:

@GetMapping("/posts/")
fun getAllPosts() = postRepository.findAll()

Does it mean that reactive endpoint does not require pagination? Is some way to implement pagination from server side using this stack?

3

There are 3 answers

1
Christoph Strobl On BEST ANSWER

The reactive support in Spring Data does not provide means of a Page return type. Still, the Pageable parameter is supported in method signatures passing on limit and offset to the drivers and therefore the store itself, returning a Flux<T> that emits the range requested.

Flux<Person> findByFirstname(String firstname, Pageable pageable);

For more information please have a look at the current Reference Documentation for 2.0.RC2 and the Spring Data Examples.

0
Ferney E. Barón On

This is not efficient but it works for me while I look for another solution

Service

public Page<Level> getPage(int page, int size, Sort.Direction direction, String properties) {
    var pageRequest = PageRequest.of(page, size, direction, properties);
    var count = levelRepository.count().block();
    var levels = levelRepository.findAllLevelsPaged(pageRequest).collectList().block();
    return new PageImpl<>(Objects.requireNonNull(levels), pageRequest, Objects.requireNonNull(count));
}

Repo

@Repository
public interface LevelRepository extends ReactiveMongoRepository<Level, String> {

    @Query("{ id: { $exists: true }}")
    Flux<Level> findAllLevelsPaged(final Pageable page);

}

Ref example

9
Hantsy On

Flux provides skip and take methods to get pagination support, and you also can use filter and sort to filter and sort the result. The filter and sort below is not a good example, but use skip and Pageable as 2nd parameter are no different.

The following codes work for me.

@GetMapping("")
public Flux<Post> all(
//@RequestParam(value = "q", required = false) String q,
                      @RequestParam(value = "page", defaultValue = "0") long page,
                      @RequestParam(value = "size", defaultValue = "10") long size) {
    return this.postRepository.findAll()
        //.filter(p -> Optional.ofNullable(q).map(key -> p.getTitle().contains(key) || p.getContent().contains(key)).orElse(true))//(replace this with query parameters)
        .sort(comparing(Post::getCreatedDate).reversed())
        .skip(page * size).take(size);
}

Update: The underlay drivers should be responsible for handling the result in the reactivestreams way.

And as you see in the answer from Christoph, if using a findByXXX method, Spring Data Mongo Reactive provides a variant to accept a pageable argument, but the findAll(reactive version) does not include such a variant, you have to do skip in the later operations if you really need the pagination feature. When switching to Flux instead of List, imagine the data in Flux as living water in the rivers or oil in the pipes, or the tweets in twitter.com.

I have tried to compare the queries using Pageale and not in the following case.

this.postRepository.findByTitleContains("title")
                .skip(0)
                .limitRequest(10)
                .sort((o1, o2) -> o1.getTitle().compareTo(o2.getTitle()))


this.postRepository.findByTitleContains("title", PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "title")))

When enabling logging for logging.level.org.springframework.data.mongodb.core.ReactiveMongoTemplate=DEBUG and found they print the same log for queries.

 find using query: { "title" : { "$regularExpression" : { "pattern" : ".*title.*", "options" : ""}}} fields: Document{{title=1}} for class: class com.example.demo.Post in collection: post

//other logging...

 find using query: { "title" : { "$regularExpression" : { "pattern" : ".*title.*", "options" : ""}}} fields: Document{{title=1}} for class: class com.example.demo.Post in collection: post

Keep in mind, all these operations should be DELEGATED to the underlay R2dbc drivers which implemented the reactive streams spec and performed on the DB side, NOT in the memory of your application side.

Check the example codes.

The early sample code I provided above maybe is not a good sample of filter and sort operations(MongoDB itself provides great regularexpression operations for it). But pagination in the reactive variant is not a good match with the concept in the reactive stream spec. When embracing Spring reactive stack, most of the time, we just move our work to a new collection of APIs. In my opinion, the realtime update and elastic response scene could be better match Reactive, eg. using it with SSE, Websocket, RSocket, application/stream+json(missing in the new Spring docs) protocols, etc