Spring Data JPA 分页和排序示例

  • by

本文将介绍使用 Spring Data JPA 对现有 Spring Boot 应用程序实现分页和排序的功能。分页可以使用户一次看到一小部分数据,而排序可以使用户以更有条理的方式查看数据。分页和排序都可以帮助用户更轻松,方便地使用信息。

下面将从 ProductManager 项目开始,该项目基于 Spring Data JPA,Hibernate,Thymeleaf 和 MySQL 数据库。

了解 Spring Data JPA 分页 API

要使用 Spring Data JPA 提供的分页和排序 API,存储库接口必须扩展 PagingAndSortingRepository 接口,该接口定义了以下两种方法(T 表示实体类):

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

JpaRepository 是 PagingAndSortingRepository 的子类,因此,如果存储库接口为 JpaRepository 类型,则无需对其进行更改。

以下代码示例从数据库中获取第一页,每页 10 个项目:

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

然后可以获取实际内容:

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

使用 Page 对象,可以根据给定的页面大小了解数据库中的总行数和总页数:

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

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

实现分页

现在,更新 ProductManger 项目,为产品列表添加分页。

更新服务类实现分页功能

在实现分页之前,ProductService 类中实现列出所有产品的方法如下:

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

要实现分页功能,按以下方式更新此方法:

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

我们将 listAll() 方法更新为带有页码参数的方法,该参数将从控制器传入。 并且此方法返回 Page <Product> 而不是 List <Product>。

注意,分页 API 认为页码是基于 0 的。 在视图中,我们为用户使用基于 1 的页码,所以会看到如上的 pageNum – 1。

更新控制器类实现分页功能

在 Spring MVC 控制器类中添加一个新方法来处理查看特定产品页面的请求:

@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";
}

页码按如下 URL 添加:/page/1,page/2,/page/3…

除了 Product 列表对象,我们还在模型中存储了 3 个用于分页的其他属性:currentPage,totalPages 和 totalItems。

并修改主页的处理程序方法以显示第一页:

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

更新 Thymeleaf 视图实现分页功能

更新视图以显示分页导航链接,例如“First”,“Previous”,从 1 到总页数的页码,“Next”和“Last”。

现在,在 index.html 中使用 Thymeleaf 表达式显示总行数:

Total Items: [[${totalItems}]]

显示导航到第一页的超链接:

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

注意,如果当前页面为 1,则它显示的是文本而不是链接。

显示到上一页的超链接:

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

显示导航到特定页面的链接,范围从页面 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;
</span>

显示下一页的链接:

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

显示最后一页的链接:

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

测试分页功能

假设 products 表中有 15 行,那么将看到主页如下:

了解 Spring Data JPA 排序 API

接下来,结合分页实现排序功能。 用户将可以单击表的列标题来对产品列表进行排序。

首先,创建一个 Sort 对象:

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

这将按“fieldName”对结果进行升序排序。fieldName 必须与实体类中声明的字段名称匹配。还可以按多个字段进行排序,例如:

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

这会按照 brand 的升序排列,然后按 price 降序排列。

然后传递 Sort 对象创建一个 Pageable:

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

实现排序

现在,除了之前所做的分页之外,让我们更新 ProjectManager 项目实现排序。为简单起见,我们仅允许用户按单个字段对产品列表进行排序。

更新服务类实现排序功能

修改 ProductService 类中的 listAll() 方法,将 sortField 和 sortDir 作为附加参数:

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);
}

这使我们可以参数化排序字段和排序方向。

更新控制器类实现排序功能

接下来,更新控制器类中的 viewPage() 方法,以从 URL 中的查询参数读取排序字段和排序方向:

@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";
}

用于分页和排序的 URL 如下所示:

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

在模型中还存储了 3 个其他属性,以便在视图中进行排序:sortField,sortDir 和 reverseSortDir。当用户单击列标题时,reverseSortDir 属性用于切换排序顺序。

修改主页的处理方法显示结果的第一页,默认情况下,结果按名称以升序排列:

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

更新 Thymeleaf 视图实现排序功能

通过使用以下代码添加超链接,使表的标题列可排序:

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

注意,超链接中的排序顺序与当前排序方向相反,这样用户可以切换排序方向(升/降)。

添加以下代码以显示当前字段按哪个方向排序:

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

对于分页导航链接,将以下值附加到每个 URL:

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

这样用户可以在应用排序的同时导航到结果的不同页面。

测试分页和排序

启动应用程序并刷新主页。 会看到表的列标题现在可以单击:

单击各列测试排序,然后单击底部的导航链接测试分页以及排序。

⬅返回目录

发表评论

电子邮件地址不会被公开。 必填项已用*标注