1. 从一次线上 OOM 说起Mybatis Cursor 到底解决什么问题如果你在项目里写过SELECT * FROM log这种没有分页的查询并且表里数据量到了百万级大概率遇到过java.lang.OutOfMemoryError: Java heap space。这不是 JVM 参数调大一点就能根治的问题因为 Mybatis 默认的查询行为是一次性把 ResultSet 里的所有行映射成 Java 对象全部塞进List再返回。数据量一大堆内存直接被撑爆。Mybatis 提供了一个叫Cursor的返回类型它的官方注释写得很直白适合处理通常不适合放进内存的数百万项查询。核心机制是惰性获取——它不会一次性把全部结果读进内存而是持有一个数据库游标你迭代一次它才从 ResultSet 里取一行或一批。这样内存占用从「全量数据」降到「单行 游标开销」OOM 风险自然大幅下降。但这里有个容易被忽略的坑Cursor 必须配合事务或手动管理 SqlSession 生命周期。在 SpringBoot 里SqlSession 默认只在 Mapper 方法调用期间存活方法一返回SqlSession 关闭绑定的 Cursor 也跟着失效你会拿到Cursor is closed或者直接抛异常。所以「用 Cursor 避免 OOM」这件事一半是写法问题一半是生命周期管理问题。这篇内容聚焦三件事Cursor 的正确写法与事务边界、结合 TaoToken 统一 Key/API 通道的配置骨架config.toml / settings.json 可复制片段、以及一套可执行的验证动作帮你在真实项目里确认内存表现而不是「看起来没报错」就完事。2. TaoToken 前置统一 Key 与 API 通道的配置骨架在讲 Cursor 配置之前先说明为什么这里会引入 TaoToken。很多团队在本地开发、CI、以及多个 AI 编码工具之间切换时Key 和 API 地址散落在各处改一个环境要动好几个文件。TaoToken 的作用是提供一个统一的 API 通道和 Key 管理入口让模型对话、编码计划、控制台、API Keys 这些能力走同一套地址减少配置漂移。官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基地址不带 UTMhttps://taotoken.net/api你需要提前准备好的几个 deep link后面配置和验证会用到模型对话https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewriteCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteClaudeCodeAnthropichttps://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite注意TaoToken 在这里的角色是统一 Key/API 通道不是数据库连接池也不替代 Mybatis 本身。Cursor 的内存控制仍然由 Mybatis JDBC 驱动负责TaoToken 负责的是你调用模型能力时的通道一致性。配置骨架分两份文件一份给命令行/服务端工具用的config.toml一份给编辑器类工具用的settings.json。两份都只放通道和 Key 引用不硬编码明文 Key。2.1 config.toml 片段# ~/.taotoken/config.toml # 统一 API 通道配置Key 从环境变量读取避免明文入库 [api] base_url https://taotoken.net/api timeout_seconds 60 max_retries 3 [auth] # 不要把真实 Key 写进文件用环境变量注入 api_key_env TAOTOKEN_API_KEY [models] default claude-sonnet fallback gpt-4o-mini [logging] level info # 记录请求耗时便于排查通道问题 log_latency true2.2 settings.json 片段{ taotoken: { baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, timeout: 60000, retry: { maxAttempts: 3, backoffMs: 500 }, features: { modelChat: true, codingPlan: true } } }环境变量注入方式Linux/macOSexport TAOTOKEN_API_KEY你的Key # 验证是否生效 echo $TAOTOKEN_API_KEY | head -c 8Windows PowerShell$env:TAOTOKEN_API_KEY 你的Key Write-Output $env:TAOTOKEN_API_KEY.Substring(0,8)Key 的获取入口在 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite3. 可复制配置Mybatis Cursor 写法 事务边界 批量参数这一节是全文技术核心。Cursor 能不能真正避免 OOM取决于四个点Mapper 返回值、事务注解、fetchSize、以及迭代时的消费方式。3.1 Mapper 返回值改成 Cursorimport org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.annotations.Select; public interface LogMapper { Select(SELECT id, level, message, created_at FROM log ORDER BY id) CursorLog streamAll(); }关键点返回值必须是CursorT不能是ListT。Mybatis 在MethodSignature解析时会判断returnsCursor Cursor.class.equals(this.returnType)只有命中这个分支才会走executeForCursor最终调用doQueryCursor而不是doQuery。3.2 事务边界两种方式二选一方式一在调用方法上加Transactionalimport org.springframework.transaction.annotation.Transactional; Service public class LogStreamService { private final LogMapper logMapper; public LogStreamService(LogMapper logMapper) { this.logMapper logMapper; } Transactional public void processAll() { try (CursorLog cursor logMapper.streamAll()) { cursor.forEach(log - { // 逐行处理内存只保留当前行 handle(log); }); } catch (IOException e) { throw new RuntimeException(cursor 关闭异常, e); } } private void handle(Log log) { // 业务处理 } }方式二手动创建 SqlSessiontry (SqlSession session sqlSessionFactory.openSession()) { LogMapper mapper session.getMapper(LogMapper.class); try (CursorLog cursor mapper.streamAll()) { IteratorLog it cursor.iterator(); while (it.hasNext()) { handle(it.next()); } } }注意Cursor实现了Closeable务必用 try-with-resources 或显式 close否则游标泄漏会拖垮数据库连接。3.3 fetchSize控制每次从数据库取多少行Cursor 的惰性获取依赖 JDBC 的 fetchSize。默认值在不同驱动下不一样MySQL 默认是「全量拉取」这会让 Cursor 失去意义。必须显式设置Select(SELECT id, level, message, created_at FROM log ORDER BY id) Options(fetchSize 1000) CursorLog streamAll();或者在 XML 里select idstreamAll resultTypecom.example.Log fetchSize1000 SELECT id, level, message, created_at FROM log ORDER BY id /selectMySQL 要真正启用流式连接串需要加useCursorFetchtruespring.datasource.urljdbc:mysql://localhost:3306/demo?useCursorFetchtruedefaultFetchSize1000PostgreSQL 则需要在自动提交关闭的前提下才能流式所以事务注解不能省。3.4 参数对照表参数作用推荐值不设置的后果fetchSize每次从 ResultSet 取的行数500–2000MySQL 默认全量拉取Cursor 失效useCursorFetchMySQL 启用游标读取true流式不生效Transactional延长 SqlSession 生命周期必须Cursor is closedtry-with-resources确保游标关闭必须连接泄漏resultOrdered嵌套 resultMap 时保证顺序true如需要嵌套映射错乱4. 验证请求与成功结果确认内存真的降下来了配置写完不代表生效。你需要一套可执行的验证动作确认三件事Cursor 是否真的流式、内存是否真的平稳、通道是否真的通。4.1 验证 Cursor 是否流式在handle方法里打印当前行号和线程内存private void handle(Log log) { long used Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); System.out.println(row log.getId() usedMB (used / 1024 / 1024)); }如果内存随行数增长而基本平稳在几十 MB 内波动说明流式生效。如果 usedMB 一路飙升到几百 MB 甚至 OOM说明 fetchSize 没生效或者返回类型被解析成了 List。4.2 验证事务边界故意去掉Transactional你会看到org.apache.ibatis.exceptions.PersistenceException: Error attempting to get column ... Cursor is closed看到这个报错反过来证明事务边界是 Cursor 存活的关键。加上注解后报错消失说明生命周期管理正确。4.3 验证 TaoToken 通道用 curl 验证通道连通性curl -s -o /dev/null -w %{http_code}\n \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ https://taotoken.net/api/models返回 200 说明 Key 和通道正常。返回 401 检查 Key返回 404 检查 base_url 是否多了斜杠。4.4 成功结果长什么样一次正常的验证输出应该类似row1 usedMB45 row1000 usedMB47 row100000 usedMB48 row1000000 usedMB49 cursor closed, total rows1000000内存从 45MB 到 49MB百万行数据只涨了 4MB这就是 Cursor 该有的表现。对比普通List查询同样数据量通常会直接 OOM 或占用 1GB 以上堆内存。5. 本篇常见错排查5.1 Cursor is closed最常见。原因是没有事务或 SqlSession 提前关闭。解决加Transactional或手动 openSession 并保证在 Cursor 消费完之前不关闭。5.2 内存还是涨Cursor 没生效检查三点Mapper 返回值是不是CursorTfetchSize 有没有设MySQL 连接串有没有useCursorFetchtrue。三者缺一流式就不成立。5.3 嵌套 resultMap 报错Cursor 注释里明确写了如果 resultMap 里用了 collectionSQL 必须用resultOrderedtrue并按 id 排序。否则嵌套映射会错乱。5.4 迭代过程中执行其他查询在 Cursor 迭代未结束时同一个 SqlSession 上执行其他查询可能因为连接被占用而阻塞或报错。建议把 Cursor 消费逻辑独立出来不要在迭代中混用同一连接做写操作。5.5 TaoToken 返回 401/403Key 没注入或过期。检查环境变量名是否和配置文件里的api_key_env一致。重新生成 Key 的入口https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite5.6 排障速查表现象可能原因排查动作Cursor is closed无事务加 Transactional内存飙升fetchSize 未生效检查连接串和 Options401Key 无效重新注入环境变量嵌套映射错乱未 resultOrderedSQL 加排序和 resultOrdered迭代阻塞同连接混用拆分消费逻辑6. 落地建议与通道入口Cursor 避免 OOM 的本质不是「换个返回类型」而是把「一次性全量加载」改成「按需拉取 生命周期可控」。事务边界、fetchSize、连接串参数三者必须同时到位缺一个都会退化成普通查询。验证时不要只看「没报错」要打印内存曲线确认百万行数据下堆内存平稳。如果你在接入过程中遇到通道或 Key 的问题优先走 API Keys 和接入文档API Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite需要验证模型输出是否符合预期用模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite长期做编码和 Agent 场景走 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite最后留一个实操技巧把 Cursor 消费逻辑包在一个独立的Transactional方法里方法内只做读取和轻量处理重活丢到队列异步做。这样事务持有时间短游标不会长时间占用连接数据库和 JVM 两边都轻松。
企业数字化 ERP 产品动态
相关推荐
如何参与 PilotDeck 开源社区:AI Agent 平台贡献流程、生态共创与路线图前瞻指南 如何参与 PilotDeck 开源社区:AI Agent 平台贡献流程、生态共创与路线图前瞻指南 【免费下载链接】PilotDeck Task-oriented AI Agent productivity platform 项目地址: https://gitcode.com/OpenBMB/PilotDeck
PilotDeck 是一个任务制的开源 AI Agent 生产力… · 2026/9/25 10:13:20
RT-Thread Smart 在泰山派1(RK3566)上的编译、烧录与 TF 卡启动实战指南 操作系统嵌入式物联网嵌入式OSRTOS 【免费下载链接】rt-thread RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/ 项目地址: https://gitcode.com/gh_mirrors/rt/rt-thread 点击查看 免费下载 泰山派1… · 2026/9/25 10:13:14
如何5分钟上手svga-cj:HarmonyOS仓颉语言SVGA动画播放器快速入门教程 如何5分钟上手svga-cj:HarmonyOS仓颉语言SVGA动画播放器快速入门教程 【免费下载链接】svga-cj svga格式动画展示库 项目地址: https://gitcode.com/Cangjie-TPC/svga-cj
svga-cj 是一款专为 HarmonyOS 打造的 SVGA 动画播放器库,使用仓颉语言&am… · 2026/9/25 10:13:14
佛山正规排名前五的破碎锤油封定制工厂推荐 客户口碑力荐 在佛山乃至整个珠三角工程施工领域,破碎锤油封的适配稳定性直接影响整台设备的作业效率,矿山露天作业高温多尘、硬岩高频冲击的恶劣工况,对油封的密封性能和使用寿命提出了更高要求。找到靠谱的正规供应商,不仅能减少漏油故障带来… · 2026/9/25 10:40:51
Atlas 300V部署YOLO全攻略:从推理加速卡到CANN实战 后台连着好几个做视觉检测的朋友问我同一个问题:“Atlas 300V 24G是不是运算加速卡?能不能直接拿来跑YOLO?”还有人直接甩过来一张电商截图,上面写着“AI推理加速卡”,下面评论区吵成一团,有人说这就是个“… · 2026/9/25 10:40:51
昇腾Atlas 300V部署YOLOv5完整指南:从模型转换到推理优化 拿到这块 Atlas 300V(我手头是 24G 显存版本)的时候,第一个念头就是拿它来跑 YOLO。网上搜“atlas 部署 yolo”能翻出一堆零散帖子,但要么停留在“能跑”的演示层面,要么把昇腾工具链的步骤写得像黑话,新手… · 2026/9/25 10:40:45
高通双旗舰芯片深度解读:端侧AI与性能爆发如何重塑安卓体验 这两年高通在旗舰芯片上的动作越来越有意思,从“一年一旗舰”变成“一年双旗舰”,而且把AI抬到了绝对核心的位置。Snapdragon 8 Elite Gen 6 与 Extreme Gen 6 一发布,基本上把 2026 年安卓旗舰的底牌提前亮了出来:一颗走主流旗舰… · 2026/9/25 10:40:45
江苏正规源头焊接套筒生产厂家客户口碑力荐 衡水万泉建筑机械有限公司 江苏正规源头焊接套筒生产厂家客户口碑力荐 衡水万泉建筑机械有限公司衡水万泉建筑机械有限公司是专业生产可焊套筒、焊接套筒的源头厂家,为各类钢混组合结构工程提供强度达标、定制灵活的钢筋钢结构过渡连接产品,兼具螺纹连接与焊接传力的双重优势&… · 2026/9/25 10:40:45
Atlas 300V 24G部署YOLO全流程:身份确认、模型转换到推理优化 拿到一块Atlas 300V 24G的时候,我第一反应是“这玩意能不能当显卡使”。问了一圈,发现很多人对它的定位都停留在“带显存的硬件加速卡”这个模糊概念上,尤其是搜索框里还经常出现“atlas部署yolo”、“atlas 300v 24g 是运算加速卡吗”这类问… · 2026/9/25 10:40:45
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37