In previous post, we’ve known how to build Spring Boot Rest CRUD Apis with Spring Data MongoDB. In this tutorial, I will continue to make Pagination (and Filter) with Spring Data MongoDB and Pageable.
Related Post:
– Spring Boot MongoDB CRUD example
– Spring Boot @ControllerAdvice & @ExceptionHandler example
More Practice:
– Spring Boot, MongoDB: JWT Authentication with Spring Security
– Spring Boot Unit Test for Rest Controller
– Documentation: Spring Boot Swagger 3 example
– Caching: Spring Boot Redis Cache example
– Validation: Validate Request Body in Spring Boot
– Dockerize: Docker Compose: MongoDB and Spring Boot example
Clients for this Server:
– React with Material-UI / React with react-table v7
– Angular 8 / Angular 10 / Angular 11 / Angular 12 / Angular 13 / Angular 14 / Angular 15
– Vue with Bootstrap / Vuetify
Contents
Overview of Spring Boot MongoDB Pagination example
One of the most important things to make a website friendly is the response time, and pagination comes for this reason. For example, this bezkoder.com website has hundreds of tutorials, and we don’t want to see all of them at once. Paging means displaying a small number of all, by a page.
Assume that we have tutorials collection in MongoDB database like this:
Here are some url samples for pagination (with/without filter):
/api/tutorials?page=1&size=3
/api/tutorials?size=5
: using default value for page/api/tutorials?title=data&page=1&size=5
: pagination & filter by title containing ‘data’/api/tutorials/published?page=2
: pagination & filter by ‘published’ status
This is structure of the result that we want to get from the APIs:
{
"totalItems": 12,
"tutorials": [...],
"totalPages": 3,
"currentPage": 1
}
Read Tutorials with default page index (0) and page size (3):
Indicate page index = 1 but not specify size (default: 3):
Indicate size = 5 but not specify page index (default: 0):
For page index = 1 and page size = 5 (in total 12 items):
Pagination and filter by title that contains a string:
Pagination and filter by published status:
Pagination and Filter with Spring Data
To help us deal with this situation, Spring Data provides way to implement pagination with PagingAndSortingRepository.
PagingAndSortingRepository
extends CrudRepository to provide additional methods to retrieve entities using the pagination abstraction.
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Page<T> findAll(Pageable pageable);
}
findAll(Pageable pageable)
: returns a Page
of entities meeting the paging condition provided by Pageable
object.
Spring Data also supports many useful Query Creation from method names that we’re gonna use to filter result in this example such as:
Page<Tutorial> findByPublished(boolean published, Pageable pageable);
Page<Tutorial> findByTitleContainingIgnoreCase(String title, Pageable pageable);
You can find more supported keywords inside method names here.
Spring Data Page
Let’s look at the Page object.
Page
is a sub-interface of Slice
with a couple of additional methods. It contains total amount of elements and total pages of the entire list.
public interface Page<T> extends Slice<T> {
static <T> Page<T> empty();
static <T> Page<T> empty(Pageable pageable);
long getTotalElements();
int getTotalPages();
<U> Page<U> map(Function<? super T,? extends U> converter);
}
If the number of items increases, the performance could be affected, it’s the time you should think about Slice.
A Slice
object knows less information than a Page
, for example, whether the next one or previous one is available or not, or this slice is the first/last one. You can use it when you don’t need the total number of items and total pages.
public interface Slice<T> extends Streamable<T> {
int getNumber();
int getSize();
int getNumberOfElements();
List<T> getContent();
boolean hasContent();
Sort getSort();
boolean isFirst();
boolean isLast();
boolean hasNext();
boolean hasPrevious();
...
}
Spring Data Pageable
Now we’re gonna see the Pageable parameter in Repository methods above. Spring Data infrastructure will recognizes this parameter automatically to apply pagination and sorting to database.
The Pageable
interface contains the information about the requested page such as the size and the number of the page.
public interface Pageable {
int getPageNumber();
int getPageSize();
long getOffset();
Sort getSort();
Pageable next();
Pageable previousOrFirst();
Pageable first();
boolean hasPrevious();
...
}
So when we want to get pagination (with or without filter) in the results, we just add Pageable
to the definition of the method as a parameter.
Page<Tutorial> findAll(Pageable pageable);
Page<Tutorial> findByPublished(boolean published, Pageable pageable);
Page<Tutorial> findByTitleContainingIgnoreCase(String title, Pageable pageable);
This is how we create Pageable
objects using PageRequest class which implements Pageable
interface:
Pageable paging = PageRequest.of(page, size);
page
: zero-based page index, must NOT be negative.size
: number of items in a page to be returned, must be greater than 0.
Create Spring Boot Application
You can follow step by step, or get source code in this post:
Spring Boot MongoDB CRUD example
The Spring Project contains structure that we only need to add some changes to make the pagination work well.
Or you can get the new Github source code at the end of this tutorial.
Data Model
We have Tutorial class that is annotated with @Document(collection = "tutorials")
like this:
package com.bezkoder.spring.data.mongodb.pagination.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "tutorials")
public class Tutorial {
@Id
private String id;
private String title;
private String description;
private boolean published;
public Tutorial() {
}
public Tutorial(String title, String description, boolean published) {
this.title = title;
this.description = description;
this.published = published;
}
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isPublished() {
return published;
}
public void setPublished(boolean isPublished) {
this.published = isPublished;
}
@Override
public String toString() {
return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
}
}
Repository that supports Pagination and Filter
Early in this tutorial, we know PagingAndSortingRepository
, but in this example, for keeping the continuity and taking advantage Spring Data MongoDB, we continue to use MongoRepository which extends PagingAndSortingRepository
interface.
package com.bezkoder.spring.data.mongodb.pagination.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.bezkoder.spring.data.mongodb.pagination.model.Tutorial;
public interface TutorialRepository extends MongoRepository<Tutorial, String> {
Page<Tutorial> findByPublished(boolean published, Pageable pageable);
Page<Tutorial> findByTitleContainingIgnoreCase(String title, Pageable pageable);
}
In the code above, we use add pageable
parameter with Spring Data MongoDB Query Creation to find all Tutorials which title containing input string (case insensitive).
Controller with Pagination and Filter
Generally, in the HTTP request URLs, paging parameters are optional. So if our Spring Boot Rest API supports pagination, we should provide default values to make paging work even when Client does not specify these parameters.
package com.bezkoder.spring.data.mongodb.pagination.controller;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
...
@RestController
@RequestMapping("/api")
public class TutorialController {
@Autowired
TutorialRepository tutorialRepository;
@GetMapping("/tutorials")
public ResponseEntity<Map<String, Object>> getAllTutorialsPage(
@RequestParam(required = false) String title,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "3") int size) {
try {
List<Tutorial> tutorials = new ArrayList<Tutorial>();
Pageable paging = PageRequest.of(page, size);
Page<Tutorial> pageTuts;
if (title == null)
pageTuts = tutorialRepository.findAll(paging);
else
pageTuts = tutorialRepository.findByTitleContainingIgnoreCase(title, paging);
tutorials = pageTuts.getContent();
Map<String, Object> response = new HashMap<>();
response.put("tutorials", tutorials);
response.put("currentPage", pageTuts.getNumber());
response.put("totalItems", pageTuts.getTotalElements());
response.put("totalPages", pageTuts.getTotalPages());
return new ResponseEntity<>(response, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/tutorials/published")
public ResponseEntity<Map<String, Object>> findByPublished(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "3") int size) {
try {
List<Tutorial> tutorials = new ArrayList<Tutorial>();
Pageable paging = PageRequest.of(page, size);
Page<Tutorial> pageTuts = tutorialRepository.findByPublished(true, paging);
tutorials = pageTuts.getContent();
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
Map<String, Object> response = new HashMap<>();
response.put("tutorials", tutorials);
response.put("currentPage", pageTuts.getNumber());
response.put("totalItems", pageTuts.getTotalElements());
response.put("totalPages", pageTuts.getTotalPages());
return new ResponseEntity<>(response, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
...
}
In the code above, we accept paging parameters using @RequestParam
annotation for page
, size
. By default, 3
Tutorials will be fetched from database in page index 0
.
Next, we create a Pageable
object with page
& size
.
Then check if the title
parameter exists or not.
- If it is null, we call Repository
findAll(paging)
with paging is thePageable
object above. - If Client sends request with
title
, usefindByTitleContaining(title, paging)
.
Both methods return a Page
object. We call:
getContent()
to retrieve the List of items in the page.getNumber()
for current Page.getTotalElements()
for total items stored in database.getTotalPages()
for number of total pages.
Conclusion
In this post, we have learned how to create a Spring Booot MongoDB pagination and filter collection using Spring Data MongoDB, Page and Pageable interface.
We also see that MongoRepository
supports a great way to make pagination and filter methods without need of boilerplate code.
Handle Exception for this Rest APIs is necessary:
– Spring Boot @ControllerAdvice & @ExceptionHandler example
– @RestControllerAdvice example in Spring Boot
React Pagination Client that works with this Server:
– React Table Pagination using react-table v7
– React Pagination with API using Material-UI
Angular Client working with this server:
– Angular 8 Pagination example
– Angular 10 Pagination example
– Angular 11 Pagination example
– Angular 12 Pagination example
– Angular 13 Pagination example
– Angular 14 Pagination example
– Angular 15 Pagination example
Or Vue Client:
– Vue Pagination example (Bootstrap)
– Vuetify Pagination example
Happy learning! See you again.
Further Reading
- Spring Data MongoDB Reference Documentation
- Spring Boot, MongoDB: JWT Authentication with Spring Security
- Spring Boot @ControllerAdvice & @ExceptionHandler example
Fullstack CRUD App:
– Angular 8 + Spring Boot + MongoDB example
– Angular 10 + Spring Boot + MongoDB example
– Angular 11 + Spring Boot + MongoDB example
– Angular 12 + Spring Boot + MongoDB example
– Angular 13 + Spring Boot + MongoDB example
– Angular 14 + Spring Boot + MongoDB example
– Angular 15 + Spring Boot + MongoDB example
– React + Spring Boot + MongoDB example
– Vue + Spring Boot + MongoDB example
Unit Test: Spring Boot Unit Test for Rest Controller
Source Code
You can find the complete source code for this tutorial on Github.
Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
Caching: Spring Boot Redis Cache example
Validation: Validate Request Body in Spring Boot
Dockerize: Docker Compose: MongoDB and Spring Boot example
This was one of the best explanations and tutorials on pagination with Springboot/MongoDb. Thank you very much!
How to use 2 filter fields example (a,b,page) and do reverse pagination
Thanks
Thanks for the Springboot tutorial.