Welcome to the final lesson on Spring Data JPA. In this lesson, we will cover Pagination and Sorting. In previous lessons of this course, we delved into the JpaRepository
interface and Entity classes, explored derived queries and custom query methods, and established entity relationships. Today, we'll extend our Todo application by incorporating pagination and sorting features, which are crucial for managing large datasets and enhancing user experience. By the end of this lesson, you'll be adept at implementing pagination and sorting in your Spring Boot applications, optimizing both efficiency and responsiveness.
Imagine your ToDo app goes viral, resulting in thousands of ToDo items. When a user requests todo items using GET /todos
, a few issues can arise:
- Your Spring Boot application will attempt to load all these items into memory at once for serialization and return, potentially causing an
OutOfMemoryError
and crashing the application. - Even if your application's heap size can handle all these objects, users may experience long wait times for all this data to be transferred over the internet and displayed on the UI.
These issues can be mitigated using pagination — a technique that divides data into manageable chunks. To implement this, controller endpoints can accept additional parameters like page
and pageSize
. For instance, the request GET /todos?page=1&pageSize=10
will retrieve the first 10 todos, and GET /todos?page=5&pageSize=10
will retrieve todos from 41 to 50.
Another helpful feature is specifying the order in which clients retrieve data. For example, GET /todos?sortBy=title
or GET /todos?sortBy=title&order=desc
allows users to sort data by title in ascending or descending order.
Of course, Spring Boot doesn't automatically understand how to process these query parameters and pass them to the database; this needs to be implemented by the developer. However, Spring Data JPA supports pagination and sorting, and you'll soon see how to implement these features.
To implement pagination and sorting, you primarily rely on methods already provided by the JpaRepository
interface, which extends the PagingAndSortingRepository
interface out of the box:
Modifier and Type | Method | Description |
---|---|---|
Page<T> | findAll(Pageable pageable) | Returns a Page of entities adhering to the restrictions defined in the Pageable object. |
Iterable<T> | findAll(Sort sort) | Returns all entities sorted according to the specified options in the Sort object. |
As you can see, the PagingAndSortingRepository
allows passing a Sort
object into the findAll
method to specify sorting criteria, or a Pageable
object (which can include both pagination and sorting information) to retrieve items in a paginated format.
It's also possible to pass Pageable
and Sort
parameters to the derived methods. Consider this snippet:
Java1package com.codesignal.repositories; 2 3import com.codesignal.entities.TodoItem; 4import org.springframework.data.domain.Page; 5import org.springframework.data.domain.Pageable; 6import org.springframework.data.domain.Sort; 7import org.springframework.data.jpa.repository.JpaRepository; 8 9public interface TodoRepository extends JpaRepository<TodoItem, Long> { 10 Page<TodoItem> findByIsCompleted(boolean isCompleted, Pageable pageable); 11 List<TodoItem> findByTitleContaining(String keyword, Sort sort); 12}
In the code snippet above, custom methods findByIsCompleted
and findByTitleContaining
accept optional Pageable
and Sort
objects to handle pagination and sorting respectively.
Creating a Sort
object can be done in various ways:
Java1import org.springframework.data.domain.Sort; 2 3Sort sortByTitleAsc = Sort.by("title").ascending(); 4Sort sortByDateDesc = Sort.by("date").descending(); 5Sort sortByMultipleFields = Sort.by("date").descending().and(Sort.by("title").ascending());
These Sort
objects can then be used independently or as part of a Pageable
object.
To create a Pageable
object, you can use different methods provided by the PageRequest
class:
Java1import org.springframework.data.domain.PageRequest; 2import org.springframework.data.domain.Pageable; 3 4Pageable firstPageWithTwoElements = PageRequest.of(0, 2); 5Pageable secondPageWithFiveElements = PageRequest.of(1, 5);
In PageRequest.of(0, 2)
, the first parameter (0
) indicates the page number (0-based index), and the second parameter (2
) specifies the number of items per page.
Now, let's see how to combine pagination with sorting:
Java1Pageable sortedByTitle = PageRequest.of(0, 10, Sort.by("title").ascending());
When you combine pagination and sorting, Spring Data JPA sorts all the items based on the specified criteria and then divides the sorted result into pages.
Having discussed how to create Pageable
and Sort
objects, let's implement a new method in our TodoController
to handle paginated and sorted requests:
Java1package com.codesignal.controllers; 2 3import com.codesignal.entities.TodoItem; 4import com.codesignal.repositories.TodoRepository; 5import org.springframework.beans.factory.annotation.Autowired; 6import org.springframework.data.domain.PageRequest; 7import org.springframework.data.domain.Sort; 8import org.springframework.web.bind.annotation.*; 9 10import java.util.List; 11 12@RestController 13public class TodoController { 14 15 @Autowired 16 private TodoRepository todoRepository; 17 18 @GetMapping("/todos/paged") 19 public List<TodoItem> getPagedTodos(@RequestParam int page, @RequestParam int size, @RequestParam(required = false) String sortBy) { 20 var sort = Sort.by(sortBy != null ? sortBy : "title").ascending(); 21 var pageable = PageRequest.of(page, size, sort); 22 return todoRepository.findByIsCompleted(false, pageable).getContent(); 23 } 24}
Let's break down the getPagedTodos
method:
@GetMapping("/todos/paged")
: Maps HTTP GET requests to/todos/paged
to this method.public List<TodoItem> getPagedTodos(@RequestParam int page, @RequestParam int size, @RequestParam(required = false) String sortBy)
: The method accepts three query parameters:page
andsize
are required for pagination, whilesortBy
is optional for sorting.var sort = Sort.by(sortBy != null ? sortBy : "title").ascending();
: Creates aSort
object that defaults to sorting bytitle
ifsortBy
is not provided.var pageable = PageRequest.of(page, size, sort);
: Creates aPageRequest
object combining pagination and sorting.return todoRepository.findByIsCompleted(false, pageable).getContent();
: Fetches non-completed Todo items from the repository, applying pagination and sorting, and returns the content.
This implementation allows users to request paginated and sorted results via URL query parameters. For example, GET /todos/paged?page=0&size=10&sortBy=title
retrieves the first page of 10 non-completed Todo items, sorted by title in ascending order.
In this final lesson, we explored the necessity of pagination and sorting, updated the TodoRepository
to support these features, and introduced different ways to create Pageable
and Sort
objects. Finally, we implemented a controller method that handles paginated and sorted requests, enhancing our application's ability to manage large datasets effectively. Next, practice the concepts covered to consolidate your understanding and application of pagination and sorting in Spring Data JPA.