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 等物理分页插件,你可以在保证代码简洁的同时,获得高性能、高稳定性的分页能力,为应用打下坚实的数据访问基础。


相关文章
|
3月前
|
SQL 缓存 NoSQL
深入骨髓!MyBatis二级缓存实战指南
本文全面解析MyBatis二级缓存的核心原理与实践应用。作为Mapper级别的缓存机制,二级缓存能有效降低数据库压力,提升查询性能。文章详细介绍了二级缓存的启用配置、工作流程、源码实现及事务一致性机制,并针对分布式环境提出了Redis集成方案。同时总结了适用场景与禁用场景,提供缓存策略选择建议,强调数据一致性的保障措施。最后给出最佳实践指南,包括缓存容量设置、性能优化技巧及常见问题解决方案,帮助开发者合理利用二级缓存实现性能优化。
456 7
|
缓存 Java 数据库连接
Mybatis一级缓存、二级缓存详讲
本文介绍了MyBatis中的查询缓存机制,包括一级缓存和二级缓存。一级缓存基于同一个SqlSession对象,重复查询相同数据时可直接从缓存中获取,减少数据库访问。执行`commit`操作会清空SqlSession缓存。二级缓存作用于同一namespace下的Mapper对象,支持数据共享,需手动开启并实现序列化接口。二级缓存通过将数据存储到硬盘文件中实现持久化,为优化性能,通常在关闭Session时批量写入缓存。文章还说明了缓存的使用场景及注意事项。
647 7
Mybatis一级缓存、二级缓存详讲
|
3月前
|
Java 应用服务中间件 Maven
Spring Boot开发环境搭建和项目启动
本节讲解JDK配置、Spring Boot工程构建与项目启动,涵盖IDEA和官方方式创建项目、Maven及编码设置,分析项目结构,并通过简单Controller验证启动成功,快速入门Spring Boot开发。
|
2月前
|
负载均衡 容灾 JavaScript
Nginx反向代理容灾备份(手把手教你搭建高可用Web服务)
本文介绍如何通过Nginx反向代理实现容灾备份与高可用架构。利用upstream模块配置主备服务器,结合健康检查与自动故障转移,确保主服务宕机时无缝切换至备用服务器。图文详解参数设置、配置步骤及测试方法,并提供Keepalived、HTTPS等进阶优化建议,助小白快速搭建稳定可靠的Web系统。
|
2月前
|
存储 人工智能 关系型数据库
告别数据库“膨胀”:Dify x SLS 构建高可用生产级 AI 架构
Dify作为热门低代码LLM平台,面临高负载下数据库性能瓶颈。通过将工作流日志从PostgreSQL迁移至阿里云SLS,实现存储解耦,显著降低DB压力与成本,提升扩展性,并利用SLS强大分析能力,将日志转化为业务洞察,助力Dify迈向生产级AI架构。
告别数据库“膨胀”:Dify x SLS 构建高可用生产级 AI 架构
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
论文精读:DeepSeek-R1是如何通过强化学习增强LLM推理能力的?
DeepSeek-R1通过纯强化学习与冷启动结合,实现强大推理能力,并利用蒸馏技术将“智慧”传递给小模型,推动大模型训练新范式。
231 0
客户端模式
该模式已超出OAuth2范畴,A服务以自身身份向B服务申请Token,无需用户参与。适用于A服务独立使用B服务资源的场景,完全为服务间内部交互,与用户权限无关。
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
2913 1
|
存储 算法 安全
ConcurrentHashMap的底层实现与深度分析
【11月更文挑战第15天】在Java并发编程中,ConcurrentHashMap是一个非常重要的数据结构,它提供了一种线程安全的哈希表实现。随着Java版本的迭代,ConcurrentHashMap的实现也在不断优化,以更好地支持高并发场景。
314 4
|
消息中间件 Java 调度
消息队列 MQ使用问题之消费者自动掉线是什么导致的
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。

热门文章

最新文章