MyBatis 分页机制详解:从 RowBounds 到物理分页实践

简介: MyBatis分页策略解析:逻辑分页(RowBounds)将全量数据加载至内存,仅适用于小数据量;物理分页通过SQL层面限制返回数据,性能更优。推荐使用PageHelper插件,自动适配数据库方言,一行代码实现高效分页,避免OOM风险,提升系统稳定性。

在 Web 开发中,分页是处理大量数据展示的必备功能。MyBatis 本身不强制提供分页实现,但支持多种分页策略,主要分为 逻辑分页(内存分页)物理分页(数据库分页)。理解它们的原理与适用场景,对系统性能和稳定性至关重要。


一、逻辑分页:MyBatis 自带的 RowBounds

RowBounds 是 MyBatis 提供的内置分页工具,属于逻辑分页——即先查询出全部结果集,再在 Java 内存中截取指定范围的数据。

使用示例:
// Mapper 接口(无需特殊定义)
List<User> selectAllUsers();
// Service 层调用
int offset = (pageNum - 1) * pageSize; // 起始行
int limit = pageSize;                  // 获取条数
RowBounds rowBounds = new RowBounds(offset, limit);
List<User> users = userMapper.selectAllUsers(rowBounds);
底层原理:
  • 执行原始 SQL(如 SELECT * FROM user),获取完整 ResultSet
  • MyBatis 遍历结果集,跳过 offset 条,再取 limit 条;
  • 未生成带 LIMIT 的 SQL,数据库仍返回全部数据。

⚠️ 缺点

  • 内存开销大:若表有 100 万条数据,即使只取第 1 页,也会加载全部到内存;
  • 性能差:大数据量下响应慢,甚至引发 OutOfMemoryError
  • 网络传输浪费:大量无用数据在网络中传输。

适用场景

仅适用于数据量极小(如配置表、字典表)且分页需求简单的场景。


二、物理分页:真正的高效分页

物理分页通过在 SQL 层面添加分页语句(如 LIMITROWNUM),让数据库只返回所需数据,大幅减少 I/O、内存和网络开销。

常见的物理分页实现方式包括:

1. 手写 SQL 分页

直接在 Mapper XML 中编写带分页关键字的 SQL(需适配不同数据库):

<!-- MySQL -->
<select id="selectUsersByPage" resultType="User">
  SELECT * FROM user
  LIMIT #{offset}, #{limit}
</select>
<!-- Oracle -->
<select id="selectUsersByPage" resultType="User">
  SELECT * FROM (
    SELECT ROWNUM rn, t.* FROM (
      SELECT * FROM user ORDER BY id
    ) t WHERE ROWNUM &lt;= #{endRow}
  ) WHERE rn > #{startRow}
</select>

✅ 优点:完全可控;

❌ 缺点:需为不同数据库维护不同 SQL,维护成本高。


2. 第三方插件:PageHelper(推荐)

PageHelper 是最流行的 MyBatis 分页插件,自动识别数据库类型并重写 SQL。

使用步骤:
  1. 引入依赖(Maven):
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper-spring-boot-starter</artifactId>
  <version>1.4.6</version>
</dependency>
  1. 在查询前开启分页:
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAllUsers(); // 原始查询方法,无需改写
  1. 获取分页信息(可选):
PageInfo<User> pageInfo = new PageInfo<>(users);
long total = pageInfo.getTotal(); // 总记录数
插件原理:
  • 拦截 SQL 执行;
  • 自动拼接分页语句(MySQL → LIMIT,Oracle → ROWNUM,SQL Server → OFFSET FETCH 等);
  • 额外执行一条 COUNT(*) 查询总条数

优势

  • 一行代码实现分页;
  • 自动适配主流数据库;
  • 返回完整分页元数据(总页数、是否首页等)。

3. 数组分页(伪物理分页)

在 DAO 层查出全部数据,Service 层用 List.subList() 截取:

public List<Student> queryStudentsByArray(int currPage, int pageSize) {
    List<Student> all = studentMapper.queryAll();
    int start = (currPage - 1) * pageSize;
    int end = Math.min(start + pageSize, all.size());
    return all.subList(start, end);
}

⚠️ 本质仍是逻辑分页!只是手动实现了 RowBounds 的功能,同样存在内存溢出风险,不推荐用于生产环境。


4. 自定义拦截器分页

通过实现 MyBatis Interceptor 接口,拦截特定命名的查询(如 *ByPage),动态拼接 LIMIT

✅ 适合有统一规范的团队;

❌ 开发成本较高,需处理 SQL 解析、方言适配等细节。


三、逻辑分页 vs 物理分页:对比总结

对比项 逻辑分页(RowBounds) 物理分页(PageHelper / 手写 SQL)
数据加载 全量加载到内存 数据库只返回当前页
内存占用 高(随总数据量增长) 低(仅当前页)
网络传输 大量无用数据 仅有效数据
性能(大数据) 极差,可能 OOM 优秀
数据库压力 高(全表扫描) 低(利用索引+分页)
适用场景 小表、配置类数据 所有业务主表

