手写 MyBatis 分页插件思路?¶
一、原理¶
MyBatis 插件用 Interceptor 拦截四大对象(Executor、ParameterHandler、ResultSetHandler、StatementHandler)。分页插件拦截 StatementHandler#prepare,在 SQL 执行前改写原 SQL,加 LIMIT ?, ?。
二、实现步骤¶
1. 定义分页对象¶
2. 实现 Interceptor¶
@Intercepts({
@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})
})
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler handler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(handler);
// 1. 拿到 BoundSql 和原始 SQL
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// 2. 判断是否需要分页(从参数中找 PageParam)
Object parameterObject = boundSql.getParameterObject();
PageParam page = findPage(parameterObject);
if (page == null) {
return invocation.proceed();
}
// 3. 改写 SQL:MySQL 用 limit
String pageSql = sql + " LIMIT " + page.getOffset() + "," + page.getPageSize();
// 4. 用 MetaObject 写回新 SQL
metaObject.setValue("boundSql.sql", pageSql);
return invocation.proceed();
}
private PageParam findPage(Object param) {
if (param instanceof MapperMethod.ParamMap) {
Map<?, ?> map = (Map<?, ?>) param;
for (Object v : map.values()) {
if (v instanceof PageParam) return (PageParam) v;
}
}
return null;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) { }
}
3. 注册插件¶
<configuration>
<plugins>
<plugin interceptor="com.example.PageInterceptor"/>
</plugins>
</configuration>
或 Spring Boot:
三、Count 查询¶
分页插件通常还需要执行一次 SELECT COUNT(*) 来拿总数。做法:
- 拦截
Executor#query。 - 如果方法上标了
@Pageable,生成 count SQL: - 先执行 count,再执行分页 SQL。
四、现有轮子¶
生产环境直接用 PageHelper:
PageHelper.startPage(2, 10);
List<User> list = userMapper.selectAll();
PageInfo<User> info = new PageInfo<>(list);
long total = info.getTotal();
面试加分
- 为什么拦截
StatementHandler#prepare而不是 Executor?因为此时 SQL 已经解析完毕,参数已绑定,直接改 SQL 字符串最干净。 MetaObject是 MyBatis 提供的反射工具,可以安全地访问私有字段。- 方言问题:MySQL 用
LIMIT,Oracle 用ROWNUM,PostgreSQL 用LIMIT,要按数据库类型生成分页 SQL。