使用 Spring Boot、ThymeLeaf、Spring Data JPA、Hibernate、MySQL 进行分页和排序

在前面的教程中,我们使用Spring Boot和 Thymeleaf 开发了一个 CRUD Web 应用程序。

在本教程中,我们将扩展spring boot CRUD web 应用程序,并使用 spring boot、thymeleaf、spring data JPA、Hibernate 和 MySQL 数据库实现分页和排序操作。

如您所知,分页允许用户一次查看一小部分数据(一页),排序允许用户以更有条理的方式查看数据。分页和排序都可以帮助用户更轻松、更方便地消费信息。

让我们从可以从本教程下载的员工管理系统项目开始,该项目基于 Spring Boot、Spring Data JPA、Hibernate、Thymeleaf 和 MySQL 数据库。

先决条件 

项目要求

这是实现员工列表页面(员工管理系统) 的分页和排序的高级项目需求。 
用户应该能够:
  • 在员工列表页面上执行分页
  • 在员工列表页面上进行排序

我们将建造什么?

我们将扩展spring boot CRUD web 应用程序,并使用 spring boot、thymeleaf、spring data JPA、Hibernate 和 MySQL 数据库实现分页和排序。

下面的截图总结了我们将在本教程中开发的分页和排序操作。

分页:

 

排序:


首先,我们将逐步实现分页操作,然后我们将完成排序操作。

一、分页实现

了解 Spring Data JPA 的分页 API

要使用 Spring Data JPA 提供的分页和排序 API,您的存储库接口必须扩展 PagingAndSortingRepository 接口,该接口定义了以下几个方法(T 指的是实体类):
@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);
}

JpaRepository 接口扩展 了 PagingAndSortingRepository 接口,因此如果您的存储库接口的类型为 JpaRepository,则无需对其进行更改。
例如,使用以下命令从数据库中获取第一页,每页 5 个项目:
int pageNumber = 1;
int pageSize = 5;
Pageable pageable = PageRequest.of(pageNumber, pageSize);
 
Page<Product> page = repository.findAll(pageable);

然后就可以得到实际内容如下:
List<Employee> listEmployees = page.getContent();

使用 Page 对象,您可以根据给定的页面大小了解数据库中的总行数和总页数:
long totalItems = page.getTotalElements();
int totalPages = page.getTotalPages();

此信息对于使用 Thymeleaf 模板在视图中实现分页很有用。

分页的后端更改

EmployeeService.java 接口更改

在此接口中添加以下方法:
Page<Employee> findPaginated(int pageNo, int pageSize);

完整代码:
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);
}

EmployeeServiceImpl.java 类更改

将以下方法添加到 EmployeeServiceImpl 类:
@Override
public Page<Employee> findPaginated(int pageNo, int pageSize) {
 Pageable pageable = PageRequest.of(pageNo - 1, pageSize);
 return this.employeeRepository.findAll(pageable);
}

EmployeeController.java 类更改

将以下处理程序方法添加到 EmployeeController 类以执行分页:
@GetMapping("/page/{pageNo}")
public String findPaginated(@PathVariable(value = "pageNo") int pageNo, Model model) {
    int pageSize = 5;

    Page < Employee > page = employeeService.findPaginated(pageNo, pageSize);
    List < Employee > listEmployees = page.getContent();

    model.addAttribute("currentPage", pageNo);
    model.addAttribute("totalPages", page.getTotalPages());
    model.addAttribute("totalItems", page.getTotalElements());
    model.addAttribute("listEmployees", listEmployees);
    return "index";
}

此外,我们需要对现有方法进行更改,如下所示:
// display list of employees
@GetMapping("/")
public String viewHomePage(Model model) {
 return findPaginated(1, model);  
}

分页的前端更改

将以下分页代码添加到 index.html:
<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}}">[[${i}]]</a>
      <span th:unless="${currentPage != i}">[[${i}]]</span> &nbsp; &nbsp;
            </span>
        </div>
        <div class="col-sm-1">
            <a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${currentPage + 1}}">Next</a>
            <span th:unless="${currentPage < totalPages}">Next</span>
        </div>

        <div class="col-sm-1">
            <a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${totalPages}}">Last</a>
            <span th:unless="${currentPage < totalPages}">Last</span>
        </div>
    </div>
</div>

完整代码:
<!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>Employee First Name</th>
                    <th>Employee Last Name</th>
                    <th>Employee Email</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}}">[[${i}]]</a>
      <span th:unless="${currentPage != i}">[[${i}]]</span> &nbsp; &nbsp;
                    </span>
                </div>
                <div class="col-sm-1">
                    <a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${currentPage + 1}}">Next</a>
                    <span th:unless="${currentPage < totalPages}">Next</span>
                </div>

                <div class="col-sm-1">
                    <a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${totalPages}}">Last</a>
                    <span th:unless="${currentPage < totalPages}">Last</span>
                </div>
            </div>
        </div>
    </div>
</body>

</html>

2.排序实现

了解 Spring Data JPA 的排序 API

Spring Data JPA 提供了 PagingAndSortingRepository 接口,该接口支持使用以下 API 进行排序和分页:
@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);
}

用户将能够通过单击表格的列标题对员工列表进行排序。 
首先,像这样创建一个 Sort 对象:
Sort sort = Sort.by(“fieldName”).ascending();

这将按 fieldName 升序对结果进行排序。 fieldName 必须与实体类中声明的字段名称匹配。 
我们还可以按多个字段排序,例如:
Sort sort = Sort.by("fieldName1").ascending().and(Sort.by("fieldName2").descending());

然后我们通过 Sort 对象来创建一个 Pageable ,如下所示:
Pageable pageable = PageRequest.of(pageNum - 1, pageSize, sort);

让我们在示例部分了解更多。

用于排序的后端更改

EmployeeService.java 接口更改

让我们在现有方法中添加两个字段:
Page<Employee> findPaginated(int pageNo, int pageSize, String sortField, String sortDirection);

完整代码:
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);
}

EmployeeServiceImpl.java 类更改

以下方法中实现的排序逻辑:
@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);
}

完整代码:
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);
    }
}

EmployeeController.java 更改

让我们更改现有方法以提供对排序的支持:
@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";
}

还为主页提供默认的排序字段和排序方向:
// display list of employees
    @GetMapping("/")
    public String viewHomePage(Model model) {
        return findPaginated(1, "firstName", "asc", model);
    }

完整代码:
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";
    }
}

排序的前端更改

索引.html

我们通过使用以下代码添加超链接来使表格的标题列可排序:
<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>

我们还需要更改分页部分以提供排序支持,例如:
<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>  &nbsp; &nbsp;
     </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>

完整代码:
<!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>  &nbsp; &nbsp;
     </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>

GitHub 存储库链接

您可以从我的 GitHub 存储库下载/克隆本教程的源代码, 网址为 https://github.com/RameshMF/springboot-thymeleaf-crud-pagination-sorting-webapp.git
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值