📌 核心原则

物理分页总是优先于逻辑分页。除非数据量极小(< 1000 条),否则应避免使用 RowBoundssubList 分页。


通过合理选择分页策略,尤其是采用 PageHelper 等物理分页插件,你可以在保证代码简洁的同时,获得高性能、高稳定性的分页能力,为应用打下坚实的数据访问基础。


相关文章
|
2月前
|
SQL 缓存 NoSQL
深入骨髓!MyBatis二级缓存实战指南
本文全面解析MyBatis二级缓存的核心原理与实践应用。作为Mapper级别的缓存机制,二级缓存能有效降低数据库压力,提升查询性能。文章详细介绍了二级缓存的启用配置、工作流程、源码实现及事务一致性机制,并针对分布式环境提出了Redis集成方案。同时总结了适用场景与禁用场景,提供缓存策略选择建议,强调数据一致性的保障措施。最后给出最佳实践指南,包括缓存容量设置、性能优化技巧及常见问题解决方案,帮助开发者合理利用二级缓存实现性能优化。
272 7
|
安全 算法 API
支付宝支付加密规则梳理,写的太好了!
前言 支付是一个安全等级很高的场景,系统间交互的每一条数据的泄露都有可能造成及其大的损失。因此支付时系统间交互的每一
支付宝支付加密规则梳理,写的太好了!
lyL
|
2月前
|
数据采集 数据建模 领域建模
领域模型图(数据架构/ER图)
本文介绍如何通过四色原型法构建领域模型,并逐步推导出数据架构中的ER图。以风控系统为例,运用时标性(MI)、参与方-地点-物品(PPT)、角色(Role)和描述(DESC)四类原型,从关键流程出发,提炼实体与关系,最终形成简洁清晰的ER图,助力数据建模。
lyL
103 1
领域模型图(数据架构/ER图)
|
2月前
|
Java 应用服务中间件 Maven
Spring Boot开发环境搭建和项目启动
本节讲解JDK配置、Spring Boot工程构建与项目启动,涵盖IDEA和官方方式创建项目、Maven及编码设置,分析项目结构,并通过简单Controller验证启动成功,快速入门Spring Boot开发。
|
2月前
|
存储 NoSQL 安全
Redis:内存陡增100%深度复盘
缓冲区用于暂存数据,防止处理速度跟不上发送速度导致丢数据。Redis通过输入/输出缓冲区管理命令与响应,但输出缓冲区在Pub/Sub模式下可能剧增,理论最大占用达9.375GB,远超实例内存(如2GB),导致SET/GET失败,系统无法正常工作。
|
2月前
|
缓存 运维 监控
一场FullGC故障排查
本文记录了一次线上CPU使用率飙升至104%的问题排查过程。通过分析发现,问题根源为JVM频繁Full GC,而机器内存监控未明显异常,易造成误判。进一步使用JProfiler分析堆内存快照,定位到大对象(List&lt;Map&gt;)占用近900MB空间,导致老年代被打满。该对象源于将Excel数据以低效结构加载至内存且长时间驻留。解决方案包括“治本”——将数据移出JVM内存存入Redis,或“治标”——请求后及时清理冗余字段。最终总结了从监控识别、工具分析到代码定位的完整排查思路,强调应关注JVM层面指标,并合理设计内存使用结构。
一场FullGC故障排查
|
2月前
|
安全 Java Spring
DelegatingFilterProxy
本文解析Spring Security核心过滤器DelegatingFilterProxy源码,重点说明其如何通过web.xml配置的springSecurityFilterChain名称,动态获取FilterChainProxy并执行过滤链,揭示Spring安全过滤机制的底层实现原理。
DelegatingFilterProxy
客户端模式
该模式已超出OAuth2范畴,A服务以自身身份向B服务申请Token,无需用户参与。适用于A服务独立使用B服务资源的场景,完全为服务间内部交互,与用户权限无关。
|
2月前
|
安全 Java Spring
SecurityFilterChain
本文解析了Spring Security中的核心组件SecurityFilterChain接口及其唯一实现DefaultSecurityFilterChain,揭示其作为web.xml中过滤器链的本质。通过源码分析,阐明请求匹配与过滤器列表的管理机制,帮助理解框架底层运作原理,为自定义认证页面奠定基础。
|
2月前
|
安全 Java Spring
FilterChainProxy
本文通过源码分析Spring Security中的`FilterChainProxy`,揭示其如何整合多个过滤器。结合调试截图与代码注释,逐步解析请求处理流程:从`doFilter`入口到`SecurityFilterChain`匹配,最终将十五个过滤器封装执行,验证了过滤器链的构建与调用机制。