在上一篇文章中,我们已经知道如何构建Spring Boot Thymeleaf示例。今天,我将继续使用 Spring Data 和 Bootstrap 进行 Thymeleaf 分页和排序(按列标题)。
百里香叶分页和排序示例
假设我们在数据库中有这样的教程表:
我们的 Spring 启动应用程序将按升序或降序处理分页和排序请求。以下是一些网址示例(带/不带过滤器):
/api/tutorials
分页 [页面=0,大小=6](默认)和按 [id,升序] 排序(默认)/api/tutorials?sort=title,asc
分页 [页面=0,大小=6](默认)并按 [标题,升序] 排序/api/tutorials?page=2&size=6&sort=title,desc
分页 [页=2,大小=6] 并按 [标题,降序] 排序/api/tutorials?keyword=data&page=1&size=6&sort=title,asc
分页和按包含“数据”的标题过滤,按[标题,升序]排序
检索具有默认页面 (1)、大小 (6) 并按 [id, 升序] 排序的教程:
更改页面和大小(每页项目数)并按 [id,升序] 排序(默认):
单击表标题(级别)以创建分页和按级别排序列:
继续单击该表标题(级别)以降序排序:
带过滤的分页和排序:
Spring 数据分页和排序存储库
为了帮助我们处理这种情况,Spring Data JPA 提供了使用PagingAndSortingRepository 实现分页和排序的方法。
PagingAndSortingRepository
扩展了 CrudRepository,以提供使用排序抽象检索实体的其他方法。因此,您可以向查询方法添加特殊的 Sort 参数。
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
}
findAll(Sort sort)
:返回满足对象提供的排序条件的 AOF 实体。Iterable
Sort
您还可以使用 additionalparameter 定义更多派生和自定义查询方法。例如,以下方法返回标题包含给定字符串的教程列表:Sort
List<Tutorial> findByTitleContaining(String title, Sort sort);
您可以在此处的方法名称中找到更多支持的关键字。
让我们继续探索类。Sort
弹簧数据排序和排序
Sort类为数据库查询提供了排序选项,在选择单个/多个排序列和方向(升序/降序)时具有更大的灵活性。
例如,我们使用,,方法来创建对象并将其传递给:by()
descending()
and()
Sort
Repository.findAll()
// order by 'level' column - ascending
List<Tutorial> tutorials =
tutorialRepository.findAll(Sort.by("level"));
// order by 'level' column, descending
List<Tutorial> tutorials =
tutorialRepository.findAll(Sort.by("level").descending());
// order by 'level' column - descending, then order by 'title' - ascending
List<Tutorial> tutorials =
tutorialRepository.findAll(Sort.by("level").descending().and(Sort.by("title")));
我们还可以使用对象列表创建一个新对象。Sort
Order
List<Order> orders = new ArrayList<Order>();
Order order1 = new Order(Sort.Direction.DESC, "level");
orders.add(order1);
Order order2 = new Order(Sort.Direction.ASC, "title");
orders.add(order2);
List<Tutorial> tutorials = tutorialRepository.findAll(Sort.by(orders));
将分页和排序结合在一起
如果我们想对数据进行排序和分页怎么办?
CrudRepository
还提供了使用分页/排序抽象检索实体的其他方法。
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Page<T> findAll(Pageable pageable);
}
findAll(Pageable pageable)
:返回满足对象提供的分页条件的 AOF 实体。Page
Pageable
Spring Data 还支持从方法名称创建许多有用的查询,我们将使用这些方法名称来过滤此示例中的结果,例如:
Page<Tutorial> findByPublished(boolean published, Pageable pageable);
Page<Tutorial> findByTitleContaining(String title, Pageable pageable);
您可以在此处的方法名称中找到更多支持的关键字。
例如:JPA 存储库查询示例
要使用分页对多个字段进行排序,请访问教程:
Spring Data JPA 按多列排序/排序|弹簧启动
让我们注意上面存储库方法中的可分页参数。Spring 数据基础设施将自动识别此参数,以将分页和排序应用于数据库。
该接口包含有关所请求页面的信息,例如大小、页面编号或对信息进行排序。Pageable
Sort
public interface Pageable {
int getPageNumber();
int getPageSize();
long getOffset();
Sort getSort();
Pageable next();
Pageable previousOrFirst();
Pageable first();
boolean hasPrevious();
...
}
因此,当我们想在结果中进行分页和排序(带或不带过滤器)时,我们只需将方法的定义添加为参数。Pageable
Page<Tutorial> findAll(Pageable pageable);
Page<Tutorial> findByPublished(boolean published, Pageable pageable);
Page<Tutorial> findByTitleContaining(String title, Pageable pageable);
这就是我们使用实现接口的PageRequest类创建对象的方式:Pageable
Pageable
Pageable paging = PageRequest.of(page, size, sort);
page
:从零开始的页面索引,不得为负数。size
:页面中要返回的项目数必须大于 0。sort
:对象。Sort
您可以在这篇文章中找到有关分页和过滤器的更多详细信息:
Spring Boot 分页和过滤器示例
创建百里香叶分页和排序项目
您可以按照步骤操作,或获取本文中的源代码:
Spring Boot Thymeleaf CRUD 示例
Spring 项目包含的结构,我们只需要添加一些更改即可使分页和排序正常工作。
或者,您可以在本教程结束时获取新的 Github 源代码(包括分页和排序)。
数据模型
这是我们将要处理的教程实体:
模型/教程.java
package com.bezkoder.spring.thymeleaf.pagingsorting.entity;
import javax.persistence.*;
@Entity
@Table(name = "tutorials")
public class Tutorial {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(length = 128, nullable = false)
private String title;
@Column(length = 256)
private String description;
@Column(nullable = false)
private int level;
@Column
private boolean published;
public Tutorial() {
}
public Tutorial(String title, String description, int level, boolean published) {
this.title = title;
this.description = description;
this.level = level;
this.published = published;
}
// getters and setters
}
分页和排序存储库
在本教程的早期,我们知道,但是在这个例子中,为了保持连续性和利用Spring Data JPA,我们继续使用扩展接口的JpaRepository。PagingAndSortingRepository
PagingAndSortingRepository
存储库/教程存储库.java
package com.bezkoder.spring.thymeleaf.pagingsorting.repository;
import javax.transaction.Transactional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import com.bezkoder.spring.thymeleaf.pagingsorting.entity.Tutorial;
@Repository
@Transactional
public interface TutorialRepository extends JpaRepository<Tutorial, Integer> {
Page<Tutorial> findByTitleContainingIgnoreCase(String keyword, Pageable pageable);
// ...
}
在上面的代码中,我们使用 addparameter 和 Spring查询创建来查找标题包含输入字符串的所有教程。pageable
更多派生查询:
Spring 引导中的 JPA 存储库查询示例
带有注释的自定义查询:
Spring JPA @Query示例:Spring 引导中的自定义查询@Query
带分页和排序功能的控制器
通常,在HTTP请求URL中,分页和排序参数是可选的。因此,我们应该提供默认值以使分页和排序工作,即使UI View没有指定这些参数。
为了获取排序请求参数,我们使用。我们还需要转换/转换为/用于与类一起工作。@RequestParam String[] sort
defaultValue="id,asc"
"asc"
"desc"
Sort.Direction.ASC
Sort.Direction.DES
Sort.Order
控制器/教程控制器.java
package com.bezkoder.spring.thymeleaf.pagingsorting.controller;
import java.util.ArrayList;
import java.util.List;
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.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.bezkoder.spring.thymeleaf.pagingsorting.entity.Tutorial;
import com.bezkoder.spring.thymeleaf.pagingsorting.repository.TutorialRepository;
@Controller
public class TutorialController {
@Autowired
private TutorialRepository tutorialRepository;
@GetMapping("/tutorials")
public String getAll(Model model, @RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "6") int size,
@RequestParam(defaultValue = "id,asc") String[] sort) {
try {
List<Tutorial> tutorials = new ArrayList<Tutorial>();
String sortField = sort[0];
String sortDirection = sort[1];
Direction direction = sortDirection.equals("desc") ? Sort.Direction.DESC : Sort.Direction.ASC;
Order order = new Order(direction, sortField);
Pageable pageable = PageRequest.of(page - 1, size, Sort.by(order));
Page<Tutorial> pageTuts;
if (keyword == null) {
pageTuts = tutorialRepository.findAll(pageable);
} else {
pageTuts = tutorialRepository.findByTitleContainingIgnoreCase(keyword, pageable);
model.addAttribute("keyword", keyword);
}
tutorials = pageTuts.getContent();
model.addAttribute("tutorials", tutorials);
model.addAttribute("currentPage", pageTuts.getNumber() + 1);
model.addAttribute("totalItems", pageTuts.getTotalElements());
model.addAttribute("totalPages", pageTuts.getTotalPages());
model.addAttribute("pageSize", size);
model.addAttribute("sortField", sortField);
model.addAttribute("sortDirection", sortDirection);
model.addAttribute("reverseSortDirection", sortDirection.equals("asc") ? "desc" : "asc");
} catch (Exception e) {
model.addAttribute("message", e.getMessage());
}
return "tutorials";
}
// other CRUD methods
}
在上面的代码中,我们接受使用注释的分页参数,,.默认情况下,教程将从页面索引中的数据库获取(UI 视图上的 page=1),排序依据(降序)。@RequestParam
page
size
sort
6
0
id
接下来,我们创建一个对象,,.
然后检查参数是否存在。Pageable
page
size
sort
title
- 如果它为 null,我们调用 Repositorywithis 上面的对象。
findAll(pageable)
pageable
Pageable
- 如果客户端发送请求,请使用。
title
findByTitleContainingIgnoreCase(title, pageable)
这两种方法都返回一个对象。我们称之为:Page
getContent()
以检索页面中的项目列表。getNumber()
对于当前页面。getTotalElements()
用于数据库中存储的总项目。getTotalPages()
总页数。
我们还需要返回一些模型属性,例如:,,,.pageSize
sortField
sortDirection
reverseSortDirection
导入引导程序
打开pom.xml并添加以下依赖项:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.6.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
然后打开包含分页栏的HTML文件,导入Thymeleaf片段,Bootstrap,jQuery和Font Awesome:
教程.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0" />
<title>BezKoder - Spring Boot Thymeleaf Pagination and Sorting example</title>
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" />
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<script type="text/javascript" th:src="@{/webjars/jquery/jquery.min.js}"></script>
<script type="text/javascript" th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>
</head>
<body>
<div th:replace="fragments/header :: header"></div>
-- list, pagination and sorting--
<div th:replace="fragments/footer :: footer"></div>
</body>
</html>
百里香叶分页和排序模板
我们将使用百里香叶片段()来重用页面链接和表格的公共部分,按列标题排序。
让我们为它们编写 HTML 代码。th:fragment
片段/分页.html
<a th:fragment="paging(pageNum, label, tooltip)" class="page-link"
th:href="@{'/tutorials?' + ${keyword!=null && keyword!=''? 'keyword=' + keyword + '&' : ''}
+ 'page=' + ${pageNum} + '&size=' + ${pageSize}
+ ${sortField!=null ? '&sort=' + sortField + ',' + sortDirection : ''}}"
th:title="${tooltip}" rel="tooltip">
[[${label}]]
</a>
片段/排序.html
<th scope="col" th:fragment="sorting(field, label)">
<a th:href="@{'/tutorials?' + ${keyword!=null && keyword!=''? 'keyword=' + keyword + '&' : ''}
+ 'page=' + ${currentPage} + '&size=' + ${pageSize}
+ ${sortField!=null ? '&sort=' + field + ',' + (sortField == field ? reverseSortDirection : sortDirection) : ''}}">
[[${label}]] </a>
<span th:if="${sortField == field}"
th:class="${sortDirection == 'asc' ? 'fas fa-arrow-down-short-wide' : 'fas fa-arrow-down-wide-short'}"></span>
</th>
现在我们修改 Thymeleaf 模板教程.html,它显示基于模型属性的分页和排序的教程列表:,,,,从类返回。tutorials
pageSize
sortField
sortDirection
reverseSortDirection
TutorialController
教程.html
<div th:if="${tutorials.size() > 0}">
<table class="table table-hover table-responsive-xl">
<thead class="thead-light">
<tr>
<th th:replace="fragments/sorting :: sorting('id','Id')"></th>
<th th:replace="fragments/sorting :: sorting('title','Title')"></th>
<th th:replace="fragments/sorting :: sorting('description','Description')"></th>
<th th:replace="fragments/sorting :: sorting('level','Level')"></th>
<th>Published</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr th:each="tutorial : ${tutorials}">
<th scope="row">[[${tutorial.id}]]</th>
<td>[[${tutorial.title}]]</td>
<td>[[${tutorial.description}]]</td>
<td>[[${tutorial.level}]]</td>
<td>
<a th:if="${tutorial.published == true}" class="fa-regular fa-square-check"
th:href="@{'/tutorials/' + ${tutorial.id} + '/published/false'}" title="Disable this tutorial"></a>
<a th:if="${tutorial.published == false}" class="fa-regular fa-square icon-dark"
th:href="@{'/tutorials/' + ${tutorial.id} + '/published/true'}" title="Enable this tutorial"></a>
</td>
<td>
<a th:href="@{'/tutorials/' + ${tutorial.id}}" title="Edit this tutorial"
class="fa-regular fa-pen-to-square icon-dark"></a>
<a th:href="@{'/tutorials/delete/' + ${tutorial.id}}" th:tutorialTitle="${tutorial.title}" id="btnDelete"
title="Delete this tutorial" class="fa-regular fa-trash-can icon-dark btn-delete"></a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="" th:unless="${tutorials.size() > 0}">
<span>No tutorials found!</span>
</div>
<nav aria-label="Pagination" th:if="${totalPages > 0}">
<ul class="pagination justify-content-center">
<li class="page-item" th:classappend="${currentPage == 1} ? 'disabled'">
<a th:replace="fragments/paging :: paging(1, '<<', 'First Page')"></a>
</li>
<li class="page-item font-weight-bold" th:classappend="${currentPage == 1} ? 'disabled'">
<a th:replace="fragments/paging :: paging(${currentPage - 1}, 'Prev', 'Previous Page')"></a>
</li>
<li class="page-item disabled" th:if="${currentPage - 2 > 1}">
<a class="page-link" href="#">...</a>
</li>
<li class="page-item" th:classappend="${page == currentPage} ? 'active'"
th:each="page : ${#numbers.sequence(currentPage > 2 ? currentPage - 2 : 1, currentPage + 2 < totalPages ? currentPage + 2 : totalPages)}">
<a th:replace="fragments/paging :: paging(${page}, ${page}, 'Page ' + ${page})"></a>
</li>
<li class="page-item disabled" th:if="${currentPage + 2 < totalPages}">
<a class="page-link" href="#">...</a>
</li>
<li class="page-item font-weight-bold" th:classappend="${currentPage == totalPages} ? 'disabled'">
<a th:replace="fragments/paging :: paging(${currentPage + 1},'Next', 'Next Page')"></a>
</li>
<li class="page-item" th:classappend="${currentPage == totalPages} ? 'disabled'">
<a th:replace="fragments/paging :: paging(${totalPages}, '>>', 'Last Page')"></a>
</li>
</ul>
</nav>
然后我们继续使用输入关键字和页面大小修改搜索表单:
教程.html
<div>
<form th:action="@{/tutorials}" id="searchForm">
<div class="row d-flex">
<div class="col-md-6 mt-2">
<div class="search">
<i class="fa fa-search"></i>
<input id="keyword" type="search" name="keyword" th:value="${keyword}" required class="form-control"
placeholder="Enter keyword">
<button type="submit" class="btn btn-secondary">Search</button>
</div>
</div>
<div class="col-md-3 input-group mt-2">
<div class="input-group-prepend">
<label class="input-group-text" for="pageSize">Items per page:</label>
</div>
<select form="searchForm" name="size" th:value="${pageSize}" onchange="changePageSize()" class="size-select"
id="pageSize">
<option th:each="s : ${ {3, 6, 9} }" th:value="${s}" th:text="${s}" th:selected="${s == pageSize}"></option>
</select>
</div>
<div class="col-md-1 mt-2">
<button id="btnClear" class="btn btn-info">Clear</button>
</div>
</div>
</form>
</div>
<!-- sortable Table and Pagination Bar -->
<script type="text/javascript">
$(document).ready(function () {
// ...
$("#btnClear").on("click", function (e) {
e.preventDefault();
$("#keyword").text("");
window.location = "[[@{/tutorials}]]";
});
});
function changePageSize() {
$("#searchForm").submit();
}
</script>
运行春季启动百里香叶分页和排序示例
使用命令运行 Spring Boot 应用程序:。mvn spring-boot:run
结论
在这篇文章中,我们学习了如何使用 Bootstrap 和 Spring Data JPA 在 Spring Boot 示例中按列标题对 Thymeleaf 进行分页和排序表。
我们还看到它支持一种很好的方法,无需样板代码即可使用过滤器方法进行服务器端分页和排序。JpaRepository
更多派生查询:
Spring 引导中的 JPA 存储库查询示例
带有注释的自定义查询:
Spring JPA @Query示例:Spring 引导中的自定义查询@Query
按多个字段排序/排序:
Spring 数据 JPA 按多列排序/排序
此 Rest API 的句柄异常是必需的:
–Spring Boot @ControllerAdvice & @ExceptionHandler 示例–Spring Boot 中@RestControllerAdvice示例
或者编写单元测试的方法:
–JPA 存储库的弹簧引导单元测试
– 其余控制器的弹簧引导单元测试
祝你学习愉快!再见。
源代码
您可以在Github 上找到本教程的完整源代码。
延伸阅读
–Spring Boot Thymeleaf CRUD 示例 – Spring Boot 分页和排序 REST API 示例
全栈 CRUD 应用程序:
–Vue + 弹簧引导示例–角度 8 + 弹簧引导示例–角度 10 + 弹簧引导示例–角度 11 + 弹簧引导示例–角度 12 + 弹簧引导示例–角度 13 + 弹簧引导示例–角度 14 + 弹簧引导示例
–反应 + 弹簧启动示例
全栈分页:
–Spring Boot + React:分页示例–Spring Boot + Angular:分页示例
更多实践:
–Spring Boot with Spring Security & JWT Authentication
–Spring Boot Rest XML 示例–Spring Boot Multipart File 上传示例
–Spring Boot 按多列排序/排序
关联:
–使用 JPA 的 Spring Boot 一对一示例,使用 JPA 的 Hibernate–Spring Boot One To More 示例,使用 JPA 的 Hibernate
–Spring Boot Many to More 示例与 JPA,Hibernate