'Is there any way to implement pagination in spring webflux and spring data reactive
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?
Solution 1:[1]
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.
Solution 2:[2]
Flux provides skip
and take
methods to get pagination support, and you also can use The filter and sort below is not a good example, but use filter
and sort
to filter and sort the result.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
Solution 3:[3]
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Christoph Strobl |
Solution 2 | |
Solution 3 | Ferney E. BarĂ³n |