PageHelper 内部到底怎么实现的?手写一个 MyBatis 分页插件,深入理解插件拦截机制。

一、MyBatis 插件的拦截原理

  • 1.1 可拦截的四大对象——Executor / ParameterHandler / StatementHandler / ResultSetHandler
  • 1.2 拦截哪个对象决定插件的功能——分页插件拦截 Executor 或 StatementHandler
  • 1.3 Plugin.wrap() 动态代理的实现

二、分页插件设计思路

  • 2.1 拦截点选择——Executor.query() 还是 StatementHandler.prepare()
  • 2.2 PageHelper 的思路——拦截 Executor
  • 2.3 自定义插件的思路——拦截 StatementHandler

三、拦截 StatementHandler 实现分页

  • 3.1 获取原始 SQL——BoundSql.getSql()
  • 3.2 拼接 count SQL——SELECT COUNT(*) FROM (原SQL) _count
  • 3.3 拼接分页 SQL——LIMIT offset, pageSize
  • 3.4 反射修改 StatementHandler 中的 BoundSql
  • 3.5 设置分页参数——Page 对象的 ThreadLocal 传递

四、拦截 Executor 实现分页

  • 4.1 拦截 Executor.query(MappedStatement, Object, RowBounds, ResultHandler)
  • 4.2 修改 MappedStatement 中的 SQL
  • 4.3 这种方式的优势——可以同时获取 count 和 list

五、完整插件代码

  • 5.1 注解配置——@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
  • 5.2 intercept() 方法的核心逻辑
  • 5.3 Dialect 接口——适配不同数据库(MySQL / PostgreSQL / Oracle)

六、测试验证

  • 6.1 配置插件——mybatis-config.xml 或 Spring Boot 配置
  • 6.2 测试分页查询——验证 SQL 改写和执行结果
  • 6.3 性能对比——与 PageHelper 的性能差异

七、总结

  • 插件的核心是”拦截 + 增强”
  • 编写插件的注意事项——ThreadLocal 清理、性能考量