新闻详情

新闻详情

首页 / 资讯中心 / 详情

MybatisPlus分页插件配置与深分页优化:分页失效与500条限制实战

发布时间:2026/9/26 20:18:47来源:尧图网络
MybatisPlus分页插件配置与深分页优化:分页失效与500条限制实战
如果你的项目里用了 MybatisPlus并且已经写过头几个 CRUD 接口那你迟早会在分页这件事上踩坑。我接触 MybatisPlus 的第二天就撞上了两个很典型的问题列表接口第一屏数据正常翻到后面 limit 参数完全不生效甚至一次性把整张表的数据全查了出来后来把数据量加到几千条又发现单页条数被框架悄悄砍到 500翻页翻到怀疑人生。这篇文章就是我围绕这两个坑做的实操记录顺便把 MybatisPlus 分页插件的配置方式、底层拦截原理、深分页优化以及我排查问题时的完整思路一次说清楚。适合刚入手 MybatisPlus 的 Java 后端开发也适合那些已经被“分页失效”和“单页 500 条限制”折磨过、想彻底搞明白来龙去脉的同学。1. 接触 MybatisPlus 的第二天先搞清楚它到底帮我省了什么事1.1 为什么 MybatisPlus 能成为持久层“标配”在接触 MybatisPlus 之前我经历过很长一段纯手写 MyBatis XML 的日子。单表 CRUD 的每一个 insert、update、selectById、deleteById 都要在 XML 里写一遍表一多这种重复劳动会非常消耗耐心。后来项目里逐渐换到 MybatisPlus核心舒适区在于单表 CRUD 完全不需要写 SQL 了你的 Mapper 接口只需要继承BaseMapperT十几条常用方法直接就有。它背后的设计思路其实就一条把“单表操作”这种 80% 的重复场景收敛成约定好的通用方法把变化的部分交给条件构造器Wrapper。你用LambdaQueryWrapper、LambdaUpdateWrapper去拼接动态查询条件不需要再去拼字符串 SQL。再加上 MybatisPlus 的IServiceT/ServiceImplM, T体系Service 层写起来也很薄事务、批量操作、链式查询都给你封装好了。但这同时也带来一个问题很多同学只看到了“封装带来的便利”却忽略了 MybatisPlus 本质上还是运行在 MyBatis 之上。分页插件不是生来就有的需要显式注册到 MyBatis 拦截器链里很多人分页失效根因就在这里——框架帮你把该做的都做了但前提是你得按它的规则把该配的配好。1.2 第二天阶段最该掌握的四个核心点如果让我给刚接触 MybatisPlus 的人划重点我会说第二天阶段不用急着学代码生成器、多租户插件这些东西先把下面四块吃透BaseMapper 通用方法知道selectPage、selectList、selectById等方法的签名知道哪个方法默认支持分页哪个不支持。Wrapper 条件构造器多条件查询、模糊查询、排序、in、between这些动态条件的拼法要熟练。IService/ServiceImpl 体系写业务代码时能省下很多样板代码但也得了解saveOrUpdate、lambdaQuery这类链式方法背后的逻辑。分页插件理解分页插件的注册方式、拦截原理以及它有什么默认行为限制。这四个点里面最容易出问题的就是分页。它不像普通 CRUD 那样“继承一下就能跑”分页依赖一个独立的拦截器配置漏一步或者环境稍微差一点表现出来的症状就是“分页失效”。2. 分页插件配置分页失效的大部分原因都藏在这里2.1 新版分页拦截器的正确配置模板MybatisPlus 在 3.4.0 版本之后把原来用于分页的PaginationInterceptor替换成了新的MybatisPlusInterceptorPaginationInnerInterceptor组合。新版设计把所有扩展能力都收敛到了MybatisPlusInterceptor这一个拦截器里分页、乐观锁、防全表更新、多租户等都是通过添加不同的InnerInterceptor来实现。以 Spring Boot 3.x MybatisPlus 3.5.x 为例正确的分页配置模板如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInnerInterceptor new PaginationInnerInterceptor(DbType.MYSQL); // 设置单页最大条数默认是500-1表示不限制我这里先写-1方便演示 paginationInnerInterceptor.setMaxLimit(-1L); // 溢出总页数后是否进行处理true回到首页 paginationInnerInterceptor.setOverflow(false); interceptor.addInnerInterceptor(paginationInnerInterceptor); return interceptor; } }这里有两个容易踩的细节。第一DbType一定要正确指定分页插件要根据数据库方言生成不同的分页 SQL。比如 MySQL 是LIMIT ?PostgreSQL 是LIMIT ? OFFSET ?SQL Server 又是另一种写法。你用的是 MySQL 却没有传DbType.MYSQL插件在方言识别上就可能出问题。第二拦截器是Bean交给 Spring 管理的如果项目里有多个地方定义MybatisPlusInterceptorBean也会造成冲突或覆盖。2.2 分页失效的六种典型症状与排查顺序我把实际排查过程中遇到过的分页失效场景整理了一张表症状不同根因往往也不同。症状常见原因处理方式total 为 0records 却返回全部数据分页拦截器没有注册或注册的旧版 PaginationInterceptor 已失效确认 MybatisPlusInterceptor Bean 存在且在 Spring 容器中生效自定义 Mapper 方法分页返回全部数据方法参数里没有 IPage或者 IPage 不在参数第一位自定义分页方法第一个参数必须是IPageT返回值用IPageT老项目从 Spring Boot 1/2 升级到 3 后分页失效依赖用错了Spring Boot 3 不支持mybatis-plus-boot-starter改成mybatis-plus-spring-boot3-starter分页参数明明传了 size100查回来还是 500命中 PaginationInnerInterceptor 的 maxLimit 限制调整 maxLimit 或自定义 LimitHandler分页 SQL 里自己手写了 LIMITMP 不会覆盖你手动拼接的 LIMIT去掉手写 LIMIT让分页插件来追加多数据源中部分数据源分页失效PaginationInnerInterceptor 固定了单一方言为不同数据源配置对应的方言或使用多拦截器策略排查分页问题我建议按照一个固定的顺序来不要焦虑地乱试。第一步看日志。把 MyBatis 的 SQL 日志开关打开看打印出来的 SQL 到底有没有LIMIT关键字。如果有LIMIT且正确说明分页插件在工作问题在业务层如果完全没有LIMIT那就是拦截器没生效方向直接奔着配置去。第二步检查依赖和版本。MybatisPlus 和 Spring Boot 版本不匹配是升级场景里特别常见的坑旧拦击器类被移除之后项目可能连启动都不会报错但分页就是静默失效。第三步检查自定义 Mapper 方法的签名。在写自定义分页 SQL 时方法参数列表里如果没有IPageMybatisPlus 是无法知道你要分页的。第四步确认 Page 对象有没有真的传给查询方法。有的人在 Service 层 new 了一个 Page却调用selectList(wrapper)而不是selectPage(page, wrapper)Page 白建了。3. 单页 500 条限制是保护也是坑3.1 500 条限制到底是谁加上的查询单页 500 条限制是 MybatisPlus 分页插件自带的一个“保险丝”。PaginationInnerInterceptor在构造时会默认把maxLimit设置为 500L意思就是不管你前端传的size是多少只要大于 500分页插件就会把它强制改成 500。这个限制并不是数据库加上的。MySQL 的LIMIT子句本身没有“最多只能 500 条”这种说法只要 offset 和 row count 范围合法一次查出 10 万条也是可以的。所以这是 MybatisPlus 在设计上的有意为之核心目的就是防呆防止某些开发同学在页面上写了个巨大的size直接把数据库拖垮或者一次性把整张表的数据都拉出来。我第一次遇到这个限制的现场很无厘头前端同事写了一个批量同步功能接口参数不小心把每页条数传成了 1000我在后端看到page.getRecords().size()永远是 500但page.getTotal()又是对的。排查了半天才发现不是查询写错了而是被maxLimit拦截了。3.2 突破 500 限制的三种做法与适用场景既然这是框架设定要突破它其实有三种做法每种适用场景不一样。做法一直接调大或放开全局限制paginationInnerInterceptor.setMaxLimit(1000L); // 或者完全放开 paginationInnerInterceptor.setMaxLimit(-1L);优点是最简单一行配置就能解决。缺点也很明显这是一个全局规则一旦放开项目里所有分页查询都没有了兜底保护。如果团队还有人在写那种“一次查全量”的接口风险会高很多。生产环境不推荐很随意地setMaxLimit(-1L)除非你有明确的接口压测数据并且确认每个分页接口的查询量都在可控范围内。做法二自定义 LimitHandler针对不同 Mapper 放行PaginationInnerInterceptor支持传入一个LimitHandler实现来定制“是否限制”和“限制多少条”。这样就能做到某个列表接口允许 2000 条其他接口还是默认 500 条。public class CustomLimitHandler implements LimitHandler { private static final MapString, Long LIMIT_MAP new HashMap(); static { // key 是 Mapper 方法的全限定名value 是允许的最大条数 LIMIT_MAP.put(cc.xa.mapper.UserMapper.selectExportList, 2000L); } Override public boolean isNeedLimit() { // 根据实际的 Mapper 方法名动态判断 return true; } Override public long getMaxLimit() { String mapperId // 这里从上下文拿到当前执行的 Mapper 方法全限定名 return LIMIT_MAP.getOrDefault(mapperId, 500L); } }不过说实话这个方案实现起来比较绕上下文获取当前 Mapper ID 还需要结合 MyBatis 的BoundSql去做配置成本不低。大多数业务场景我用做法一的“全局限制调大”就够了自定义 LimitHandler 适合那些需要精细化管理的团队。如果你决定走这个方案务必保证方法名的 key 和实际运行时的全限定名一致否则不会命中。做法三不改代码改交互设计说实话Web 列表页很少有场景真的需要用户在单页里看 500 条以上的数据。表格单页 2050 条是体验最好的区间10 万条数据可以翻 2000 页但正常人不会这么用。如果你真的面临大列表更合理的方向是提供筛选条件、搜索框、导出功能导出走异步任务任务内部做分批查询或流式查询而不是想着突破单页 500 条。3.3 数据量大时该改的不是限制而是分页方式顺着上面说的“单页 500 条”继续延伸你会发现另一个更重要的问题当单表数据量到了百万级别你以为把maxLimit调到 5000 就完事了实际深分页一样把你打得抬不起头。比如 MySQL 执行LIMIT 1000000, 20数据库并不是“从第 1000000 行开始拿 20 条”而是先扫描 1000020 行再把前 1000000 行丢掉。这个操作随着页码增大越来越慢因为扫描的行数一直在膨胀。优化方向不是调大单页条数而是改变翻页方式方案一基于主键游标翻页。前端记住上一次最后一条记录的主键下次查询带上WHERE id 上一次的id或WHERE id 上一次的id配合主键索引查询速度不会随着页数增加而劣化。方案二先查主键再回表。先用LIMIT查出主键集合再用WHERE id IN (主键集合)去查完整记录减少回表扫描量。方案三限制可用页码。列表页只允许“上一页/下一页”不支持输入页码跳转本质上避免了深分页。在 MybatisPlus 里如果做游标分页其实可以不用IPage那一套了直接在 Wrapper 里加上字段条件和last(LIMIT 20)手动控制。虽然失去了total总条数但对百万级大列表来说total本身有两面性查询总条数也需要全表扫描你省掉这个count反而能让接口快很多。4. 分页服务层封装与优化经验4.1 把 IPage 包装成响应体别把 MP 类直接暴露给前端很多项目里会看到 Controller 直接返回IPage对象这其实不是一个好习惯。IPage自带current、size、records、total、pages等字段和前端约定的字段名不一定一致而且一旦返回了 MP 的类上层业务代码就和 MybatisPlus 绑定死了后续换框架或者做字段裁剪都很难受。我习惯在 Service 层把分页结果转换成一个通用的PageResultTpublic class PageResultT { private ListT records; private Long pageNum; private Long pageSize; private Long total; private Long pages; public static T PageResultT of(IPageT page) { PageResultT result new PageResult(); result.setRecords(page.getRecords()); result.setPageNum(page.getCurrent()); result.setPageSize(page.getSize()); result.setTotal(page.getTotal()); result.setPages(page.getPages()); return result; } }这样做有几个实际好处。第一pageNum和pageSize可以按照自己项目的命名规范来前端和后台的契约稳定第二可以把records里查出来的实体批量转成 VO 再塞回去第三后续如果某个接口改成游标分页PageResult的字段可以平滑演进。4.2 深分页优化从 limit offset 走向游标和子查询前面已经提到深分页慢的核心原因这里展开分享一个我在真实场景里用过的优化经验。业务背景是这样某张业务流水表数据量大概 300 万行管理后台需要支持按时间倒序查看流水。第一版我直接让前端传页码和每页条数用 MP 的selectPage查。测到第 200 页的时候接口耗时已经超过了 3 秒。加索引也没用因为LIMIT 400000, 20的 offset 本身就是要扫 40 万行。后来改成游标方案。前端不再传页码而是传一个lastId我按主键倒序查询LambdaQueryWrapperBizRecord wrapper new LambdaQueryWrapper(); wrapper.eq(BizRecord::getStatus, 1) .lt(BizRecord::getId, lastId) // lastId 为上一页最后一条记录的 ID .orderByDesc(BizRecord::getId) .last(LIMIT 20); ListBizRecord nextPage bizRecordMapper.selectList(wrapper);这样每次查询走的都是主键索引的范围扫描扫描行数固定在 20 行左右200 页和 2 亿页的性能是一样的。代价是失去了“跳到任意页”的能力也不能直接拿到总条数。但管理后台的流水列表绝大多数用户的真实路径就是“往下一页一页翻”这个接受度是很高的。如果你既要页码跳转又要性能那可以考虑“先查主键”的优化思路代码大致是这样SELECT * FROM biz_record WHERE id IN ( SELECT id FROM biz_record WHERE status 1 ORDER BY create_time DESC LIMIT 400000, 20 )在这个写法里内层查询的覆盖索引只返回主键和排序字段扫描成本比回表后再丢弃低很多。配合 MybatisPlus 写自定义 SQL 时把这段话直接写在 XML 里即可。注意内层子查询的ORDER BY字段务必有索引否则也不会快。4.3 count 查询的隐性成本每当我听到“分页查询要返回 total所以必须 count 一次”时都会多说一句这个 count 可能比列表查询本身还费钱。MybatisPlus 的分页插件在查询时会默认生成一条 count SQL。它对常见查询能做一定程度的优化但如果你在查询条件里关联了多张表或者用了复杂子查询count 语句可能无法优化到理想的形态。我的习惯是数据量小无所谓数据量大了以后要么优化 count 写法要么干脆不要 count。有一个很实用的判断标准如果列表数据本身是一个“无限滚动的信息流”用户根本不关心总共有多少条那就没必要返回total。在这种情况下可以直接用一个不带 count 的分页方式比如前面的游标方案或者自定义 SQL 时只查询一页数据。5. 常见问题排查速查表与实操心得5.1 分页相关问题的症状、原因与解决办法速查表症状原因解决办法翻页超过一定页数后变慢深分页导致 offset 过大改游标分页先查主键再回表限制页码跳转page.getTotal()总是 0拦截器没注册方言类型不匹配检查 MybatisPlusInterceptor Bean检查 DbType 配置page.getRecords()返回全部数据查询方法不是 selectPage拦截器缺失改用 selectPage注册分页插件size传 1000 实际返回 500maxLimit 默认限制调大 maxLimit使用自定义 LimitHandler自定义 Mapper 方法分页失败方法参数里没放 IPage或 IPage 不在第一位第一个参数改成 IPage 方法返回 IPage多表 join 分页 total 不准确MP 自动生成的 count 语句不支持复杂 join手写 count SQL或在 XML 中定制 count 语句分页后排序字段数据错乱ORDER BY 字段无索引或重复值多主键/业务排序键加索引补充 secondary order by获取pages总页数时和业务预期不一致overflow 开关为 false超出总页数时不处理按需设置 setOverflow(true) 回到首页或前端兜底这些场景基本覆盖了我在真实项目里碰到过的绝大多数分页问题。你会发现真正意义上“Bug级”的问题很少大量问题都出在“配置没生效”和“默认限制”这两类原因上。所以排查前先把这两类原因过一遍能够节省大量时间。5.2 排查分页问题的实操套路我把自己后来固定的排查流程写一下照着做基本不会漏先看依赖确定 MybatisPlus 版本核对 Spring Boot 版本是否匹配。看配置类里有没有MybatisPlusInterceptorBean有没有分页插件被 add 进去。开 MyBatis SQL 日志定位到具体查询语句看有没有LIMIT有的话看限制值是否正确。对比page.getTotal()和page.getRecords().size()如果 total 是 0 但 records 是对的就是方言或拦截器问题如果 total 对但 records 一直是 500就是 maxLimit 撞上了。如果是自定义 Mapper 方法检查方法签名确认参数列表第一个是 IPage。顺便看一眼返回类型是否也是 IPage。最后再确认排序字段是否有索引。这一步经常被忽略ORDER BY字段如果没索引深分页的慢会让你误以为是分页插件的问题。这个流程下来大多数分页问题 10 分钟内都能定位。5.3 两个值得记住的心得第一MybatisPlus 的分页是“单表神器”不是“复杂查询神器”。如果查询语句涉及多表 join、子查询、聚合统计我一般会直接在 XML 里手写分页 SQL利用 IPage 参数让拦截器帮忙生成 LIMIT但查询主体的控制权完全留给自己。不要硬把复杂的统计需求塞进 Wrapper 里那只会让你写出难以维护的代码。第二setMaxLimit(-1L)这个开关我建议在本地开发环境随便开但生产环境一定要谨慎。日常开发中你确实会遇到“分页条数被限制到 500 导致导出数据不完整”的情况这时候正确的做法是在导出任务里做分批循环每次取 500 条追加写入而不是给分页接口设置一个巨大的 size。把“列表展示”和“数据导出”这两种场景分开对待你的代码质量和接口稳定性都会好很多。最后分享一个小技巧如果你怀疑某个自定义 Mapper 方法的分页没生效不要盯着 XML 一直看最快的验证方式是在方法参数里加一个Page然后在日志里搜LIMIT。有LIMIT说明拦截器参与了没有LIMIT大概率是参数签名的问题。这个验证方法我用了很久至今仍然是最直接的定位手段。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Atlas 300V部署YOLOv5实战:从ONNX转OM到ACL推理 2026/9/26 21:07:12

