源本科技 | 码上会

流式查询

2026/04/02
0
0

引言

MyBatis Plus 从 3.5.4 版本开始正式支持流式查询,其底层基于 MyBatis 原生的 ResultHandler 接口实现,属于 MyBatis 原生能力增强。

流式查询不会将结果集一次性加载到 JVM 内存,而是逐条从数据库读取并处理,专门用于大数据量跑批、数据同步、报表导出、全表处理等易引发内存溢出(OOM)的业务场景。

优势

  1. 避免内存溢出:不一次性加载全部数据,逐条处理,内存占用极低

  2. 原生支持:基于 MyBatis 底层实现,无额外依赖,性能稳定

  3. 无缝兼容:可直接使用 MP 条件构造器、分页、逻辑删除等原有能力

  4. 可控性强:支持中途停止查询、计数统计、自定义单条处理逻辑

前置要求

  1. MyBatis Plus 版本 ≥ 3.5.4

  2. Mapper 接口继承 BaseMapper<T>

  3. 数据库支持流式结果集(MySQL、Oracle、PostgreSQL 均支持)

支持的 BaseMapper 重载

MP 在 BaseMapper 中新增了支持 ResultHandler 的重载方法,可直接传入处理器实现流式处理:

  • selectList(Wrapper<T> queryWrapper, ResultHandler<T> resultHandler)

  • selectList(Page<T> page, Wrapper<T> queryWrapper, ResultHandler<T> resultHandler)

  • selectByMap(Map<String, Object> map, ResultHandler<T> resultHandler)

  • selectBatchIds(Collection<? extends Serializable> idList, ResultHandler<T> resultHandler)

  • selectMaps(Wrapper<T> queryWrapper, ResultHandler<Map<String, Object>> resultHandler)

  • selectObjs(Wrapper<T> queryWrapper, ResultHandler<Object> resultHandler)

核心 API

ResultHandler

结果处理器,用于定义每一条数据的处理逻辑,只需重写 handleResult 方法。

ResultContext

结果上下文对象,提供流式查询的核心操作方法:

方法

作用

getResultObject()

获取当前单条结果记录

getResultCount()

获取当前已处理的记录条数,从 1 开始计数

stop()

停止后续结果集处理,等价于循环 break

使用示例

示例 1

分页 + 流式查询(大数据跑批推荐)

适用于百万级数据分批处理,避免一次性加载全量数据导致 OOM。

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.Page;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;

// 分页参数:第 1 页,每页 100000 条
Page<H2User> page = new Page<>(1, 100000);
// 关闭 count 查询,大数据量下提升性能
page.setSearchCount(false);

baseMapper.selectList(page, Wrappers.lambdaQuery(), new ResultHandler<H2User>() {
    @Override
    public void handleResult(ResultContext<? extends H2User> resultContext) {
        // 1. 获取当前单条数据
        H2User user = resultContext.getResultObject();
        // 2. 获取当前处理条数
        long currentNum = resultContext.getResultCount();
        // 3. 执行业务处理(数据转换、同步、导出等)
        System.out.println("流式处理第 " + currentNum + " 条数据:" + user);

        // 4. 满足条件可手动停止流式查询
        if (currentNum >= 50000) {
            resultContext.stop();
        }
    }
});

示例 2

全表流式查询(无分页)

适用于全表数据扫描、数据初始化、数据校验等场景。

baseMapper.selectList(Wrappers.lambdaQuery(H2User.class).eq(H2User::getStatus, 1),
    new ResultHandler<H2User>() {
        @Override
        public void handleResult(ResultContext<? extends H2User> resultContext) {
            H2User user = resultContext.getResultObject();
            // 业务处理逻辑
            doBusiness(user);
        }
    });

示例 3

Map 类型流式查询

无需实体类,直接处理 Map 结构数据。

baseMapper.selectMaps(Wrappers.lambdaQuery(H2User.class),
    new ResultHandler<Map<String, Object>>() {
        @Override
        public void handleResult(ResultContext<? extends Map<String, Object>> resultContext) {
            Map<String, Object> map = resultContext.getResultObject();
            System.out.println(map);
        }
    });

最佳实践

  1. 大数据量处理:优先使用分页 + 流式查询,每页数据量控制在 1000~100000

  2. 禁止全表更新:流式查询仅用于读取数据,不建议在处理中执行更新 / 删除操作

  3. 分批提交:若需批量写入,可每处理 1000 条 集中执行一次批量操作

  4. 异常处理:在 handleResult 中捕获异常,避免单条数据异常导致整个流式任务中断

总结

MyBatis Plus 流式查询是大数据量处理的最优方案:

  • 底层基于 MyBatis 原生 ResultHandler3.5.4+ 版本支持

  • 逐条加载数据,彻底避免 OOM

  • 兼容条件构造器、分页、逻辑删除等 MP 核心能力

  • 支持手动计数、中途停止,可控性极强

  • 适用于数据跑批、全表处理、数据同步、报表导出等场景