南平市网站建设_网站建设公司_Tailwind CSS_seo优化
2026/1/2 20:15:26 网站建设 项目流程

🚨 前言:凌晨 1 点的 OOM 惊魂

场景还原:生产环境某个列表接口突然响应巨慢,紧接着应用抛出OOM (Out Of Memory)崩溃。
排查日志发现,原本应该是分页查询的 SQL,竟然没有LIMIT语句!

-- 期望的 SQL (物理分页)SELECT*FROMuserWHEREstatus=1LIMIT0,10-- 实际执行的 SQL (全表扫描 + 内存分页)SELECT*FROMuserWHEREstatus=1

这就是典型的分页插件失效。如果表里有 100 万行数据,应用服务器的内存瞬间就会被撑爆。


⚔️ 一、 核心原理:MyBatis 的拦截器机制

要理解为什么失效,必须先懂 MyBatis 的插件原理。
MyBatis 允许我们在 SQL 执行的生命周期中进行拦截(Interceptor)。

  • PageHelper:通过PageInterceptor拦截Executor,检测ThreadLocal中是否有Page对象(PageHelper.startPage()),如果有,就改写 SQL。
  • MyBatis-Plus:通过MybatisPlusInterceptor拦截Executor,检测参数中是否有IPage接口的实现类,如果有,就改写 SQL 添加LIMIT

拦截器链示意图 (Mermaid):

1. Mapper 调用

执行最终 SQL

拦截器链 (责任链模式)

放行

放行

改写 SQL (LIMIT)

拦截器 A

拦截器 B

MP 分页拦截器

PageHelper 拦截器

应用代码

MyBatis 核心

数据库


💣 二、 事故现场:三种常见的“失效”姿势

1. 最低级错误:完全没配置拦截器 (MP 3.4.0+)

很多新手以为引入了mybatis-plus-boot-starter就万事大吉了。
错!在 MP 3.4.0 之后,必须显式配置MybatisPlusInterceptorBean,否则分页功能完全不生效。

错误代码:

// ControllerPage<User>page=newPage<>(1,10);userMapper.selectPage(page,null);// 结果:SQL 无 LIMIT,全表查询
2. 隐形冲突:PageHelper 抢戏

如果你的项目里既引入了pagehelper-spring-boot-starter,又想用 MP 的selectPage
PageHelper 的自动配置可能会干扰 MP,或者在同一个 ThreadLocal 里产生了脏数据。
虽然现在的版本兼容性好了很多,但混用依然是 Bug 的温床。

3. 参数传递错误

如果你在自定义 SQL 中使用分页:

// MapperList<User>selectUserList(Page<User>page,@Param("name")Stringname);

如果Page对象不是第一个参数,且没有正确处理,MP 的拦截器可能识别不到它,从而放弃改写 SQL。


🔍 三、 源码深扒:为什么配置了 Bean 还是没用?

假设你已经配置了MybatisPlusInterceptor,但依然失效。我们需要看源码。

打开MybatisPlusInterceptor.java,找到intercept方法:

@OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{// 1. 获取所有内部拦截器 (如分页、乐观锁等)List<InnerInterceptor>interceptors=this.interceptors;// 2. 遍历拦截器for(InnerInterceptorinterceptor:interceptors){// 3. 调用 willDoQuery (关键点!)// 这里会判断是否需要进行分页处理if(!interceptor.willDoQuery(executor,ms,parameter,rowBounds,resultHandler,boundSql)){returninterceptor.beforeQuery(executor,ms,parameter,rowBounds,resultHandler,boundSql);}}// ...}

再看PaginationInnerInterceptor.java(分页拦截器核心):

@OverridepublicbooleanwillDoQuery(...){// 1. 获取参数中的 IPage 对象IPage<?>page=ParameterUtils.findPage(parameter).orElse(null);// 2. 如果参数里没找到 Page 对象,或者 Page.size < 0if(page==null||page.getSize()<0||!page.isSearchCount()){// 直接返回 true,不做处理 -> 导致 LIMIT 缺失!returntrue;}// ...}

结论:只要 MP 在参数列表里找不到IPage对象,它就当作普通查询处理,直接放行,导致“假分页”。


✅ 四、 终极解决方案

方案 1:标准配置 (必做)

在 Spring Boot 配置类中,必须注册MybatisPlusInterceptor,并添加PaginationInnerInterceptor

@ConfigurationpublicclassMybatisPlusConfig{@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();// 1. 添加分页拦截器// DbType.MYSQL 根据你的数据库类型选择,不要漏了!interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));// (可选) 如果有乐观锁等其他插件,也在这里添加// interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());returninterceptor;}}
方案 2:依赖隔离 (推荐)

如果新项目全面拥抱 MP,建议直接移除 PageHelper 依赖

方案 3:自定义 SQL 的规范写法

在 Mapper.xml 对应的 Interface 中,确保Page参数放在第一位,或者虽然不在第一位但 MP 能识别。

// ✅ 推荐写法:Page 放在第一个参数IPage<UserDto>selectCustomUsers(IPage<UserDto>page,@Param("status")Integerstatus);

📊 五、 性能自测:怎么确认分页生效了?

不要只看接口返回的数据条数!

  1. 开启 MP 的 SQL 打印
mybatis-plus:configuration:log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
  1. 观察控制台
  • 正常:看到LIMIT ?, ?结尾的 SQL。
  • 异常:看到SELECT count(0)...查总数,但随后的查询语句没有LIMIT

🎯 总结

MyBatis-Plus 的分页插件失效,90% 都是因为配置缺失依赖冲突
“假分页”是生产环境的隐形杀手,它平时不报错,一到大促流量洪峰就 OOM。

Next Step:
赶紧去检查一下你的 Config 类,那个new MybatisPlusInterceptor()到底加了没?别等到报警电话响了才后悔!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询