MyBatis流式查询两种实现方式

2025-08-09 21:50

本文主要是介绍MyBatis流式查询两种实现方式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《MyBatis流式查询两种实现方式》本文详解MyBatis流式查询,通过ResultHandler和Cursor实现边读边处理,避免内存溢出,ResultHandler逐条回调,Cursor支持迭代...

MyBatis 流式查询详解:ResultHandler 与 Cursor

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

1. 什么是流式查询?

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

适用场景

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

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

2.1 使用 ResultHandler

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

不带参数示例
@Mapper
public interface UserMapper {
    @Select("SELECT id, name, age FROM user")
    void scanAlhttp://www.chinasem.cnlUsers(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") inhttp://www.chinasem.cnt age);
}

调用:

@Transactional
@Transactional
public void getUsersByAge(int minAge) throws IOException {
    try (Cursor<User> cursor = userMapper.scanUsersByAge(minAge)) {
        for (User http://www.chinasem.cnuser : 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 查询即可

到此这篇关于MyBatis流式查询两种实现方式的文章就介绍到这了,更多相关mybatis流式查询内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于MyBatis流式查询两种实现方式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155657

相关文章

Python Excel 通用筛选函数的实现

《PythonExcel通用筛选函数的实现》本文主要介绍了PythonExcel通用筛选函数的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录案例目的示例数据假定数据来源是字典优化:通用CSV数据处理函数使用说明使用示例注意事项案例目的第一

C#使用SendMessage实现进程间通信的示例代码

《C#使用SendMessage实现进程间通信的示例代码》在软件开发中,进程间通信(IPC)是关键技术之一,C#通过调用WindowsAPI的SendMessage函数实现这一功能,本文将通过实例介绍... 目录第一章:SendMessage的底层原理揭秘第二章:构建跨进程通信桥梁2.1 定义通信协议2.2

JAVA实现亿级千万级数据顺序导出的示例代码

《JAVA实现亿级千万级数据顺序导出的示例代码》本文主要介绍了JAVA实现亿级千万级数据顺序导出的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 前提:主要考虑控制内存占用空间,避免出现同时导出,导致主程序OOM问题。实现思路:A.启用线程池

Python实现中文大写金额转阿拉伯数字

《Python实现中文大写金额转阿拉伯数字》在财务票据中,中文大写金额被广泛使用以防止篡改,但在数据处理时,我们需要将其转换为阿拉伯数字形式,下面我们就来看看如何使用Python实现这一转换吧... 目录一、核心思路拆解二、中文数字解析实现三、大单位分割策略四、元角分综合处理五、测试验证六、全部代码在财务票

java 恺撒加密/解密实现原理(附带源码)

《java恺撒加密/解密实现原理(附带源码)》本文介绍Java实现恺撒加密与解密,通过固定位移量对字母进行循环替换,保留大小写及非字母字符,由于其实现简单、易于理解,恺撒加密常被用作学习加密算法的入... 目录Java 恺撒加密/解密实现1. 项目背景与介绍2. 相关知识2.1 恺撒加密算法原理2.2 Ja

React 记忆缓存的三种方法实现

《React记忆缓存的三种方法实现》本文主要介绍了React记忆缓存的三种方法实现,包含React.memo、useMemo、useCallback,用于避免不必要的组件重渲染和计算,感兴趣的可以... 目录1. React.memo2. useMemo3. useCallback使用场景与注意事项在 Re

Nginx实现端口映射的示例代码

《Nginx实现端口映射的示例代码》本文主要介绍了Nginx实现端口映射的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1. 找到nginx的部署路径2. 备份原来的配置文件3. 编辑nginx.conf文件4. 在

Java StringBuilder 实现原理全攻略

《JavaStringBuilder实现原理全攻略》StringBuilder是Java提供的可变字符序列类,位于java.lang包中,专门用于高效处理字符串的拼接和修改操作,本文给大家介绍Ja... 目录一、StringBuilder 基本概述核心特性二、StringBuilder 核心实现2.1 内部

Nginx屏蔽服务器名称与版本信息方式(源码级修改)

《Nginx屏蔽服务器名称与版本信息方式(源码级修改)》本文详解如何通过源码修改Nginx1.25.4,移除Server响应头中的服务类型和版本信息,以增强安全性,需重新配置、编译、安装,升级时需重复... 目录一、背景与目的二、适用版本三、操作步骤修改源码文件四、后续操作提示五、注意事项六、总结一、背景与

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码