📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
🎓 Top 15 Udemy Courses (80-90% Discount): My Udemy Courses - Ramesh Fadatare — All my Udemy courses are real-time and project oriented courses.
▶️ Subscribe to My YouTube Channel (176K+ subscribers): Java Guides on YouTube
▶️ For AI, ChatGPT, Web, Tech, and Generative AI, subscribe to another channel: Ramesh Fadatare on YouTube
In this PART 7, we will implement Sorting for our employee list page using Spring Data JPA and Thymeleaf.
Let's understand more in the example section.
← Previous Next → Use the below links to navigate different parts of this tutorial:
1. Spring Boot Thymeleaf CRUD Database Real-Time Project - PART 1- Create and Setup Spring Boot Project in Eclipse STS
- Database Setup
Understand Spring Data JPA’s Sorting API
The Spring Data JPA provides PagingAndSortingRepository interface which supports spring and pagination with the following APIs:
@NoRepositoryBean public interface PagingAndSortingRepository < T, ID > extends CrudRepository < T, ID > { /** * Returns all entities sorted by the given options. * * @param sort * @return all entities sorted by the given options */ Iterable < T > findAll(Sort sort); /** * Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object. * * @param pageable * @return a page of entities */ Page < T > findAll(Pageable pageable); }
The users will be able to sort the employee's list by clicking on the 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("fieldName1").ascending().and(Sort.by("fieldName2").descending());
Then we pass the Sort object to create a Pageable as follows:
Pageable pageable = PageRequest.of(pageNum - 1, pageSize, sort);
1. Back-end changes
1. EmployeeService.java interface changes
Let's add two fields to the existing method:
Page<Employee> findPaginated(int pageNo, int pageSize, String sortField, String sortDirection);
The complete code:
package net.javaguides.springboot.service; import java.util.List; import org.springframework.data.domain.Page; import net.javaguides.springboot.model.Employee; public interface EmployeeService { List < Employee > getAllEmployees(); void saveEmployee(Employee employee); Employee getEmployeeById(long id); void deleteEmployeeById(long id); Page < Employee > findPaginated(int pageNo, int pageSize, String sortField, String sortDirection); }
2. EmployeeServiceImpl.java class changes
The sorting logic implemented in the below method:
@Override public Page<Employee> findPaginated(int pageNo, int pageSize, String sortField, String sortDirection) { Sort sort = sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortField).ascending() : Sort.by(sortField).descending(); Pageable pageable = PageRequest.of(pageNo - 1, pageSize, sort); return this.employeeRepository.findAll(pageable); }
The complete code:
package net.javaguides.springboot.service; import java.util.List; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import net.javaguides.springboot.model.Employee; import net.javaguides.springboot.repository.EmployeeRepository; @Service public class EmployeeServiceImpl implements EmployeeService { @Autowired private EmployeeRepository employeeRepository; @Override public List < Employee > getAllEmployees() { return employeeRepository.findAll(); } @Override public void saveEmployee(Employee employee) { this.employeeRepository.save(employee); } @Override public Employee getEmployeeById(long id) { Optional < Employee > optional = employeeRepository.findById(id); Employee employee = null; if (optional.isPresent()) { employee = optional.get(); } else { throw new RuntimeException(" Employee not found for id :: " + id); } return employee; } @Override public void deleteEmployeeById(long id) { this.employeeRepository.deleteById(id); } @Override public Page < Employee > findPaginated(int pageNo, int pageSize, String sortField, String sortDirection) { Sort sort = sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortField).ascending() : Sort.by(sortField).descending(); Pageable pageable = PageRequest.of(pageNo - 1, pageSize, sort); return this.employeeRepository.findAll(pageable); } }
3.EmployeeController.java changes
Let's change the existing method to provide a support for sorting:
@GetMapping("/page/{pageNo}") public String findPaginated(@PathVariable(value = "pageNo") int pageNo, @RequestParam("sortField") String sortField, @RequestParam("sortDir") String sortDir, Model model) { int pageSize = 5; Page < Employee > page = employeeService.findPaginated(pageNo, pageSize, sortField, sortDir); List < Employee > listEmployees = page.getContent(); model.addAttribute("currentPage", pageNo); 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("listEmployees", listEmployees); return "index"; }
Also provide default sorting field and sorting direction for home page:
// display list of employees @GetMapping("/") public String viewHomePage(Model model) { return findPaginated(1, "firstName", "asc", model); }
The complete code:
package net.javaguides.springboot.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import net.javaguides.springboot.model.Employee; import net.javaguides.springboot.service.EmployeeService; @Controller public class EmployeeController { @Autowired private EmployeeService employeeService; // display list of employees @GetMapping("/") public String viewHomePage(Model model) { return findPaginated(1, "firstName", "asc", model); } @GetMapping("/showNewEmployeeForm") public String showNewEmployeeForm(Model model) { // create model attribute to bind form data Employee employee = new Employee(); model.addAttribute("employee", employee); return "new_employee"; } @PostMapping("/saveEmployee") public String saveEmployee(@ModelAttribute("employee") Employee employee) { // save employee to database employeeService.saveEmployee(employee); return "redirect:/"; } @GetMapping("/showFormForUpdate/{id}") public String showFormForUpdate(@PathVariable(value = "id") long id, Model model) { // get employee from the service Employee employee = employeeService.getEmployeeById(id); // set employee as a model attribute to pre-populate the form model.addAttribute("employee", employee); return "update_employee"; } @GetMapping("/deleteEmployee/{id}") public String deleteEmployee(@PathVariable(value = "id") long id) { // call delete employee method this.employeeService.deleteEmployeeById(id); return "redirect:/"; } @GetMapping("/page/{pageNo}") public String findPaginated(@PathVariable(value = "pageNo") int pageNo, @RequestParam("sortField") String sortField, @RequestParam("sortDir") String sortDir, Model model) { int pageSize = 5; Page < Employee > page = employeeService.findPaginated(pageNo, pageSize, sortField, sortDir); List < Employee > listEmployees = page.getContent(); model.addAttribute("currentPage", pageNo); 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("listEmployees", listEmployees); return "index"; } }
2. Front-end changes
1.index.html
We make the header columns of the table sortable by adding hyperlinks with the following code:
<th> <a th:href="@{'/page/' + ${currentPage} + '?sortField=firstName&sortDir=' + ${reverseSortDir}}"> Employee First Name</a> </th> <th> <a th:href="@{'/page/' + ${currentPage} + '?sortField=lastName&sortDir=' + ${reverseSortDir}}"> Employee Last Name</a> </th> <th> <a th:href="@{'/page/' + ${currentPage} + '?sortField=email&sortDir=' + ${reverseSortDir}}"> Employee Email</a> </th> <th> Actions </th>
We also need to change pagination part to provide sorting support like:
<div th:if = "${totalPages > 1}"> <div class = "row col-sm-10"> <div class = "col-sm-2"> Total Rows: [[${totalItems}]] </div> <div class = "col-sm-1"> <span th:each="i: ${#numbers.sequence(1, totalPages)}"> <a th:if="${currentPage != i}" th:href="@{'/page/' + ${i}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">[[${i}]]</a> <span th:unless="${currentPage != i}">[[${i}]]</span> </span> </div> <div class = "col-sm-1"> <a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${currentPage + 1}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">Next</a> <span th:unless="${currentPage < totalPages}">Next</span> </div> <div class="col-sm-1"> <a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${totalPages}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">Last</a> <span th:unless="${currentPage < totalPages}">Last</span> </div> </div> </div>
The complete code:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="ISO-8859-1"> <title>Employee Management System</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> </head> <body> <div class="container my-2"> <h1>Employees List</h1> <a th:href = "@{/showNewEmployeeForm}" class="btn btn-primary btn-sm mb-3"> Add Employee </a> <table border="1" class = "table table-striped table-responsive-md"> <thead> <tr> <th> <a th:href="@{'/page/' + ${currentPage} + '?sortField=firstName&sortDir=' + ${reverseSortDir}}"> Employee First Name</a> </th> <th> <a th:href="@{'/page/' + ${currentPage} + '?sortField=lastName&sortDir=' + ${reverseSortDir}}"> Employee Last Name</a> </th> <th> <a th:href="@{'/page/' + ${currentPage} + '?sortField=email&sortDir=' + ${reverseSortDir}}"> Employee Email</a> </th> <th> Actions </th> </tr> </thead> <tbody> <tr th:each="employee : ${listEmployees}"> <td th:text="${employee.firstName}"></td> <td th:text="${employee.lastName}"></td> <td th:text="${employee.email}"></td> <td> <a th:href="@{/showFormForUpdate/{id}(id=${employee.id})}" class="btn btn-primary">Update</a> <a th:href="@{/deleteEmployee/{id}(id=${employee.id})}" class="btn btn-danger">Delete</a> </td> </tr> </tbody> </table> <div th:if = "${totalPages > 1}"> <div class = "row col-sm-10"> <div class = "col-sm-2"> Total Rows: [[${totalItems}]] </div> <div class = "col-sm-1"> <span th:each="i: ${#numbers.sequence(1, totalPages)}"> <a th:if="${currentPage != i}" th:href="@{'/page/' + ${i}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">[[${i}]]</a> <span th:unless="${currentPage != i}">[[${i}]]</span> </span> </div> <div class = "col-sm-1"> <a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${currentPage + 1}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">Next</a> <span th:unless="${currentPage < totalPages}">Next</span> </div> <div class="col-sm-1"> <a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${totalPages}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">Last</a> <span th:unless="${currentPage < totalPages}">Last</span> </div> </div> </div> </div> </body> </html>
Comments
Post a Comment
Leave Comment