Springboot + Bootstrap + Thymeleaf 分页
前言
这里需要使用到的组件有:
- Springboot 2.7.x
- MySQL 5.7.x
- Mybatis Plus 3.5.2
- Bootstrap 4.5.x
- Thymeleaf
添加依赖和配置 application.properties
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
配置 application.properties
# Thymeleaf Configuration
# 关闭缓存
spring.thymeleaf.cache=false
# 字符编码
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML
# 模板文件位置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
# MySQL Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/dong?serverTimezone=UTC&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root#admin
# HikariCP Configuration
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
# Tomcat Port
server.port=18443
server.address=localhost
Model,View,Controller layers
在数据层,先创建一个简单的实体类Post,包含title和body字段
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
@Data
public class Post {
@TableId
private Long id;
private String title;
private String body;
}
在数据访问层(PostDao),直接使用 Mybatisplus提供的 API
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.syuez.dong.entity.Post;
public interface PostDAO extends BaseMapper<Post> {
}
在服务层,PostService类负责查询数据,并将结果与Paged实例打包
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.syuez.dong.entity.Paged;
import com.syuez.dong.entity.Paging;
import com.syuez.dong.entity.Post;
import com.syuez.dong.mapper.PostDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PostService {
private final PostDAO postDAO;
@Autowired
public PostService(PostDAO postDAO) {
this.postDAO = postDAO;
}
public Paged<Post> getPage(int pageNumber, int size) {
Page<Post> page = new Page<>(pageNumber, size);
postDAO.selectPage(page, null);
/*
总页数 = 总条目数÷每页显示目数,如果整除则取商,如果有余数则取(商+1)
*/
int totalPages = (int) page.getTotal() % size == 0 ? (int) page.getTotal() / size : (int) page.getTotal() / size + 1;
return new Paged<>(page, Paging.of(totalPages, pageNumber,size));
}
}
Paged,PageItem,PageItemType和Paging类是用于准备分页组件的工具类
Paged类结构
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Getter;
public class Paged<T> {
@Getter
private final Page<T> page;
@Getter
private final Paging paging;
public Paged(Page<T> page, Paging paging) {
this.page = page;
this.paging = paging;
}
}
PageItem类结构
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class PageItem {
private PageItemType pageItemType;
/**
* 当前索引
*/
private int index;
/**
* 是否激活
*/
private boolean active;
}
PageItemType结构
/**
* 分页类型
*/
public enum PageItemType {
/**
* 省略号
*/
DOTS,
/**
* 页面
*/
PAGE
}
Paging类负责计算如何在分页组件中显示页面和...
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
public class Paging {
private static final int PAGINATION_STEP = 3;
/**
* 是否存在下一页
*/
@Setter
@Getter
private boolean nextEnabled;
/**
* 是否存在上一页
*/
@Setter
@Getter
private boolean prevEnabled;
/**
* 每页显示多少数据
*/
@Setter
@Getter
private int pageSize;
/**
* 当前是第几页
*/
@Setter
@Getter
private int pageNumber;
/**
* 分页列表
*/
@Getter
private List<PageItem> items = new ArrayList<>();
public void addPageItems(int from, int to, int pageNumber) {
for (int i = from; i < to; i++) {
items.add(PageItem.builder()
.active(pageNumber !=i)
.index(i)
.pageItemType(PageItemType.PAGE)
.build());
}
}
public void lastPage(int pageSize) {
items.add(PageItem.builder()
.active(false)
.pageItemType(PageItemType.DOTS)
.build());
items.add(PageItem.builder()
.active(true)
.index(pageSize)
.pageItemType(PageItemType.PAGE)
.build());
}
public void firstPage(int pageNumber) {
items.add(PageItem.builder()
.active(pageNumber != 1)
.index(1)
.pageItemType(PageItemType.PAGE)
.build());
items.add(PageItem.builder()
.active(false)
.pageItemType(PageItemType.DOTS)
.build());
}
public static Paging of(int totalPages, int pageNumber, int pageSize) {
Paging paging = new Paging();
// 设置每页显示条目
paging.setPageSize(pageSize);
// 当前页不是总页数时,总有下一页
paging.setNextEnabled(pageNumber != totalPages);
// 当前页不是第一页时,总有上一页
paging.setPrevEnabled(pageNumber != 1);
// 设置当前页
paging.setPageNumber(pageNumber);
// 分页逻辑
if (totalPages < PAGINATION_STEP * 2 + 6) {
paging.addPageItems(1, totalPages + 1, pageNumber);
} else if (pageNumber < PAGINATION_STEP * 2 + 1) {
paging.addPageItems(1, PAGINATION_STEP * 2 + 4, pageNumber);
paging.lastPage(totalPages);
} else if (pageNumber > totalPages - PAGINATION_STEP * 2) {
paging.firstPage(pageNumber);
paging.addPageItems(totalPages - PAGINATION_STEP * 2 - 2, totalPages + 1, pageNumber);
} else {
paging.firstPage(pageNumber);
paging.addPageItems(pageNumber - PAGINATION_STEP, pageNumber + PAGINATION_STEP + 1, pageNumber);
paging.lastPage(totalPages);
}
return paging;
}
}
GET 请求是由PostController类处理的。为了改变当前页面和页面显示的条目数量,我们需要添加两个请求参数:
- pageNumber - 当前的页面(默认是第一页)
- size - 每页显示条目数(默认是5行)
import com.syuez.dong.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.ui.Model;
@Controller
@RequestMapping("/")
public class PostController {
private final PostService postService;
@Autowired
public PostController(PostService postService) {
this.postService = postService;
}
@GetMapping
public String posts(@RequestParam(value = "pageNumber", required = false, defaultValue = "1") int pageNumber,
@RequestParam(value = "size", required = false, defaultValue = "5") int size, Model model) {
model.addAttribute("posts", postService.getPage(pageNumber, size));
return "posts";
}
}
配置 MybatisPlush 的分页
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.syuez.dong.mapper")
public class MybatisPlusConfig {
/**
* 新的分页插件,分页插件,如果你不配置,分页插件将不生效,指定数据库方言为 MYSQL
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 单页分页条数限制
pageInterceptor.setMaxLimit(20L);
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}
Thymeleaf 模板
posts.html源码:
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<!-- Required meta tags -->
<meta http-equiv="Content-Type" content="text/html" charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link type="text/css" th:href="@{css/bootstrap4/bootstrap.min.css}" rel="stylesheet">
<title>狗屁不通文章</title>
</head>
<body>
<div class="container-fluid">
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
<div class="container">
<a class="navbar-brand" href="/">Thymeleaf - Bootstrap Pagination</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home
<span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-lg-10 mt-5 mb-5">
<table id="posts" class="table table-bordered table-responsive-sm">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Body</th>
</tr>
</thead>
<tbody>
<tr th:each="post : ${posts.page.getRecords()}">
<td th:text="${post.id}">id</td>
<td th:text="${post.title}">title</td>
<td th:text="${post.body}">body</td>
</tr>
</tbody>
</table>
<nav aria-label="Page navigation" class="paging">
<ul class="pagination" th:if="${posts.page.getTotal() > 1}">
<li class="page-item" th:classappend="${!posts.paging.isPrevEnabled()? 'disabled' : ''}">
<a class="page-link" th:href="@{'/?pageNumber=' + ${posts.paging.pageNumber - 1}}"
tabindex="-1">上一页</a>
</li>
<th:block th:each="item : ${posts.paging.getItems()}">
<li class="page-item" th:classappend="${item.index == posts.paging.pageNumber? 'active' : ''}"
th:if="${item.pageItemType.name() == 'PAGE'}">
<a class="page-link" th:href="@{'/?pageNumber=' + ${item.index}}"
th:text="${item.index}"></a>
</li>
<li class="page-item disabled" th:if="${item.pageItemType.name() == 'DOTS'}">
<a class="page-link" href="#">...</a>
</li>
</th:block>
<li class="page-item" th:classappend="${!posts.paging.isNextEnabled()? 'disabled' : ''}">
<a class="page-link" th:href="@{'/?pageNumber=' + ${posts.paging.pageNumber + 1}}">下一页</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</div>
<!-- jQuery first, then Popper.js and Bootstrap JS -->
<script type="text/javascript" th:src="@{js/jquery/jquery-3.6.0.min.js}" ></script>
<script type="text/javascript" th:src="@{js/bootstrapjs/bootstrap.bundle.min.js}"></script>
</body>
</html>
效果
请访问 https://dong.syuez.com 查看效果
本文转载自:Spring Boot + Bootstrap + Thymeleaf Pagination (JPA, Liquibase, H2) | FrontBackend 代码有略微修改。