MyBatis 流式查询详解:ResultHandler 与 Cursor

在业务中,如果一次性查询出百万级数据并返回 List,很容易造成 OOM长时间 GC
MyBatis 提供了 流式查询(Streaming Query) 能力,让我们可以边读边处理,极大降低内存压力。


1. 什么是流式查询?

普通查询:一次性将全部结果加载到内存,然后再处理。
流式查询:数据库返回一个游标(Cursor),应用端一批一批地从游标读取数据,边读边处理,避免占用大量内存。

适用场景

  • 导出大批量数据(CSV、Excel)
  • 批量处理(数据同步、数据迁移)
  • 实时计算

2. MyBatis 流式查询的两种实现方式

2.1 使用 ResultHandler

ResultHandler 是 MyBatis 提供的经典方式,查询结果不会一次性放到内存,而是每读取一条就调用一次回调方法。

不带参数示例
@Mapper
public interface UserMapper {@Select("SELECT id, name, age FROM user")void scanAllUsers(ResultHandler<User> handler);
}

调用:

@Autowired
private UserMapper userMapper;public void processUsersNoParam() {userMapper.scanAllUsers(ctx -> {User user = ctx.getResultObject();System.out.println(user);});
}
带参数示例
@Mapper
public interface UserMapper {@Select("SELECT id, name, age FROM user WHERE age > #{age}")void scanUsersByAge(@Param("age") int age, ResultHandler<User> handler);
}

调用:

public void processUsersWithParam(int minAge) {userMapper.scanUsersByAge(minAge, ctx -> {User user = ctx.getResultObject();System.out.println(user);});
}

特点

  • 边查边处理,不占用过多内存
  • 处理逻辑和查询绑定在一起
  • 适合流式消费(文件写入、推送消息)
  • 如果收集成 List,内存压力和普通查询差不多

2.2 使用 Cursor(推荐 MyBatis 3.4+)

Cursor 提供了更接近 JDBC ResultSet 的方式,支持 Iterable 迭代。

不带参数示例
@Mapper
public interface UserMapper {@Select("SELECT id, name, age FROM user")@Options(fetchSize = Integer.MIN_VALUE) // MySQL 开启流式Cursor<User> scanAllUsers();
}

调用:

@Transactional
@Transactional
public void getUsersAsList() throws IOException {try (Cursor<User> cursor = userMapper.scanAllUsers()) {for (User user : cursor) {System.out.println(user);}}
}
带参数示例
@Mapper
public interface UserMapper {@Select("SELECT id, name, age FROM user WHERE age > #{age}")@Options(fetchSize = Integer.MIN_VALUE)Cursor<User> scanUsersByAge(@Param("age") int age);
}

调用:

@Transactional
@Transactional
public void getUsersByAge(int minAge) throws IOException {try (Cursor<User> cursor = userMapper.scanUsersByAge(minAge)) {for (User user : cursor) {System.out.println(user);}}
}

3. Cursor 踩坑:A Cursor is already closed

很多人在用 Cursor 时会遇到:

A Cursor is already closed.

原因

  • Cursor 是延迟加载的,必须在 同一个 SqlSession 存活期间 迭代
  • 如果你在 mapper 方法中返回 Cursor,却在外部再去遍历,此时 SqlSession 已经被 MyBatis 关闭,Cursor 自然不可用

错误示例

Cursor<User> cursor = userMapper.scanAllUsers(); // 此时 SQLSession 会在方法返回后关闭
for (User user : cursor) { // 这里会报错...
}

解决办法

  1. 在同一个方法中迭代,不要把 Cursor 返回到方法外
  2. 加 @Transactional 保证 SqlSession 在方法执行期间不关闭
  3. 用 try-with-resources 及时关闭 Cursor

正确示例

