MyBatis RowBounds原理

ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花

一、核心定位:原生逻辑分页(内存分页)

RowBounds是MyBatis内置的轻量分页工具,核心本质是逻辑分页(内存分页),而非数据库层面的物理分页。它不会改写SQL语句、不会生成LIMIT/OFFSET等数据库分页关键字,而是先执行全量SQL查询获取完整结果集,再在Java内存中对结果集做截取筛选,最终返回指定范围的分页数据。

这种分页方式无需修改Mapper XML、无需额外配置,仅通过传入RowBounds对象即可实现分页,具备零侵入、易使用的特点,但也存在明显的性能短板。

二、核心参数与基础用法

2.1 核心参数

RowBounds仅包含两个核心整型参数,精准控制分页范围:

  • offset:偏移量,代表从结果集的第几条记录开始截取(默认值为0,从第一条开始)
  • limit:限制数,代表每页最多返回的记录条数(默认值为Integer.MAX_VALUE,返回全部数据)

2.2 基础调用方式

在调用Mapper接口方法时,直接传入RowBounds对象即可,SQL语句无需做任何改动:

// 构建分页对象:偏移量0,每页10条
RowBounds rowBounds = new RowBounds(0, 10);
// 调用Mapper查询,自动实现分页
List<User> userList = userMapper.selectAllUser(rowBounds);

对应的Mapper XML仍为普通全量查询,无需添加分页条件:

<select id="selectAllUser" resultType="com.demo.entity.User">
    SELECT id,username,age FROM user
</select>

三、底层执行全流程

RowBounds的分页逻辑贯穿查询全链路,每一步都围绕「内存截取」展开,具体流程如下:

  1. 参数接收:MyBatis执行器接收到带有RowBounds参数的查询请求,解析offset和limit值
  2. 全量SQL执行:向数据库发送未改写的原始全量SQL,数据库返回符合条件的所有数据(ResultSet结果集)
  3. 结果集遍历筛选:MyBatis底层处理器遍历ResultSet,先跳过offset条前置数据,再读取最多limit条数据
  4. 封装返回:将筛选后的少量数据封装为List集合返回,丢弃剩余未选中数据

关键结论:无论查询第几页,数据库都会执行全表/全条件查询,分页筛选完全在Java应用内存中完成,这是RowBounds与物理分页的核心区别。

四、源码核心实现逻辑

RowBounds的分页筛选逻辑,主要在MyBatis的DefaultResultSetHandler(结果集处理器)中实现,核心源码片段解析如下:

// 核心方法:处理简单结果集的分页逻辑
private void handleRowValuesForSimpleResultMap(...) throws SQLException {
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    List<Object> values = new ArrayList<>();
    int rowNum = 0;
    // 遍历结果集
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
        // 跳过偏移量之前的数据
        if (rowNum < rowBounds.getOffset()) {
            rowNum++;
            continue;
        }
        // 读取限制条数内的数据
        if (values.size() < rowBounds.getLimit()) {
            Object rowValue = getRowValue(rsw, mappedStatement, resultHandler, resultContext, rowBounds);
            values.add(rowValue);
        }
        rowNum++;
    }
    // 返回筛选后的分页数据
    callResultHandler(resultHandler, values);
}

源码核心逻辑拆解:

  • 通过rowNum计数器遍历ResultSet,逐行比对偏移量
  • rowNum小于offset时,直接跳过当前行,不做数据封装
  • 达到offset后,开始封装数据,直到收集满limit条后停止遍历
  • 全程不修改SQL,仅对内存中的结果集做裁剪,无数据库分页交互

五、核心特性与局限性

5.1 核心优势

  • 零侵入:无需修改SQL、无需配置插件,兼容所有数据库
  • 轻量简洁:仅依赖MyBatis原生API,无第三方依赖
  • 适配简单场景:小数据量查询下,分页逻辑简洁高效

5.2 致命局限性

  • 性能极差:大数据量/大表查询时,数据库需返回全量数据,网络传输耗时、内存占用飙升,极易触发OOM
  • 资源浪费:数据库执行全量查询、应用加载全量结果集,CPU和内存资源无效消耗
  • 无总条数:默认不返回总记录数,无法实现常规分页的「总页数、页码」计算

六、适用场景与替代方案

6.1 适用场景

仅适用于数据量极小的场景(如字典表、配置表、缓存后数据),这类场景全量查询耗时可忽略,能发挥其轻量便捷的优势。

6.2 推荐替代方案

  • 物理分页:手写SQL LIMIT/OFFSET(MySQL)、ROWNUM(Oracle)
  • 分页插件:PageHelper、MyBatis-Plus分页插件,底层改写SQL实现数据库物理分页,性能远超RowBounds

ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花

MyBatis 文章被收录于专栏

本专栏聚焦Java主流持久层框架MyBatis,从基础搭建到源码原理,系统拆解核心组件、动态SQL、结果映射与缓存机制。助力开发者从入门到精通,掌握高效数据层开发技能,适配电商、金融等复杂业务场景。

全部评论

相关推荐

钱嘛数字而已:拖拉机被发明出来之后,就不需要农民了吗?农民还是需要的,但不需要这么多了,另外对农民的要求也变高了,需要会开拖拉机。
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务