Example Spring Data JPA Paging and Sorting : Throughout this tutorial, I will guide you to code paging and sorting functionalities for an existing Spring Boot application using Spring Data JPA. As you know, pagination allows the users to see a small portion of data at a time (a page), and sorting allows the users to view the data in a more organized way. Both paging and sorting help the users consume information more easily and conveniently.

1. Understand Spring Data JPA’s Pagination APIs

To use paging and sorting APIs provided by Spring Data JPA, your repository interface must extend the PagingAndSortingRepository interface which defines the following couple of methods (T refers to an entity class):

Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);

Note that JpaRepository is a subtype of PagingAndSortingRepository so if your repository interface is of type JpaRepository, you don’t have to make change to it.

The following code example gets the first page from the database, with 10 items per page:

int pageNumber = 1;
int pageSize = 10;
Pageable pageable = PageRequest.of(pageNumber, pageSize);
 
Page<Product> page = repository.findAll(pageable);

Then you can get the actual content as follows:

List<Product> listProducts = page.getContent();

With a Page object you can know the total rows in the database and the total pages according to the given page size:

long totalItems = page.getTotalElements();
int totalPages = page.getTotalPages();

This information is useful for implementing pagination in the view with Thymeleaf template.

2. Implement Pagination

Now, let’s update the ProductManger project to add pagination for the products list.

Update Service class for paging

Prior to pagination, the method that lists all products is implemented in the ProductService class is as follows:

@Service
public class ProductService {
    @Autowired
    private ProductRepository repo;
     
    public List<Product> listAll() {
        return repo.findAll();
    }  
}

Now, to implement paging function, update this method as follows:

public Page<Product> listAll(int pageNum) {
    int pageSize = 5;
     
    Pageable pageable = PageRequest.of(pageNum - 1, pageSize);
     
    return repo.findAll(pageable);
}

As you can see, we update the listAll() method to takes a parameter for page number which will be passed from the controller. And this method returns a Page<Product> instead of a List<Product>.

Note that pagination APIs considers page number is 0-based. In the view, we use 1-based page number for the users, but in the code we need to convert to 0-based page number, hence you see pageNum – 1 as above.

Update Controller class for paging

Add a new method in the Spring MVC controller class to handle requests to see a specific page of products, with the following code:

@RequestMapping("/page/{pageNum}")
public String viewPage(Model model,
        @PathVariable(name = "pageNum") int pageNum) {
     
    Page<Product> page = service.listAll(pageNum);
     
    List<Product> listProducts = page.getContent();
     
    model.addAttribute("currentPage", pageNum);
    model.addAttribute("totalPages", page.getTotalPages());
    model.addAttribute("totalItems", page.getTotalElements());
    model.addAttribute("listProducts", listProducts);
     
    return "index";
}

As you can see, the page number is added to the URL like this: /page/1, page/2, /page/3…

Beside a List of Products object, we also store 3 additional attributes in the model to be used for pagination: currentPage, totalPages and totalItems.

And modify the handler method for the home page to display the first page as follows:

@RequestMapping("/")
public String viewHomePage(Model model) {
    return viewPage(model, 1);
}

Update Thymeleaf template view for paging

We will be updating the view to show navigation links for pagination like First, Previous, page numbers from 1 to total pages, Next and Last.

Now, for the view template (index.html), we show the total number of rows using Thymeleaf expression as follows:

Total Items: [[${totalItems}]]

To display a hyperlink that allows the user to navigate to the first page:

<a th:if="${currentPage > 1}" th:href="/@{'/page/1'}">First</a>
<span th:unless="${currentPage > 1}">First</span>

Note that if the current page is 1, then it is showing a text – not a link.

Code to show the link to navigate to the previous page:

<a th:if="${currentPage > 1}" th:href="/@{'/page/' + ${currentPage - 1}}">Previous</a>
<span th:unless="${currentPage > 1}">Previous</span>

To display links that allow the users to navigate to a specific page, in the range from page 1 to total pages, write the code as follows: 

<span th:each="i: ${#numbers.sequence(1, totalPages)}">
    <a th:if="${currentPage != i}" th:href="/@{'/page/' + ${i}}">[[${i}]]</a>
    <span th:unless="${currentPage != i}">[[${i}]]</span>
     
</span>

Code to show the Next page hyperlink:

<a th:if="${currentPage < totalPages}" th:href="/@{'/page/' + ${currentPage + 1}}">Next</a>
<span th:unless="${currentPage < totalPages}">Next</span>

And the code to display the Last page hyperlink:

<a th:if="${currentPage < totalPages}" th:href="/@{'/page/' + ${totalPages}}">Last</a>
<span th:unless="${currentPage < totalPages}">Last</span>

Testing pagination

Suppose that we have 15 rows in the products table, then we would see the home page as follows:

