跳到主要内容MyBatisPlus 与 Thymeleaf 全栈分页实战指南 | 极客日志Java大前端java
MyBatisPlus 与 Thymeleaf 全栈分页实战指南
基于 Spring Boot 结合 MyBatisPlus 与 Thymeleaf,演示从数据库分页查询到前端表格渲染的完整流程。涵盖依赖配置、实体映射、Service 层分页逻辑、Controller 接口设计以及前端 Ajax 交互实现。重点解决分页插件配置缺失导致的数据全量加载问题,提供可直接参考的代码片段和常见错误排查思路,帮助开发者快速构建高效稳定的后台数据展示功能。
佛系玩家1 浏览 MyBatisPlus 与 Thymeleaf 全栈分页实战
在现代 Web 开发中,分页是提升用户体验和系统性能的关键。无论是企业级后台还是面向用户的平台,合理的分页策略都能显著降低服务器负载。本文将结合 Spring Boot、MyBatisPlus 和 Thymeleaf,演示如何构建一套完整的全栈分页方案,从后端数据查询到前端页面渲染,实现无缝对接。
一、环境搭建与表结构
MyBatisPlus 作为 MyBatis 的增强工具,内置了强大的分页插件,能极大简化 SQL 编写。我们基于 Maven 管理依赖,集成 PostgreSQL 数据库驱动及 Lombok 组件来简化代码。
1. 依赖配置
在 pom.xml 中引入核心依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.postgis</groupId>
<artifactId>postgis-jdbc</artifactId>
2.5.0
<version>
</version>
</dependency>
2. 示例表结构
以城市停水信息为例,主要包含时间、地点、原因等字段。表结构如下:
二、Java 后台分页实现
后端部分主要涉及实体类映射、业务层逻辑封装以及控制层接口暴露。
1. 实体类定义
使用注解映射数据库表,Lombok 处理 Getter/Setter。注意日期字段的格式化配置。
package org.yelang.pcwater.domain;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@TableName(value = "biz_stop_water_info")
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class StopWaterInfo implements Serializable {
private static final long serialVersionUID = -1582687729349525826L;
@TableId(value = "pk_id")
private Long pkId;
@TableField(value = "affect_user")
private String affectUser;
@TableField(value = "affected_range")
private String affectedRange;
@TableField(value = "affect_region")
private String affectRegion;
@TableField(value = "created_on")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdOn;
@TableField(value = "inform_emergency_plan")
private String informEmergencyPlan;
@TableField(value = "inform_id")
private Integer informId;
@TableField(value = "inform_remark")
private String informRemark;
@TableField(value = "main_lead_path")
private String mainLeadPath;
private String position;
private String reason;
@TableField(value = "service_phone")
private String servicePhone;
@TableField(value = "show_end_date")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@SerializedName("showenddate")
private Date showEndDate;
@TableField(value = "stop_end_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date stopEndTime;
@TableField(value = "stop_start_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date stopStartTime;
@TableField(value = "stop_time")
@SerializedName("stoptime")
private String stopTime;
@TableField(value = "stopwater_type")
private String stopwaterType;
@TableField(value = "supply_emergency_plan")
private String supplyEmergencyPlan;
@TableField(value = "supply_remark")
private String supplyRemark;
private String title;
private String type;
@TableField(value = "regin_name")
private String reginName;
@TableField(value = "sync_sno")
private Long syncSno;
}
2. 业务层分页逻辑
MyBatisPlus 的分页核心在于 Page 对象和 IPage 返回类型。我们在 Service 接口定义分页方法,并在实现类中组装查询条件。
IPage<StopWaterInfo> page(Integer pageNum, Integer pageSize, String regionName);
具体实现时,利用 QueryWrapper 构建动态查询,并传入当前页码和每页条数:
@Override
public IPage<StopWaterInfo> page(Integer pageNum, Integer pageSize, String regionName) {
QueryWrapper<StopWaterInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.like("regin_name", regionName);
queryWrapper.orderByDesc("created_on");
Page<StopWaterInfo> page = new Page<>(pageNum, pageSize);
return this.baseMapper.selectPage(page, queryWrapper);
}
这里通过构造 Page 对象,将控制层传入的当前页和每页大小一起传给 MyBatisPlus,框架会自动拦截 SQL 并添加 LIMIT 和 COUNT 语句。
3. 控制层接口
Controller 负责接收前端参数,调用 Service,并将结果封装返回。这里使用了标准的 AJAX 响应格式。
@PostMapping("/list")
@ResponseBody
public AjaxResult list(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "5") Integer pageSize,
@RequestParam(defaultValue = "") String regionName) {
IPage<StopWaterInfo> page = waterInfoService.page(pageNum, pageSize, regionName);
AjaxResult result = AjaxResult.success();
result.put("data", page);
return result;
}
三、Thymeleaf 前端集成
前端部分主要利用 Ajax 异步加载数据,并通过 DOM 操作动态渲染表格和分页条。
1. 表格展示
HTML 结构中预留表格容器,JS 负责填充内容。当数据为空时显示提示行。
<div>
<table>
<thead>
<tr>
<th>创建时间</th>
<th>停水地点</th>
<th>原因</th>
</tr>
</thead>
<tbody id="table-body">
</tbody>
</table>
</div>
function renderTablePage(page) {
currentPage = page;
const tableBody = document.getElementById('table-body');
tableBody.innerHTML = '';
$.ajax({
type: "post",
url: ctx + "datasync/list",
dataType: "json",
cache: false,
processData: true,
data: {"pageNum": page, "pageSize": EVENTS_PER_PAGE, "regionName": ""},
success: function(result) {
const pageData = result.data.records;
const totalPages = result.data.pages;
if (pageData.length === 0) {
tableBody.innerHTML = `<tr><td colspan="5">未找到符合条件的停水事件。</td></tr>`;
} else {
pageData.forEach(event => {
const row = tableBody.insertRow();
row.innerHTML = `
<td>${event.createdOn}</td>
<td>${event.position}</td>
<td>${event.reason}</td>
`;
});
}
renderPagination(totalPages);
},
error: function() {
tableBody.innerHTML = `<tr><td colspan="5">未找到符合条件的停水事件。</td></tr>`;
}
});
}
2. 分页条集成
分页条同样需要动态生成,支持上一页和下一页跳转。注意处理边界情况(如第一页禁用上一页)。
<div>
<nav aria-label="Table Pagination">
<ul id="pagination-list"></ul>
</nav>
</div>
JS 逻辑修正了原代码中的标签错误,确保生成合法的 <li> 元素:
function renderPagination(totalPages) {
const paginationContainer = document.getElementById('pagination-list');
paginationContainer.innerHTML = '';
if (totalPages <= 1) return;
const prevLi = document.createElement('li');
if (currentPage > 1) {
prevLi.innerHTML = `<a href="#" onclick="changePage(${totalPages},${currentPage - 1})">前一页</a>`;
} else {
prevLi.classList.add('disabled');
prevLi.innerHTML = `<span>前一页</span>`;
}
paginationContainer.appendChild(prevLi);
const nextLi = document.createElement('li');
if (currentPage < totalPages) {
nextLi.innerHTML = `<a href="#" onclick="changePage(${totalPages},${currentPage + 1})">后一页</a>`;
} else {
nextLi.classList.add('disabled');
nextLi.innerHTML = `<span>后一页</span>`;
}
paginationContainer.appendChild(nextLi);
}
四、常见问题排查
在实际开发中,最容易出现的问题是分页失效,即所有数据一次性加载,或者分页条不显示。
1. 现象描述
如果页面下方没有分页条,且表格加载了大量数据,通常意味着分页插件未生效。
2. 解决方案
这通常是因为缺少 MyBatisPlus 的分页拦截器配置。需要在 Spring Boot 配置类中添加拦截器 Bean,指定数据库类型。
package org.yelang.pcwater.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor i = new MybatisPlusInterceptor();
i.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
return i;
}
}
五、总结
通过上述步骤,我们完成了一个基于 MyBatisPlus 和 Thymeleaf 的全栈分页功能。重点掌握了分页对象的传递、拦截器的配置以及前后端数据的交互模式。这套方案不仅适用于停水信息发布场景,也可复用于各类后台管理系统的列表展示。遇到分页异常时,优先检查拦截器配置是否生效。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online