@Transactional
public void processCursor() {try (Cursor<User> cursor = userMapper.scanAllUsers()) {for (User user : cursor) {// 处理数据}} catch (IOException e) {throw new RuntimeException(e);}
}

4. 注意事项

  1. MySQL 必须设置 @Options(fetchSize = Integer.MIN_VALUE) 才能真正流式
  2. 事务控制:Cursor 必须在事务或 SqlSession 存活期间消费
  3. 大事务风险:流式处理可能导致事务时间长,要权衡
  4. 网络延迟:流式每次批量取数,可能比一次性查询多几毫秒,但内存安全
  5. 收集成 List 慎用:这样会失去流式查询的内存优势

5. 区别

ResultHandler(回调模式):

  • 基于观察者模式/回调模式
  • MyBatis 主动推送数据给你的处理器
  • 你提供一个处理函数,MyBatis 逐条调用

Cursor(迭代器模式):

  • 基于迭代器模式
  • 你主动从 Cursor 中拉取数据
  • 更符合 Java 集合框架的使用习惯

ResultHandler 更适合:

  • 简单的逐条处理场景
  • 不需要复杂控制流程的情况
  • 希望 MyBatis 完全管理资源的场景

Cursor 更适合:

  • 需要复杂处理逻辑的场景
  • 需要灵活控制处理流程
  • 习惯使用 Java 8 Stream API 的开发者
  • 需要与现有迭代处理代码集成

选择 ResultHandler 当:

  • 处理逻辑简单直接
  • 不需要复杂的流程控制
  • 希望代码更紧凑
  • 不希望手动管理资源

选择 Cursor 当:

  • 需要灵活的流程控制
  • 处理逻辑复杂,需要分步骤
  • 团队熟悉迭代器模式
  • 需要与其他基于迭代器的代码集成
  • 希望有更好的异常处理控制

6. 总结

  • ResultHandler:更灵活,回调式消费,适合不需要一次性得到全部结果

  • Cursor:可迭代,语法直观,但必须在 SqlSession 存活期间消费,否则就会遇到 A Cursor is already closed

  • 带参数查询:ResultHandler 和 Cursor 都支持,只需在 mapper 方法加参数

  • 实战建议

    • 大批量导出、批量同步 → Cursor
    • 条件过滤、部分收集 → ResultHandler
    • 不需要流式直接用普通 List 查询即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/92598.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/92598.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/92598.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1Panel Agent 证书绕过实现远程命令执行漏洞复现(CVE-2025-54424)

免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 前…

kettle插件-kettle http post plus插件,轻松解决https post接口无法调用文件流下载问题

场景&#xff1a;小伙伴在使用kettle调用https post接口过程中无法正常调用&#xff0c;程序出错问题&#xff0c;今天演示下用自研插件轻松解决这个问题。1、使用openssl 生成自签名证书openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 3652、…

剑指offer第2版——面试题2:实现单例

文章目录一、题目二、考察点三、答案3.1 C11写法3.2 C98写法&#xff08;线程安全只存在于懒汉模式&#xff09;3.2.1 小菜写法3.2.2 小菜进阶写法3.2.3 中登写法3.2.3 老鸟写法四、扩展知识4.1 饿汉模式和懒汉模式的区别4.1.1 饿汉模式&#xff08;Eager Initialization&#…

OpenAI开源大模型gpt-oss系列深度解析:从120B生产级到20B桌面级应用指南

引言&#xff1a;OpenAI开源里程碑&#xff0c;AI民主化加速到来 2025年8月&#xff0c;OpenAI正式宣布开源其两款重磅大语言模型——gpt-oss-120b&#xff08;1200亿参数生产级模型&#xff09;和gpt-oss-20b&#xff08;200亿参数桌面级模型&#xff09;&#xff0c;引发全球…

本地部署文档管理平台 BookStack 并实现外部访问( Windows 版本)

BookStack 是一款专注于书籍、文档管理的开源平台&#xff0c;它界面设计直观简洁&#xff0c;功能强大且易于使用&#xff0c;允许用户创建、组织和分享文档资料&#xff0c;特别适合用于构建内部文档系统、知识库或公开的文档站点。本文将详细介绍如何在 Windows 系统本地部署…

VS Code编辑器

实际上&#xff0c;‌Visual Studio Code&#xff08;简称VS Code&#xff09;‌是由微软开发的免费、开源、跨平台的代码编辑器&#xff0c;支持多种编程语言和框架&#xff0c;广泛应用于现代Web和云应用开发。这也是个编辑器&#xff0c;可能是继 GitHub 的 Atom 之后的一枝…

自动化测试篇--BUG篇

目录 一.软件测试的生命周期 二.bug是什么&#xff1f; 三.如何描述一个bug&#xff1f; 四.bug的级别 五.bug的生命周期 六.测试与开发产生争执怎么办&#xff1f;&#xff08;重要&#xff01;&#xff01;&#xff01;&#xff09; 一.软件测试的生命周期 软件测试人员…

Solidity智能合约基础

基础学习使用 remix&#xff1a;ide Remix - Ethereum IDE evm&#xff1a;ethreum virtual machine evm字节码 强类型脚本语言 compile >evm bytescode >evm hello的样例 声明的关键字&#xff1a;contract // SPDX-License-Identifier: MIT pragma solidi…

Unity跨平台超低延迟的RTSP/RTMP播放器技术解析与实战应用

✳️ 引言&#xff1a;为什么说 Unity 中的视频能力是“可视化神经元”&#xff1f; 随着“可视化 实时性”成为工业数字化的关键支撑&#xff0c;Unity 正从传统游戏引擎&#xff0c;演进为数字孪生系统、智能机器人中控、虚拟交互平台、XR 可视引擎等领域的底层核心。它不再…

python学智能算法(三十三)|SVM-构建软边界拉格朗日方程

【1】引用 在前序学习进程中&#xff0c;我们初步了解了SVM软边界&#xff0c;今天就更进一步&#xff0c;尝试构建SVM软边界的拉格朗日函数。 【2】基本问题 在SVM软边界中&#xff0c;我们已经获得此时的最优化几何距离的表达式&#xff1a; fmin⁡12∣∣w∣∣2C∑i1nξif…

【YOLOv5】

Focus模块&#xff1a;早期再yolov5版本提出&#xff0c;后期被常规卷积替换&#xff0c;作用是图像进入主干网络之前&#xff0c;进行隔行隔列采样&#xff0c;把空间维度堆叠到通道上&#xff0c;减少计算量。 SPPF:SPP的改进版本&#xff0c;把SPP的不同池化核改变为K 5 的…

Pytest项目_day05(requests加入headers)

headers 由于每个请求都需要加入一些固定的参数&#xff0c;例如&#xff1a;cookies、user-agent&#xff0c;那么将这些固定参数放入URL或params中会显得很臃肿&#xff0c;因此一般将这些参数放在request headers中headers的反爬作用 在豆瓣网站中&#xff0c;如果我们不加入…

安全引导功能及ATF的启动过程(四)

安全引导功能及ATF的启动过程&#xff08;四&#xff09; ATF中bl31的启动 在bl2中触发安全监控模式调用后会跳转到bl31中执行&#xff0c;bl31最主要的作用是建立EL3运行态的软件配置&#xff0c;在该阶段会完成各种类型的安全监控模式调用ID的注册和对应的ARM核状态的切换&am…

从手工到智能决策,ERP让制造外贸企业告别“数据孤岛“降本增效

在全球化竞争加剧的当下&#xff0c;制造型外贸企业正面临订单碎片化、供应链复杂化、合规风险上升等多重挑战。数字化转型已成为企业突破增长瓶颈、构建核心竞争力的必选项。然而&#xff0c;许多企业在推进过程中因选型不当陷入“系统孤岛”“数据失真”“流程低效”等困境。…

DMETL简单介绍、安装部署和入门尝试

一、DMETL的介绍1.1 概述我们先来简单了解一下DMETL。DMETL是什么&#xff1f;说的简单一点&#xff0c;DMETL一款数据处理与集成平台&#xff1b;从功能来说&#xff0c;那DMETL就是对数据同步、数据处理以及数据交换共享提供一站式支持的平台&#xff1b;从它的意义来说&…

NLP 人工智能 Seq2Seq、K-means应用实践

基于Java和人工智能的Web应用 以下是基于Java和人工智能的Web应用实例,涵盖自然语言处理、计算机视觉、数据分析等领域。这些案例结合了沈七星AI或其他开源框架(如TensorFlow、Deeplearning4j)的实现思路,供开发参考: 自然语言处理(NLP) 1. 智能客服系统 使用Java的Op…

Docker 从入门到实战(一):全面解析容器化革命 | 2025 终极指南

2025 年,全球容器市场规模突破 200 亿美元,超过 80% 的企业生产环境运行在容器之上。掌握 Docker 已成为开发、运维乃至架构师的核心竞争力。本文带你彻底搞懂 Docker 的底层逻辑与核心价值! 一、Docker 是什么?为什么它能改变世界? 想象一下:你开发时运行完美的 Pytho…

Lazada东南亚矩阵营销破局:指纹手机如何以“批量智控+数据中枢”重构运营生态

在Lazada以“超级APP”战略渗透东南亚6国市场的进程中&#xff0c;商家正陷入一个结构性矛盾&#xff1a;如何用有限人力高效管理10个国家账号&#xff0c;却不被数据孤岛拖垮营销效率&#xff0c;更不因账号关联风险引发平台封禁&#xff1f;传统多账号运营依赖“人手一台设备…

操作系统: 线程(Thread)

目录 什么是线程&#xff08;Thread&#xff09;&#xff1f; 线程与进程之间的关系 线程调度与并发执行 并发&#xff08;Concurrency&#xff09;与并行&#xff08;Parallelism&#xff09; 多线程编程的四大核心优势&#xff08;benefits of multithreaded programmin…

Uber的MySQL实践(一)——学习笔记

MySQL 是Uber数据基础设施的核心支柱&#xff0c;支撑着平台上大量关键操作。Uber 拥有一套庞大的 MySQL 集群&#xff0c;如何构建一个控制平面来管理如此大规模的 MySQL 集群&#xff0c;并同时确保零宕机、零数据丢失是一个十分有挑战性的问题。下面重点介绍 Uber 的 MySQL …