news 2026/5/1 3:20:23

面试官:MyBatis 是如何进行分页的?分页插件的原理是什么?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
面试官:MyBatis 是如何进行分页的?分页插件的原理是什么?

在线 Java 面试刷题(已更新239题,图文并茂):https://www.quanxiaoha.com/java-interview

面试考察点

  1. 分页方式认知:面试官不仅仅是想知道你 "用过 PageHelper",更是想考察你是否理解逻辑分页和物理分页的区别,以及为什么生产环境必须用物理分页。

  2. 插件原理理解:看你能否说清楚 PageHelper 分页插件的底层原理——基于 MyBatis 的Interceptor机制拦截 SQL,自动改写为分页 SQL。

  3. 生产实践意识:考察你是否知道分页插件的坑(如count查询的性能问题、深度分页优化等)。

核心答案

MyBatis 的分页方式分为两种:逻辑分页(内存分页)和物理分页(数据库分页)。

分页方式

原理

性能

生产推荐

逻辑分页(RowBounds)

查出全部数据,内存中截取

⭐ 差

❌ 不推荐

物理分页(手写 SQL)

SQL 加LIMIT子句

⭐⭐⭐ 好

✅ 可用

物理分页(PageHelper)

插件自动改写 SQL 加LIMIT

⭐⭐⭐ 好

推荐

物理分页(MyBatis-Plus)

内置分页插件

⭐⭐⭐ 好

✅ 推荐

一句话结论:生产环境使用物理分页,最常用的方案是 PageHelper 或 MyBatis-Plus 分页插件,底层原理是利用 MyBatis 的Interceptor拦截 SQL 并自动拼接LIMIT语句。

深度解析

一、逻辑分页 vs 物理分页

img

两种分页方式的核心差异在于数据在哪里被截断

  • 逻辑分页:SQL 不加任何限制,查出全部数据加载到内存,再用RowBounds在 Java 层截取指定范围的数据。数据量大时内存直接炸掉,绝对不能在生产环境使用。

  • 物理分页:SQL 层面就通过LIMIT/ROWNUM等子句限制返回的数据量,数据库只返回一页的数据。内存占用小,性能稳定。

加入小哈的星球,你将获得:专属的项目实战(4个项目) / 1v1 提问 / 简历修改 /Java 学习路线 /社群讨论 /学习打卡 / 每月赠书

  • 《仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍;演示地址:http://116.62.199.48:7070/

  • 《Spring AI 应用(RAG 智能客服)》已完结, 基于 Spring AI + Spring Boot 3.x + JDK 21

  • 《秒杀系统设计》正在更新中,单体到微服务高并发架构演进

  • 《前后端分离博客项目(全栈开发)》已完结,演示链接:http://116.62.199.48/

  • 项目阅读地址:https://quanxiaoha.com/column

截止目前,累计输出 120w+ 字,讲解图 4013+ 张,还在持续爆肝中..戳我加入学习,解锁全部项目,已有4500+小伙伴加入

二、逻辑分页——RowBounds(了解即可)

MyBatis 内置了RowBounds支持逻辑分页:

// offset:起始位置(从 0 开始),limit:每页条数 // 查第 3 页,每页 10 条 → offset = 20, limit = 10 List<User> users = sqlSession.selectList( "com.example.mapper.UserMapper.selectAll", null, new RowBounds(20, 10) // 跳过前 20 条,取 10 条 );

底层原理:MyBatis 执行完整的 SQL 查询,将所有结果加载到内存后,通过DefaultResultHandler跳过offset条记录,只取limit条。

致命缺陷:100 万条数据只看 10 条,却要把 100 万条全加载到内存。生产环境绝对不能用

三、物理分页——手写 SQL

最原始的方式,直接在 SQL 中手写LIMIT

<select id="selectByPage" resultType="User"> SELECT * FROM t_user ORDER BY id DESC LIMIT #{offset}, #{pageSize} </select>
// 调用 int pageNum = 3; // 第 3 页 int pageSize = 10; // 每页 10 条 int offset = (pageNum - 1) * pageSize; // 偏移量 = 20 List<User> users = userMapper.selectByPage(offset, pageSize);

缺点:每次分页都要手算offset,还要单独写COUNT(*)查询获取总数,代码重复且容易出错。

四、物理分页——PageHelper 插件(重点)

PageHelper 是国内最流行的 MyBatis 分页插件,使用非常简单:

// 使用 PageHelper 分页 PageHelper.startPage(3, 10); // 第 3 页,每页 10 条 List<User> users = userMapper.selectAll(); // 正常查询,自动分页 PageInfo<User> pageInfo = new PageInfo<>(users); System.out.println("总记录数:" + pageInfo.getTotal()); // 100 System.out.println("总页数:" + pageInfo.getPages()); // 10 System.out.println("当前页数据:" + pageInfo.getList().size()); // 10 System.out.println("是否有下一页:" + pageInfo.isHasNextPage()); // true

核心 API

API

说明

PageHelper.startPage(pageNum, pageSize)

开启分页,仅对紧随其后的第一条查询生效

