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. 编写分页拦截器类:
@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()
三、注册拦截器到 SqlSessionFactory
在 Spring Boot 中(mybatis-spring-boot-starter),通过配置类注入:
@Configuration
public class MyBatisConfig {
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.addInterceptor(new PaginationInterceptor());
}
}
或在传统 Spring XML 中配置: 内设置 plugins 属性。
四、使用建议与注意事项
- 避免直接修改原 SQL 字符串(如用正则替换
SELECT),推荐用 SQL 解析库(如 JSQLParser)提升健壮性 - 务必处理
count查询 —— 分页通常需总条数,可拦截后自动补一个SELECT COUNT(*) FROM (...) t的统计 SQL - 线程安全:分页参数建议通过
ThreadLocal传递,避免参数对象被多个查询误用 - 兼容性:MyBatis 3.4+ 推荐用
Plugin.wrap();低版本注意ParameterHandler和ResultSetHandler的联动
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











