MyBatis 通过自定义 Interceptor 拦截 StatementHandler 的 prepare() 方法实现物理分页,核心是解析 SELECT SQL、按方言重写并绑定 offset/limit 参数,推荐使用 JSQLParser 解析及 ThreadLocal 传递分页上下文。

MyBatis 本身不内置物理分页功能,但可通过 Interceptor(拦截器) 拦截 SQL 执行过程,动态重写 SQL 实现数据库层的物理分页(如 MySQL 的 LIMIT offset, size)。这是最常用、最可控的自定义分页插件方式。
一、实现 MyBatis 分页拦截器的核心逻辑
需实现 org.apache.ibatis.plugin.Interceptor 接口,重点拦截 StatementHandler 的 prepare() 方法,在 SQL 准备阶段修改 SQL 和参数:
- 识别带
RowBounds或自定义分页参数(如PageHelper.startPage()风格)的查询 - 解析原始 SQL,区分
SELECT语句(跳过 INSERT/UPDATE/DELETE) - 根据方言(MySQL / Oracle / PostgreSQL 等)生成对应分页 SQL 片段
- 将计算出的
offset和limit绑定为 PreparedStatement 参数
二、关键步骤与代码示例(以 MySQL 为例)
1. 编写分页拦截器类:
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PaginationInterceptor implements Interceptor {
private String dialect = "mysql"; // 可扩展为多数据库支持
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
Object parameterObject = boundSql.getParameterObject();
// 判断是否需要分页(例如:参数中含 Page 对象,或 ThreadLocal 存了分页上下文)
Page page = getPageFromParam(parameterObject);
if (page == null || page.getSize() <= 0) {
return invocation.proceed();
}
String originalSql = boundSql.getSql();
String paginatedSql = buildMysqlPagedSql(originalSql, page.getOffset(), page.getSize());
metaObject.setValue("delegate.boundSql.sql", paginatedSql);
// 替换 ParameterHandler(可选:若需改参数列表,需反射替换)
return invocation.proceed();
}
private String buildMysqlPagedSql(String sql, long offset, int size) {
return sql + " LIMIT " + offset + ", " + size;
}
private Page getPageFromParam(Object param) {
if (param instanceof Page) return (Page) param;
if (param instanceof Map) {
for (Object v : ((Map) param).values()) {
if (v instanceof Page) return (Page) v;
}
}
return null;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}
三、注册拦截器到 SqlSessionFactory
在 Spring Boot 中(mybatis-spring-boot-starter),通过配置类注入:
立即学习“Java免费学习笔记(深入)”;
@Configuration
public class MyBatisConfig {
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.addInterceptor(new PaginationInterceptor());
}
}
或在传统 Spring XML 中配置:<bean class="org.mybatis.spring.SqlSessionFactoryBean"></bean> 内设置 plugins 属性。
四、使用建议与注意事项
- 避免直接修改原 SQL 字符串(如用正则替换
SELECT),推荐用 SQL 解析库(如 JSQLParser)提升健壮性 - 务必处理
count查询 —— 分页通常需总条数,可拦截后自动补一个SELECT COUNT(*) FROM (...) t的统计 SQL - 线程安全:分页参数建议通过
ThreadLocal<page></page>传递,避免参数对象被多个查询误用 - 兼容性:MyBatis 3.4+ 推荐用
Plugin.wrap();低版本注意ParameterHandler和ResultSetHandler的联动

