PageHelper.offsetPage(offset, limit)

基于 offset 的分页

PageInfo

分页信息封装类,包含总记录数、总页数、页码列表等

注意PageHelper.startPage()只对紧跟其后的第一条查询生效。这是通过ThreadLocal实现的——调用startPage()后将分页参数存入ThreadLocal,下一次查询消费后自动清除。

五、PageHelper 的底层原理

PageHelper 的核心原理是MyBatis 插件(Interceptor)机制+SQL 改写

img

PageHelper 的完整执行流程可以拆解为以下关键步骤:

  • 步骤一(设置分页参数):调用PageHelper.startPage(3, 10),将分页参数封装为Page对象,存入当前线程的ThreadLocal。这一步和查询方法是分开调用的,通过ThreadLocal在两者之间传递分页信息。

  • 步骤二(拦截查询)PageInterceptor实现了 MyBatis 的Interceptor接口,通过@Signature注解拦截了Executorquery方法。当查询执行时,插件介入。

  • 步骤三(SQL 改写):这是核心。插件从ThreadLocal取出分页参数,使用 SQL 解析器(JSqlParser)解析原始 SQL 的 AST(抽象语法树),根据数据库方言自动拼接LIMIT(MySQL)、ROWNUM(Oracle)、TOP(SQL Server)等分页子句。

  • 步骤四(COUNT 查询):如果需要总数(默认会查),插件会将原始 SQL 改写为SELECT COUNT(*) FROM ...的形式,去掉ORDER BYLIMITLEFT JOIN等不影响总数的部分,单独执行一次获取总记录数。

  • 步骤五(清理 ThreadLocal):查询完成后,自动清除ThreadLocal中的分页参数,确保不影响后续查询。

六、SQL 改写的实现细节

PageHelper 使用JSqlParser库解析和改写 SQL:

// 简化版 SQL 改写逻辑 public String addLimit(String originalSql, int offset, int limit) { // 1. 用 JSqlParser 解析 SQL 为 AST Statement stmt = CCJSqlParserUtil.parse(originalSql); Select select = (Select) stmt; // 2. 创建 LIMIT 子句 Limit limitObj = new Limit(); limitObj.setOffset(offset); // 跳过多少条 limitObj.setRowCount(limit); // 取多少条 // 3. 将 LIMIT 设置到 SELECT 语句中 select.getSelectBody().setLimit(limitObj); // 4. 重新生成 SQL 字符串 return stmt.toString(); }

COUNT 查询的改写

-- 原始 SQL SELECT u.id, u.username, d.dept_name FROM t_user u LEFT JOIN t_dept d ON u.dept_id = d.id WHERE u.status = 1 ORDER BY u.created_time DESC -- 改写后的 COUNT SQL SELECT COUNT(0) FROM t_user u WHERE u.status = 1 -- 去掉了 SELECT 的列、LEFT JOIN(不影响行数)、ORDER BY

七、深度分页的性能问题

offset非常大时(比如第 10 万页),LIMIT的性能会急剧下降:

-- 第 10 万页,每页 10 条 SELECT * FROM t_user ORDER BY id DESC LIMIT 10 OFFSET 999990; -- MySQL 实际上要先扫描 100 万条记录,再丢弃前 999990 条,只返回最后 10 条

优化方案

方案

原理

适用场景

游标分页(推荐)

WHERE id > lastMaxId LIMIT 10替代OFFSET

App 无限滚动、瀑布流

子查询优化

WHERE id >= (SELECT id FROM t_user ORDER BY id LIMIT 999990, 1)

传统分页

延迟关联

INNER JOIN (SELECT id FROM t_user LIMIT 999990, 10) t

传统分页