Atlas 300V部署YOLOv5实战:从ONNX转OM到ACL推理

先直接回答那个让我被问烂了的问题:Atlas 300V 24G到底是不是运算加速卡?是,但它不是你以为的那种“加速卡”。它长得像一张显卡,驱动装好之后npn-smi info里看到的却不是CUDA设备,而是一颗昇腾310P芯片。很多第一次接…

阅读更多 →
百度网盘下载慢?从传输节点到网络环境,五招实测提速攻略 2026/9/26 21:07:12

百度网盘下载慢?从传输节点到网络环境,五招实测提速攻略

我自己的网盘里常年躺着几百GB的学习资料和软件镜像,每次要拉下来,速度一旦掉到几十KB/s就恨不得把电脑扔掉。百度网盘下载速度慢这件事,遇到的人确实太多,但这篇不是劝你盲目开会员,也不是让你去碰那些来路不明的第三…

阅读更多 →
从零开始学AI:用TaoToken统一Key打通LLM与RAG的配置骨架 2026/9/26 21:07:12

从零开始学AI:用TaoToken统一Key打通LLM与RAG的配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
电力系统碳排放流计算详解:基于IEEE 14节点的Matlab复现 2026/9/26 21:06:59

电力系统碳排放流计算详解:基于IEEE 14节点的Matlab复现

