MyBatis Plus分页拦截器冲突:双Limit问题排查与解决
1. 双Limit问题现象与复现最近在项目中使用MyBatis Plus做分页查询时发现生成的SQL语句里竟然出现了两个LIMIT子句比如LIMIT 0,10 LIMIT 0,10。这种问题会导致数据库执行报错分页功能完全失效。经过排查发现这是MyBatis Plus 3.4版本后一个典型的配置冲突问题。我遇到的具体场景是这样的项目原本使用的是3.4.1版本的MyBatis Plus按照旧版习惯配置了PaginationInterceptor。后来引入了一个公共组件包这个包里又自动配置了新的MybatisPlusInterceptor。启动项目后每次分页查询都会生成重复的LIMIT语句。用个简单的测试用例就能复现这个问题Test public void testDuplicateLimit() { PageUser page new Page(1, 10); userMapper.selectPage(page, null); System.out.println(page.getRecords()); }执行后查看日志会发现SQL变成了类似这样SELECT * FROM user LIMIT 0,10 LIMIT 0,102. 问题根源分析2.1 拦截器工作机制MyBatis的插件机制是基于责任链模式实现的。当有多个拦截器时它们会按照配置顺序依次执行。在分页场景下每个分页拦截器都会在SQL执行前添加自己的LIMIT逻辑。关键问题在于旧版的PaginationInterceptor和新版的MybatisPlusInterceptor都是独立的分页拦截器。如果两者同时存在MyBatis会依次执行这两个拦截器导致LIMIT子句被添加两次。2.2 版本演进带来的变化MyBatis Plus在3.4版本进行了重大调整3.4之前使用单独的PaginationInterceptor3.4之后推荐使用MybatisPlusInterceptor统一管理所有插件这个变化本意是为了统一插件管理但如果没有及时更新旧配置就会导致新旧拦截器共存的情况。我在项目中就是遇到了这种过渡期的问题 - 旧代码保留了PaginationInterceptor的配置而新引入的公共包又添加了MybatisPlusInterceptor。3. 解决方案与实践3.1 标准解决方案最彻底的解决方式是统一使用新的MybatisPlusInterceptor。具体配置如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 添加分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } // 必须注释或删除旧的配置 // Bean // public PaginationInterceptor paginationInterceptor() { // return new PaginationInterceptor(); // } }这里有几个关键点需要注意完全移除PaginationInterceptor的Bean定义通过addInnerInterceptor方法添加PaginationInnerInterceptor如果需要支持多种数据库可以设置数据库类型new PaginationInnerInterceptor(DbType.MYSQL)3.2 临时解决方案如果项目暂时不能完全移除旧配置可以采用以下临时方案在公共配置类中添加条件判断Bean ConditionalOnMissingBean(MybatisPlusInterceptor.class) public MybatisPlusInterceptor mybatisPlusInterceptor() { // 配置同上 }或者在特定场景下手动清理分页参数// 在执行分页查询前 PageHelper.clearPage();不过这些方案都有局限性建议尽快迁移到标准方案。4. 深度排查技巧4.1 如何确认拦截器冲突当遇到分页问题时可以通过以下方式确认是否是双拦截器导致查看启动日志中的Interceptor注册信息使用调试模式观察SQL构建过程在MyBatis配置中开启完整SQL日志mybatis-plus.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl4.2 其他可能引起冲突的场景除了本文讨论的主要场景外还有一些情况也可能导致类似问题自定义分页拦截器与官方拦截器混用不同模块重复配置拦截器旧版本PageHelper与MyBatis Plus混用我曾经遇到过一个棘手案例项目同时使用了PageHelper和MyBatis Plus两者都修改了SQL语句导致分页完全混乱。最终解决方案是统一使用MyBatis Plus的分页机制彻底移除PageHelper依赖。5. 最佳实践建议基于多次项目实战经验我总结出以下MyBatis Plus分页配置的最佳实践版本统一确保所有模块使用相同版本的MyBatis Plus单一配置源在基础模块中集中配置MybatisPlusInterceptor禁用自动配置如果使用Spring Boot注意自动配置的冲突mybatis-plus.auto-configfalse测试验证编写单元测试验证分页SQL的正确性Test void testPaginationSQL() { String sql userMapper.selectPage(new Page(2, 10), null).getSql(); assertThat(sql).contains(LIMIT 10,10); }监控报警在生产环境添加SQL监控及时发现异常分页语句6. 源码解析与原理理解MyBatis Plus分页的实现原理能帮助我们更好地排查问题。核心流程大致如下拦截器注册阶段// MybatisPlusInterceptor初始化 public void addInnerInterceptor(InnerInterceptor innerInterceptor) { innerInterceptors.add(innerInterceptor); }SQL拦截阶段// PaginationInnerInterceptor执行逻辑 public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 解析分页参数 // 修改原始SQL String newSql dialect.buildPaginationSql(originalSql, page, boundSql); }SQL重写逻辑以MySQL为例public String buildPaginationSql(String originalSql, long offset, long limit) { return originalSql LIMIT offset , limit; }当两个拦截器都执行这个流程时自然就会出现双LIMIT问题。这也是为什么新版本要将所有拦截器统一管理的原因 - 确保每个SQL修改操作都能有序执行。7. 常见问题排查清单根据社区反馈和自身经验我整理了一份双LIMIT问题的排查清单检查依赖版本dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency搜索项目中的PaginationInterceptor定义检查是否有模块引入了自动配置确认没有混用PageHelper查看生成的SQL日志检查Interceptor的执行顺序确保没有自定义分页逻辑冲突这个清单基本覆盖了90%的双LIMIT问题场景。按照这个顺序排查通常能在10分钟内定位问题根源。8. 项目升级指南对于还在使用旧版MyBatis Plus的项目建议按照以下步骤升级先升级MyBatis Plus到最新稳定版全局搜索移除PaginationInterceptor添加统一的MybatisPlusInterceptor配置测试所有分页相关功能特别注意动态数据源等特殊场景在最近的一个微服务改造项目中我们用了这个方案成功升级了15个服务整个过程比较平滑。唯一遇到的坑是有一个服务自定义了方言实现需要额外适配新的拦截器接口。9. 性能优化建议解决了基础功能问题后还可以进一步优化分页性能使用优化后的count查询new PaginationInnerInterceptor().setOptimizeJoin(true)对大表使用延迟关联优化SELECT * FROM user INNER JOIN (SELECT id FROM user LIMIT 100000,10) AS tmp USING(id)缓存常用分页结果考虑使用游标分页替代传统分页这些优化在我的电商项目中将商品列表页的加载时间从800ms降低到了200ms左右效果非常明显。