mybatis 的 resulthandler 是解决海量数据查询 oom 的关键机制,通过逐行回调实现流式读取,需配合 select() 方法、jdbc 流式配置、手动关闭 sqlsession 及禁用缓存才能生效。

MyBatis 的 ResultHandler 是解决海量数据查询时内存溢出(OOM)的关键机制,它绕过默认的“全量加载到 List”模式,改为逐行回调处理,实现真正的流式读取。
ResultHandler 的核心作用
MyBatis 默认执行 selectList() 会把所有结果一次性映射为 Java 对象并存入内存。当数据量达百万级,极易触发 OOM。而 ResultHandler 让你注册一个回调接口,在每一条记录从 JDBC ResultSet 中解析完成后立即被调用,对象用完即弃,不累积在内存中。
正确启用流式读取的必要条件
- 必须使用 SqlSession 的 select() 方法(带 ResultHandler 参数),不能用 Mapper 接口的返回 List 的方法 —— 后者底层仍是全量加载
-
JDBC 连接需配置流式游标:MySQL 需加参数
&useCursorFetch=true&defaultFetchSize=100;PostgreSQL 需设置fetchSize > 0(如setFetchSize(100)),否则驱动仍可能缓存整结果集 -
SqlSession 必须手动关闭:流式读取依赖底层 ResultSet 保持打开状态,用完务必调用
sqlSession.close(),否则连接泄漏 -
禁用一级/二级缓存:流式场景下缓存无意义且可能干扰,建议在 statement 上显式设置
flushCache="true"或使用sqlSession.clearCache()
典型使用示例(含异常与资源管理)
以下是一个安全、可落地的写法:
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 注意:此处不能调用 mapper.selectAll()!
mapper.selectAll(new ResultHandler<user>() {
@Override
public void handleResult(ResultContext<user> context) {
User user = context.getResultObject();
// ✅ 在这里做实时处理:写文件、发消息、批量入库、统计等
processUser(user);
// ❌ 不要存到 List 或 Map 里累积!
}
});
} // 自动 close,释放 ResultSet 和连接
</user></user>
若用 Lambda(MyBatis 3.4+)可更简洁:
sqlSession.select("com.example.UserMapper.selectAll",
resultContext -> processUser(resultContext.getResultObject()));
进阶注意事项
- 流式读取期间不能复用同一 SqlSession 执行其他查询,ResultSet 未关闭前会阻塞连接
- 若需分批提交(如每 1000 条 insert 到另一库),可在 handler 内计数 + 手动
sqlSession.flushStatements(),但注意事务隔离性 - MyBatis-Plus 的
selectBatchByIds等方法不支持 ResultHandler,需退回原生 Mapper XML 或注解方式 - Spring 中若用
@Mapper接口,需定义一个 void 方法签名并传入 ResultHandler 参数,Spring 会自动注入代理
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