1. 碳排放流到底在算什么:一个容易误解的切入点做电力系统碳排放分析,最容易踩的坑就是把“碳排放因子乘总发电量”当成完整结论。这种总量核算应付报告可以,一旦想回答“某个城市、某个工业用户、某条联络线上到底承担了多少碳”&#xff0c…

阅读更多 →
OpenClaw本地部署全指南:从环境准备到问题排查 2026/9/26 21:06:46

OpenClaw本地部署全指南:从环境准备到问题排查

如果 2026 年你还在把 OpenClaw 这类 AI 助手完全跑在云端,那我建议你花一个周末试试本地部署。这篇 OpenClaw 本地部署全指南就一个目标:带你从环境准备走到实战运行。OpenClaw 是一个开源的个人 AI Agent 网关,核心作用是打通"大模型能…

阅读更多 →
百度网盘下载慢?从链路瓶颈到客户端设置,五种实测提速方法 2026/9/26 21:06:46

百度网盘下载慢?从链路瓶颈到客户端设置,五种实测提速方法

百度网盘下载速度慢这件事,几乎成了国内互联网用户共同的“默契痛点”。很多人第一反应就是“百度在限速”,但我在实际排查中发现问题往往没那么简单——宽带、路由器、网线、客户端设置、甚至你要下载的文件本身,都可能成为真正的短板。这篇…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