Lesson 5
Pagination and Sorting with Spring Data JPA
Introduction

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.

Why Pagination and Sorting are needed?

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.

Adding Pagination and Sorting

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 TypeMethodDescription
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.

Pagination and Sorting in Derived Methods

It's also possible to pass Pageable and Sort parameters to the derived methods. Consider this snippet:

Java
1package 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.

Sorting

Creating a Sort object can be done in various ways:

Java
1import 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.

Pagination

To create a Pageable object, you can use different methods provided by the PageRequest class:

Java
1import 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:

Java
1Pageable 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.

Controller

Having discussed how to create Pageable and Sort objects, let's implement a new method in our TodoController to handle paginated and sorted requests:

Java
1package 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 and size are required for pagination, while sortBy is optional for sorting.
  • var sort = Sort.by(sortBy != null ? sortBy : "title").ascending();: Creates a Sort object that defaults to sorting by title if sortBy is not provided.
  • var pageable = PageRequest.of(page, size, sort);: Creates a PageRequest 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.

Summary

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.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.