






















erDiagram %% 用户信息表 sys_user { bigint(20) user_id PK "用户ID(主键)" bigint(20) dept_id "部门ID" varchar(30) user_name "用户账号" varchar(30) nick_name "用户昵称" varchar(2) user_type "用户类型(00系统用户)" varchar(50) email "用户邮箱" varchar(11) phonenumber "手机号码" char(1) sex "用户性别(0男 1女 2未知)" varchar(100) avatar "头像地址" varchar(100) password "密码" char(1) status "帐号状态(0正常 1停用)" char(1) del_flag "删除标志(0代表存在 2代表删除)" varchar(128) login_ip "最后登录IP" datetime login_date "最后登录时间" varchar(64) create_by "创建者" datetime create_time "创建时间" varchar(64) update_by "更新者" datetime update_time "更新时间" varchar(500) remark "备注" } %% 用户角色关联表(中间表) sys_user_role { bigint(20) user_id FK "用户ID(联合主键/外键)" bigint(20) role_id FK "角色ID(联合主键/外键)" } %% 角色信息表 sys_role { bigint(20) role_id PK "角色ID(主键)" varchar(30) role_name "角色名称" varchar(100) role_key "角色权限字符串" int(4) role_sort "显示顺序" char(1) data_scope "数据范围(1:全部 2:自定 3:本部门 4:本部门及以下)" tinyint(1) menu_check_strictly "菜单树选择项是否关联显示" tinyint(1) dept_check_strictly "部门树选择项是否关联显示" char(1) status "角色状态(0正常 1停用)" char(1) del_flag "删除标志(0代表存在 2代表删除)" varchar(64) create_by "创建者" datetime create_time "创建时间" varchar(64) update_by "更新者" datetime update_time "更新时间" varchar(500) remark "备注" } %% 角色菜单关联表(中间表) sys_role_menu { bigint(20) role_id FK "角色ID(联合主键/外键)" bigint(20) menu_id FK "菜单ID(联合主键/外键)" } %% 菜单权限表 sys_menu { bigint(20) menu_id PK "菜单ID(主键)" varchar(50) menu_name "菜单名称" bigint(20) parent_id "父菜单ID" int(4) order_num "显示顺序" varchar(200) path "路由地址" varchar(255) component "组件路径" varchar(255) query "路由参数" int(1) is_frame "是否为外链(0是 1否)" int(1) is_cache "是否缓存(0缓存 1不缓存)" char(1) menu_type "菜单类型(M目录 C菜单 F按钮)" char(1) visible "菜单状态(0显示 1隐藏)" char(1) status "菜单状态(0正常 1停用)" varchar(100) perms "权限标识" varchar(100) icon "菜单图标" varchar(64) create_by "创建者" datetime create_time "创建时间" varchar(64) update_by "更新者" datetime update_time "更新时间" varchar(500) remark "备注" } %% 定义关联关系 %% 用户与角色:多对多,通过sys_user_role关联 sys_user ||--o{ sys_user_role : "关联" sys_role ||--o{ sys_user_role : "关联" %% 角色与菜单:多对多,通过sys_role_menu关联 sys_role ||--o{ sys_role_menu : "关联" sys_menu ||--o{ sys_role_menu : "关联"
||--o{ 表示一对多关系,中间表与主表的关联关系通过该符号体现sys_user(多)↔ sys_user_role(中间表)↔ sys_role(多)、sys_role(多)↔ sys_role_menu(中间表)↔ sys_menu(多)的多对多关联链PK 标注主键,FK 标注外键 @GetMapping("/list")
public TableDataInfo list(SysLogininfor logininfor)
{
// 需要分页的接口需要加上该函数
startPage();
List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
return getDataTable(list);
}
分页参数加在url上,后端可通过serlvetUtil获得
PageInterceptor 如何通过实现 MyBatis Interceptor 接口完成分页功能:
// 1. 核心拦截器类:实现 MyBatis 的 Interceptor 接口
public class PageInterceptor implements Interceptor {
// 数据库方言(如 MySQL、Oracle,用于生成对应分页语法)
private String dialect;
// ====================== 接口实现方法1:setProperties - 初始化配置 ======================
// 读取配置文件中的分页属性(如 dialect=mysql)
@Override
public void setProperties(Properties properties) {
this.dialect = properties.getProperty("helperDialect", "mysql"); // 默认MySQL方言
// 其他配置:如是否自动统计总条数、分页参数合理化等
}
// ====================== 接口实现方法2:plugin - 生成代理对象 ======================
// 为目标组件(Executor/StatementHandler)生成动态代理,使拦截逻辑生效
@Override
public Object plugin(Object target) {
// 只对需要拦截的组件进行代理(避免无关组件被包装)
if (target instanceof Executor || target instanceof StatementHandler) {
return Plugin.wrap(target, this); // MyBatis 自带的代理工具
}
return target; // 非目标组件直接返回
}
// ====================== 接口实现方法3:intercept - 核心分页逻辑(重点) ======================
@Override
public Object intercept(Invocation invocation) throws Throwable {
// ===== 步骤1:获取分页参数(从 ThreadLocal 中获取,由 PageHelper.startPage() 存入) =====
Page<?> pageParam = PageContext.getLocalPage(); // ThreadLocal 存储分页参数
if (pageParam == null) {
// 无分页参数,直接执行原始SQL,不做分页处理
return invocation.proceed();
}
// ===== 步骤2:获取原始SQL及执行相关信息 =====
// 从 invocation 中解析目标对象、方法参数(不同拦截目标解析方式略有差异,此处以 Executor 为例)
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1]; // SQL参数
String originalSql = getOriginalSql(mappedStatement, parameter); // 获取原始SQL(如:SELECT * FROM sys_user WHERE status = 0)
Integer pageNum = pageParam.getPageNum(); // 当前页码(如第2页)
Integer pageSize = pageParam.getPageSize(); // 每页条数(如每页10条)
// ===== 步骤3:动态改写SQL,生成分页SQL =====
String pageSql = generatePageSql(originalSql, pageNum, pageSize);
// 示例:MySQL 分页SQL → SELECT * FROM sys_user WHERE status = 0 LIMIT 10, 10
// ===== 步骤4:自动生成总条数查询SQL,统计总记录数 =====
if (pageParam.isCount()) { // 是否需要统计总条数(默认true)
String countSql = generateCountSql(originalSql); // 生成 COUNT SQL:SELECT COUNT(0) FROM (SELECT * FROM sys_user WHERE status = 0) temp
Long totalCount = executeCountSql(countSql, parameter); // 执行COUNT SQL,获取总条数
pageParam.setTotal(totalCount); // 设置总记录数
pageParam.setPages(totalCount % pageSize == 0 ? totalCount / pageSize : totalCount / pageSize + 1); // 计算总页数
}
// ===== 步骤5:替换原始SQL,执行分页查询 =====
replaceOriginalSql(mappedStatement, pageSql); // 将改写后的分页SQL替换原始SQL
Object pageResult = invocation.proceed(); // 执行分页SQL,获取当前页数据列表
// ===== 步骤6:封装分页结果,清除ThreadLocal缓存 =====
pageParam.addAll((List<?>) pageResult); // 将当前页数据存入分页对象
PageContext.clearLocalPage(); // 清除ThreadLocal中的分页参数,避免线程复用导致错乱
// ===== 步骤7:返回分页结果(最终可包装为 PageInfo 对象) =====
return pageParam;
}
// ---------------------- 内部辅助方法:生成分页SQL ----------------------
private String generatePageSql(String originalSql, Integer pageNum, Integer pageSize) {
if ("mysql".equalsIgnoreCase(dialect)) {
// MySQL 分页:LIMIT (pageNum-1)*pageSize, pageSize
int offset = (pageNum - 1) * pageSize;
return originalSql + " LIMIT " + offset + ", " + pageSize;
} else if ("oracle".equalsIgnoreCase(dialect)) {
// Oracle 分页:嵌套ROWNUM查询
int start = (pageNum - 1) * pageSize + 1;
int end = pageNum * pageSize;
return "SELECT * FROM (SELECT t.*, ROWNUM rn FROM (" + originalSql + ") t WHERE ROWNUM <= " + end + ") WHERE rn >= " + start;
}
// 其他数据库方言(SQL Server、PostgreSQL等)...
return originalSql;
}
// ---------------------- 内部辅助方法:生成总条数查询SQL ----------------------
private String generateCountSql(String originalSql) {
// 去除原始SQL中的 ORDER BY(避免统计总条数时排序影响性能)
String sqlWithoutOrderBy = originalSql.replaceAll("ORDER BY.*", "");
return "SELECT COUNT(0) FROM (" + sqlWithoutOrderBy + ") temp_count";
}
// ---------------------- 内部辅助方法:执行COUNT SQL,获取总条数 ----------------------
private Long executeCountSql(String countSql, Object parameter) {
// 伪代码:通过JDBC/MyBatis执行COUNT SQL,返回总记录数
PreparedStatement pstmt = getConnection().prepareStatement(countSql);
setParameters(pstmt, parameter); // 设置SQL参数
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
return rs.getLong(1);
}
return 0L;
}
// 其他辅助方法:获取原始SQL、替换原始SQL...
private String getOriginalSql(MappedStatement ms, Object parameter) { /* 实现逻辑 */ }
private void replaceOriginalSql(MappedStatement ms, String newSql) { /* 实现逻辑 */ }
}
// ====================== 辅助类:ThreadLocal 存储分页参数 ======================
class PageContext {
// ThreadLocal 保证多线程环境下分页参数隔离
private static final ThreadLocal<Page<?>> PAGE_THREAD_LOCAL = new ThreadLocal<>();
// 设置分页参数(由 PageHelper.startPage(pageNum, pageSize) 调用)
public static void setLocalPage(Page<?> page) {
PAGE_THREAD_LOCAL.set(page);
}
// 获取分页参数
public static Page<?> getLocalPage() {
return PAGE_THREAD_LOCAL.get();
}
// 仅清空当前线程的分页参数
public static void clearLocalPage() {
PAGE_THREAD_LOCAL.remove();
}
}
// ====================== 分页参数载体:Page 类(存储分页相关信息) ======================
class Page<T> extends ArrayList<T> {
private Integer pageNum; // 当前页码
private Integer pageSize; // 每页条数
private Long total; // 总记录数
private Integer pages; // 总页数
private boolean count = true; // 是否统计总条数
// getter/setter 省略
}
// ====================== 外部调用入口:PageHelper 静态方法 ======================
class PageHelper {
// 开发者调用该方法设置分页参数,本质是将参数存入 ThreadLocal
public static <T> Page<T> startPage(Integer pageNum, Integer pageSize) {
Page<T> page = new Page<>(pageNum, pageSize);
PageContext.setLocalPage(page);
return page;
}
}
PageHelper.startPage(pageNum, pageSize),分页参数存入 ThreadLocal,保证多线程隔离;PageInterceptor 拦截目标组件,从 ThreadLocal 获取分页参数;LIMIT)和总条数统计 SQL;ThreadLocal 中的分页参数,避免线程复用问题;PageInfo 供业务使用。此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。