MyBatis流式查詢詳解

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 …