1. 概述
本文将带你快速实现一个基于 Spring 与 Thymeleaf 的分页列表展示功能。
我们不会从零讲起 Spring 和 Thymeleaf 的集成细节(这属于基础操作),如果你还不熟悉,建议先查阅我们之前的文章:Spring MVC 中使用 Thymeleaf。本文聚焦于 分页逻辑与前后端协同展示,属于实战型内容,踩坑点也会一并指出。
2. Maven 依赖
除了常规的 Spring Web 和 Spring Boot 相关依赖外,我们需要引入以下两个关键依赖:
✅ Thymeleaf Spring5 集成包:用于模板渲染
✅ Spring Data Commons:提供 Page
、Pageable
等分页核心接口
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
💡 提示:最新版本可前往 Maven Central 搜索
thymeleaf-spring5
和spring-data-commons
获取。
⚠️ 注意:如果你用的是 Spring Boot,推荐直接使用 spring-boot-starter-thymeleaf
,它已内置了这些依赖,无需手动添加。
3. 模型定义
我们以一个图书列表为例,演示分页功能。
定义一个简单的 Book
类,包含 ID 和书名:
public class Book {
private int id;
private String name;
// 全参构造、getter、setter 省略
// 实际项目中建议使用 Lombok @Data
}
数据源我们暂时用静态工具类生成(BookUtils.buildBooks()
),模拟数据库返回的列表。
4. 分页服务实现
核心逻辑在 BookService
中完成。这里我们不依赖 JPA,而是手动实现内存分页,便于理解原理。
@Service
public class BookService {
final private List<Book> books = BookUtils.buildBooks();
public Page<Book> findPaginated(Pageable pageable) {
int pageSize = pageable.getPageSize();
int currentPage = pageable.getPageNumber();
int startItem = currentPage * pageSize;
List<Book> list;
if (books.size() < startItem) {
list = Collections.emptyList();
} else {
int toIndex = Math.min(startItem + pageSize, books.size());
list = books.subList(startItem, toIndex);
}
Page<Book> bookPage
= new PageImpl<Book>(list, PageRequest.of(currentPage, pageSize), books.size());
return bookPage;
}
}
关键点解析:
- ✅
Pageable
是 Spring Data 提供的分页参数接口,封装了页码和页大小 - ✅
PageImpl
是Page
接口的实现类,用于构造分页结果,需传入:- 当前页数据列表
- 分页请求对象(
PageRequest
) - 总记录数(用于计算总页数)
- ⚠️ 注意:
Pageable.getPageNumber()
从 0 开始,前端通常从 1 开始,所以 controller 中要-1
这个实现虽然简单粗暴,但足够清晰,适合理解分页机制。真实项目中若使用 JPA,JpaRepository
直接支持 Page<T> findAll(Pageable)
,一行代码搞定。
5. Spring 控制器
控制器负责接收分页参数,调用服务层,并将数据交给视图渲染。
@Controller
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping(value = "/listBooks", method = RequestMethod.GET)
public String listBooks(
Model model,
@RequestParam("page") Optional<Integer> page,
@RequestParam("size") Optional<Integer> size) {
int currentPage = page.orElse(1);
int pageSize = size.orElse(5);
Page<Book> bookPage = bookService.findPaginated(PageRequest.of(currentPage - 1, pageSize));
model.addAttribute("bookPage", bookPage);
int totalPages = bookPage.getTotalPages();
if (totalPages > 0) {
List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
.boxed()
.collect(Collectors.toList());
model.addAttribute("pageNumbers", pageNumbers);
}
return "listBooks.html";
}
}
参数说明:
/listBooks
:默认访问第 1 页,每页 5 条/listBooks?page=2&size=6
:访问第 2 页,每页 6 条
数据准备要点:
- ✅
bookPage
:Thymeleaf 模板中用于遍历当前页数据和获取分页元信息(如总页数、当前页码等) - ✅
pageNumbers
:生成页码列表[1, 2, 3, ...]
,用于前端生成页码链接
⚠️ 踩坑提醒:PageRequest.of(currentPage - 1, pageSize)
这里必须减 1,因为 Spring Data 的页码从 0 开始!
6. Thymeleaf 模板
创建 templates/listBooks.html
,使用 Thymeleaf 渲染分页列表。
<table border="1">
<thead>
<tr>
<th th:text="#{msg.id}" />
<th th:text="#{msg.name}" />
</tr>
</thead>
<tbody>
<tr th:each="book, iStat : ${bookPage.content}"
th:style="${iStat.odd}? 'font-weight: bold;'"
th:alt-title="${iStat.even}? 'even' : 'odd'">
<td th:text="${book.id}" />
<td th:text="${book.name}" />
</tr>
</tbody>
</table>
<div th:if="${bookPage.totalPages > 0}" class="pagination"
th:each="pageNumber : ${pageNumbers}">
<a th:href="@{/listBooks(size=${bookPage.size}, page=${pageNumber})}"
th:text=${pageNumber}
th:class="${pageNumber==bookPage.number + 1} ? active"></a>
</div>
模板解析:
表格部分:
th:each="book, iStat : ${bookPage.content}"
遍历当前页的书籍列表,iStat
是状态变量,可用于判断奇偶行th:style="${iStat.odd}? 'font-weight: bold;'
奇数行加粗,简单实现隔行高亮
分页部分:
th:if="${bookPage.totalPages > 0}"
只有总页数大于 0 才显示分页栏th:each="pageNumber : ${pageNumbers}"
遍历页码列表生成链接th:href="@{/listBooks(size=${bookPage.size}, page=${pageNumber})}"
使用 Thymeleaf URL 表达式构造带参数的请求链接th:class="${pageNumber==bookPage.number + 1} ? active"
当前页高亮显示(注意bookPage.number
从 0 开始,所以要+1
)
✅ 效果:点击页码跳转,当前页链接样式为 active
,便于 CSS 定制。
7. 总结
本文通过一个简单示例,完整展示了 Spring + Thymeleaf 实现分页列表的全流程:
- ✅ 使用
Pageable
接收分页参数 - ✅ 手动实现
PageImpl
构建分页结果(或使用 JPA 自动支持) - ✅ Controller 传递
Page
和页码列表到视图 - ✅ Thymeleaf 模板渲染数据与分页链接
虽然示例基于内存数据,但结构完全适用于真实项目。后续可结合 Spring Data JPA 实现数据库分页,性能更优。
所有代码示例均可在 GitHub 获取:
👉 https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-thymeleaf-5