MyBatis通过ResultHandler实现流式查询,需配置fetchSize、禁用一级缓存、关闭自动提交,并使用ExecutorType.SIMPLE手动管理SqlSession;自定义ResultHandler逐行处理数据,适用于海量导出等场景。

MyBatis 通过 ResultHandler 实现流式查询,核心是避免将全部结果集一次性加载进内存,适用于导出、同步、ETL 等海量数据场景。关键在于使用 SqlSession#select(String statement, Object parameter, ResultHandler handler) 方法,并配合合适的 Executor 类型和 JDBC 配置。
启用流式查询的必要配置
默认情况下 MyBatis 会缓存整个结果集,必须显式配置才能触发逐行流式处理:
-
JDBC 层需设置游标类型:在
<select>标签中添加fetchSize="Integer.MIN_VALUE"(MySQL)或fetchSize="1000"+resultSetType="FORWARD_ONLY"(PostgreSQL/Oracle),让 JDBC 驱动启用流式游标; -
禁用一级缓存:调用时使用
SqlSessionTemplate或手动创建SqlSession时指定ExecutorType.SIMPLE(避免REUSE或BATCH缓存干扰); - 关闭自动提交:流式查询期间保持连接打开,但不建议手动 commit/rollback,除非业务需要分段事务控制。
编写自定义 ResultHandler 处理单行数据
ResultHandler<T> 是函数式接口,只需实现 handleResult(ResultContext<T> context) 方法。每读取一行就回调一次,适合做实时转换、写入文件、发消息等操作:
ResultHandler<User> handler = new ResultHandler<>() {
@Override
public void handleResult(ResultContext<User> context) {
User user = context.getResultObject();
// ✅ 此处处理单条记录:写入 CSV、投递 MQ、聚合统计...
System.out.println("Processing: " + user.getId());
// ⚠️ 注意:context.getResultSet() 可获取原始 ResultSet(慎用,破坏封装性)
// ⚠️ 不要在此处调用 context.stop() 除非明确要提前终止(如查到某条件就退出)
}
};
结合 SqlSession 安全执行流式查询
推荐使用 try-with-resources 确保会话释放,且避免 Spring 管理的 SqlSession(可能被代理或复用):
立即学习“Java免费学习笔记(深入)”;
- 直接从
SqlSessionFactory获取非托管SqlSession; - 传入
ExecutorType.SIMPLE和autoCommit=false; - 查询结束后手动
close(),不要依赖 Spring 生命周期;
示例:
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE, false)) {
sqlSession.select("com.example.mapper.UserMapper.selectAll", null, handler);
} // 自动 close,底层 Connection 和 Statement 一并释放
注意事项与常见陷阱
流式查询不是银弹,需警惕以下问题:
-
数据库连接长期占用:查询耗时越长,连接空闲等待越久,需合理设置数据库连接池的
maxLifetime和idleTimeout; - 事务范围扩大:若开启事务,整个流式过程处于同一事务中,失败时回滚代价高,建议仅读操作且不开启事务;
-
MyBatis 日志干扰:DEBUG 级日志会尝试打印完整 List,导致 OOM,生产环境务必关闭
org.apache.ibatis的 DEBUG 日志; -
分页失效:
LIMIT/OFFSET或RowBounds与流式不兼容,应改用游标分页(如 last_id)或数据库原生游标支持。


