See the navigation links for pagination at the bottom of the page. You can click the links First, Previous, a specific page number, Next and Last to test the pagination.

3. Understand Spring Data JPA’s Sorting APIs

Next, I will guide you to implement sorting functionality in conjunction with pagination. The users will be able to sort the products list by clicking on column header of the table.

First, create a Sort object like this:

Sort sort = Sort.by(“fieldName”).ascending();

This will sort the result by fieldName in ascending order. fieldName must match a field name declared in the entity class. We can also sort by more than one field, for example:

Sort sort = Sort.by("brand").ascending().
        and(Sort.by("price").descending());

This will sort the products list by brand column first in ascending order, then by price in descending order.

Then we pass the Sort object to create a Pageable as follows:

Pageable pageable = PageRequest.of(pageNum - 1, pageSize, sort);
Page<Product> page = repo.findAll(pageable);

So as you can see, we can apply sorting alongside with paging neatly.

4. Implement Sorting

Now, let’s update the ProjectManager project to implement sorting, in addition to the pagination we’ve done previously. For simplicity, we allow the users to sort the products list by a single field only.

Update Service class for sorting

Modify the listAll() method in the ProductService class to take sortField and sortDir as additional parameters, as follows:

public Page<Product> listAll(int pageNum, String sortField, String sortDir) {
    int pageSize = 5;
    Pageable pageable = PageRequest.of(pageNum - 1, pageSize,
            sortDir.equals("asc") ? Sort.by(sortField).ascending()
                                              : Sort.by(sortField).descending()
    );
     
    return repo.findAll(pageable);
}

This allows us to parameterize the sort field and sort direction.

Update Controller class for sorting

Next, update the viewPage() handler method in the controller class to read sort field and sort direction from query parameters in URL as follows:

@RequestMapping("/page/{pageNum}")
public String viewPage(Model model,
        @PathVariable(name = "pageNum") int pageNum,
        @Param("sortField") String sortField,
        @Param("sortDir") String sortDir) {
     
    Page<Product> page = service.listAll(pageNum, sortField, sortDir);
     
    List<Product> listProducts = page.getContent();
     
    model.addAttribute("currentPage", pageNum);    
    model.addAttribute("totalPages", page.getTotalPages());
    model.addAttribute("totalItems", page.getTotalElements());
     
    model.addAttribute("sortField", sortField);
    model.addAttribute("sortDir", sortDir);
    model.addAttribute("reverseSortDir", sortDir.equals("asc") ? "desc" : "asc");
     
    model.addAttribute("listProducts", listProducts);
     
    return "index";
}

So the URL for paging and sorting would look like this:

      /page/1?sortField=name&sortDir=asc

We also store 3 additional attributes in the model to be used in the view for sorting: sortField, sortDir and reverseSortDir. The reverseSortDir attribute is used to toggle sorting order when the users click on a column header.

Modify the handler method for the homepage to show the first page of result which is sorted by name in ascending order by default:

@RequestMapping("/")
public String viewHomePage(Model model) {
    return viewPage(model, 1, "name", "asc");
}

Update Thymeleaf template view for sorting

We make the header columns of the table sortable by adding hyperlinks with the following code:

<th>
    <a th:href="/@{'/page/' + ${currentPage} + '?sortField=id&sortDir=' + ${reverseSortDir}}">Product ID</a>
</th>
<th>
    <a th:href="/@{'/page/' + ${currentPage} + '?sortField=name&sortDir=' + ${reverseSortDir}}">Name</a>
</th>
<th>
    <a th:href="/@{'/page/' + ${currentPage} + '?sortField=brand&sortDir=' + ${reverseSortDir}}">Brand</a>
</th>
<th>
    <a th:href="/@{'/page/' + ${currentPage} + '?sortField=madein&sortDir=' + ${reverseSortDir}}">Made In</a>
</th>
<th>
    <a th:href="/@{'/page/' + ${currentPage} + '?sortField=price&sortDir=' + ${reverseSortDir}}">Price</a>
</th>

Note that the sorting order in the hyperlink is in reverse to the current sorting direction to allow the user to toggle the direction (ascending/descending).

Add the following code to show the current field being sorted with which direction:

<div><i>Sorted by [[${sortField}]] in [[${sortDir}]] order</i></div>

For the navigation links for pagination, append the following value to each URL:

'?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}

This allows the users to navigate to different pages of the result while the sorting is still applied.

Test sorting and paging

Launch the application and refresh the homepage. You see the column headers of the table are now clickable, as shown in the following screenshot:

Click each column to test sorting and click navigation links at the bottom to test pagination alongside with sorting.

Conclusion:

So far you have learned to implement paging and sorting functionalities for an existing Spring Boot application using Spring Data JPA, Hibernate, Thymeleaf and MySQL database. You see Spring makes it easy, simple and convenient.

Laisser un commentaire