八、常见误区

  1. 误区一:"PageHelper.startPage()对所有查询都生效"

  • 不是。它只对紧跟其后的第一条查询生效,查询完成后ThreadLocal中的分页参数自动清除。这是为了防止分页参数 "污染" 后续的非分页查询。

  • 误区二:"PageHelper会自动识别所有查询需要分页"

    • 不是。必须在查询前显式调用PageHelper.startPage()。如果没调用,就是普通查询,不会分页。

  • 误区三:"分页插件的count查询没有性能问题"

    • 有。count查询在大表上可能很慢,尤其是有复杂JOINWHERE条件时。PageHelper 支持配置countColumn来优化(比如用COUNT(0)替代COUNT(*)),或者手动指定count查询的 SQL。

    面试高频追问

    1. 追问一

      :PageHelper 是怎么保证只对第一条查询生效的?

    • 通过ThreadLocal实现。startPage()将分页参数存入ThreadLocal,查询执行时PageInterceptor消费分页参数后立即从ThreadLocal中移除,后续查询就不会再被分页了。

  • 追问二

    :MyBatis-Plus 的分页插件和 PageHelper 有什么区别?

    • 原理相同,都是基于 MyBatis 的Interceptor拦截 SQL 并改写。区别在于:MyBatis-Plus 的分页插件需要传入IPage参数(编译时就能确定分页),而 PageHelper 通过ThreadLocal(运行时绑定)。MyBatis-Plus 的方式更明确,不容易出错。

  • 追问三

    :深度分页怎么优化?

    • 最推荐的方案是 "游标分页":用WHERE id > lastMaxId ORDER BY id LIMIT 10替代LIMIT OFFSET,避免扫描和丢弃大量数据。对于必须用传统分页的场景,可以用 "延迟关联" 优化:先通过子查询在索引上定位id,再回表取数据。

    常见面试变体

    • 变体一:"MyBatis 的逻辑分页和物理分页有什么区别?"

    • 变体二:"说说 PageHelper 分页插件的实现原理?"

    • 变体三:"深度分页性能差怎么优化?"

    记忆口诀

    逻辑分页查全量,物理分页加 LIMIT;PageHelper 拦 Executor,ThreadLocal 传参数,JSqlParser 改写 SQL;深度分页用游标,offset 大了性能崩。

    总结

    MyBatis 分页分为逻辑分页(RowBounds,内存截取,不能用)和物理分页(LIMIT,数据库层面分页)。生产环境使用 PageHelper 或 MyBatis-Plus 分页插件,底层原理是利用 MyBatis 的Interceptor拦截Executor.query(),通过 JSqlParser 改写 SQL 自动拼接LIMIT子句,并通过ThreadLocal保证分页参数只对第一条查询生效。

    加入小哈的星球,你将获得:专属的项目实战(4个项目) / 1v1 提问 / 简历修改 /Java 学习路线 /社群讨论 /学习打卡 / 每月赠书

    • 《仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍;演示地址:http://116.62.199.48:7070/

    • 《Spring AI 应用(RAG 智能客服)》已完结, 基于 Spring AI + Spring Boot 3.x + JDK 21

    • 《秒杀系统设计》正在更新中,单体到微服务高并发架构演进

    • 《前后端分离博客项目(全栈开发)》已完结,演示链接:http://116.62.199.48/

    • 项目阅读地址:https://quanxiaoha.com/column

    截止目前,累计输出 120w+ 字,讲解图 4013+ 张,还在持续爆肝中..戳我加入学习,解锁全部项目,已有4500+小伙伴加入

    1. 我的私密学习小圈子,从0到1手撸企业实战项目~ 2. 小红书面试:Redis 怎么实现延迟消息?我:啊?我一般都用 MQ 来实现... 3. 面试官:Nacos 如何实现服务注册与发现的? 4. SpringBoot + Disruptor 实现特快高并发处理,支撑每秒 600 万订单无压力!
    最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。 获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。
    PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。 点“在看”支持小哈呀,谢谢啦
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 3:16:59

Focus-dLLM:稀疏注意力优化大语言模型推理效率

1. 项目背景与核心价值在自然语言处理领域&#xff0c;大语言模型&#xff08;LLM&#xff09;的推理效率一直是制约其实际应用的关键瓶颈。传统注意力机制的计算复杂度随序列长度呈平方级增长&#xff0c;导致长文本处理时显存占用激增、推理延迟显著提升。Focus-dLLM正是针对…

作者头像 李华
网站建设 2026/5/1 3:14:00

HPH内部构造图解 三大核心模块

HPH身为一种精密传动部件&#xff0c;其内部构造巧妙地融合了力学与材料学的多重智慧结晶。深入理解HPH的构造&#xff0c;绝非仅仅知晓它的零件组成那般简单&#xff0c;更是要精准洞察其究竟是怎样达成高效、低损耗的动力传递这一关键过程。 下面我们将从用户最为关心的几个问…

作者头像 李华
网站建设 2026/5/1 3:11:28

智能体路由技术:从负载均衡到能力感知调度的演进

1. 智能体路由的技术演进与核心挑战在分布式AI系统架构中&#xff0c;智能体路由技术正经历从简单负载均衡到能力感知调度的范式转变。传统路由方案如ToolOrchestra采用强化学习训练协调器&#xff0c;虽然能实现基础的任务分配&#xff0c;但在处理异构模型池和多样化工具时面…

作者头像 李华
网站建设 2026/5/1 3:09:36

034、Agent的部署实战:将开发好的智能体发布为API

034、Agent的部署实战:将开发好的智能体发布为API 你的智能体在本地跑得飞快,但如何让全世界都能调用它?从Jupyter Notebook到可扩展的生产级API,只差一次正确的部署。 前言 在之前的三十三篇文章中,我们系统地学习了Agent智能体的开发全流程:从核心概念、环境搭建、Lan…

作者头像 李华
网站建设 2026/5/1 3:07:17

一天一个开源项目(第87篇):Tank-OS —— Red Hat 工程师用一个周末,把 AI Agent 塞进了一个可启动的 Linux 镜像

引言 “当 AI Agent 开始删除邮件、访问数据库、调用外部 API&#xff0c;你真的确定它不会越界吗&#xff1f;” 这是"一天一个开源项目"系列的第 87 篇文章。今天带你了解的项目是 Tank-OS&#xff0c;一个将 OpenClaw AI Agent 直接烧进操作系统镜像的开源工具。 …

作者头像 李华