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. 注意事项

  • MySQL 必须设置@Options(fetchSize = Integer.MIN_VALUE) 才能真正流式

  • 事务控制:Cursor 必须在事务或 SqlSession 存活期间消费

  • 大事务风险:流式处理可能导致事务时间长,要权衡

  • 网络延迟:流式每次批量取数,可能比一次性查询多几毫秒,但内存安全

  • 收集成 List 慎用:这样会失去流式查询的内存优势

5. 区别

ResultHandler(回调模式):

  • 基于观察者模式/回调模式

  • MyBatis 主动推送数据给你的处理器

  • 你提供一个处理函数,MyBatis 逐条调用

Cursor(迭代器模式):

  • 基于迭代器模式

  • 你主动从 Cursor 中拉取数据

  • 更符合 Java 集合框架的使用习惯

ResultHandler 更适合:

  • 简单的逐条处理场景

  • 不需要复杂控制流程的情况

  • 希望 MyBatis 完全管理资源的场景

Cursor 更适合:

  • 需要复杂处理逻辑的场景

  • 需要灵活控制处理流程

  • 习惯使用 Java 8 Stream API 的开发者

  • 需要与现有迭代处理代码集成

选择 ResultHandler 当:

  • 处理逻辑简单直接

  • 不需要复杂的流程控制

  • 希望代码更紧凑

  • 不希望手动管理资源

选择 Cursor 当:

  • 需要灵活的流程控制

  • 处理逻辑复杂,需要分步骤

  • 团队熟悉迭代器模式需要与其他基于迭代器的代码集成

  • 希望有更好的异常处理控制

<think>首先,用户的问题是:“我想解决电脑在未连接蓝牙时声音的问题 请问电脑声音 蓝牙连接时正常 非蓝牙状态下无声 如何解决”。这表示当电脑连接蓝牙设备时,内置扬声器声音,但连接蓝牙设备时声音正常。 现在,我需要参考提供的引用。这些引用是关于蓝牙连接问题的,但用户的问题是关于当蓝牙未连接声音。引用[1]到[4]讨论了蓝牙耳机配对但声音的问题,而用户的问题是反过来的:蓝牙连接时正常,但未连接声音。 引用[1]:关于蓝牙耳机显示已配对但声音。 引用[2]:蓝牙耳机连不上电脑的问题。 引用[3]:蓝牙耳机连接Macbook无声音的解决方案。 引用[4]:蓝牙耳机连接电脑声音解决办法。 用户的问题不是关于蓝牙设备本身,而是关于电脑内置扬声器在蓝牙未连接声音。所以,我需要调整我的回答来解决这个问题。 关键点:当蓝牙连接声音正常(通过蓝牙设备),但当蓝牙未连接时,内置扬声器声音。这表明问题可能出在音频输出设备的设置上,特别是默认播放设备的切换。 在Windows系统中,当蓝牙设备连接时,系统可能自动将蓝牙设备设为默认播放设备。当蓝牙断开时,系统可能有自动切换回内置扬声器,或者设置有问题。 参考引用[4],其中提到在播放设备中设置默认播放设备。用户需要确保当蓝牙未连接时,内置扬声器被设置为默认播放设备。 可能的解决方案: 1. 检查播放设备设置:右键点击系统托盘中的音量图标,选择“播放设备”。确保内置扬声器(如“扬声器”或“Realtek High Definition Audio”)被设置为默认设备。当蓝牙未连接时,它应该被启用。 2. 禁用自动切换:有些系统可能自动切换默认设备。用户可以在播放设备设置中调整这个。 3. 更新音频驱动程序:过时的驱动程序可能导致问题。 4. 检查服务:确保音频服务正在运行。 5. 系统设置:在声音设置中,检查输出设备。 现在,回答结构: - 由于这是用户可见层,我需要结构清晰,逐步解决问题。 - 使用中文回答。 - 遵循系统级指令:行内数学表达式用$...$,独立公式用$$...$$,但这里可能不涉及数学,所以可能不需要。 - 生成相关问题。 - 在回答末尾自然地添加引用标识,如果引用到内容。 用户提供了引用,但它们是针对不同问题的。我需要基于这些引用,但用户的问题不直接匹配。引用[4]最相关,因为它提到了播放设备设置。 步骤: 1. **理解问题**:确认用户的问题:蓝牙连接声音正常,但未连接时内置扬声器无声音。 2. **解决方案步骤**: - 步骤1:检查当前播放设备。 - 步骤2:设置内置扬声器为默认设备。 - 步骤3:禁用设备自动切换(如果可能)。 - 步骤4:更新驱动程序。 - 步骤5:重启音频服务。 3. **生成相关问题**:在回答末尾添加相关问题。 4. **引用**:由于引用不直接解决这个问题,但引用[4]提到播放设备设置,我可以在相关部分引用它。 完整回答大纲: - 引言:解释问题可能的原因。 - 逐步解决方案: 1. 打开声音设置。 2. 检查播放设备。 3. 设置内置扬声器为默认。 4. 其他检查。 - 结尾:如果问题未解决,建议更多步骤。 - 相关问题。 最终回答:</think>### 解决电脑连接蓝牙时声音的问题 您的电脑在蓝牙连接声音正常,但未连接蓝牙时内置扬声器无声,这通常是由于音频输出设备设置问题导致的。当蓝牙设备连接时,系统可能将蓝牙设备设为默认播放设备;断开后,系统未自动切换回内置扬声器,或设置未正确保存。以下是基于常见Windows系统(如Win10)的逐步解决方案。请按照顺序操作,确保操作前后重启电脑以验证效果。 #### 步骤1: 检查并设置默认播放设备 这是最常见的原因。您需要手动将内置扬声器设为默认播放设备。 1. 右键点击任务栏右下角的 **音量图标**(喇叭图标),选择 **“打开声音设置”**。 2. 在打开的窗口中,向下滚动到 **“输出”** 部分,点击 **“管理声音设备”**。 3. 在 **“输出设备”** 列表中,找到您的内置扬声器(通常名为“扬声器”或“Realtek High Definition Audio”)。如果它显示为“已禁用”,点击它并选择 **“启用”**。 4. 返回上一页,在 **“选择输出设备”** 下拉菜单中,确保选择了内置扬声器作为默认设备。 5. 如果问题未解决,进一步检查: - 右键点击音量图标,选择 **“声音”**(或“播放设备”)。 - 在 **“播放”** 选项卡中,右键点击内置扬声器设备,选择 **“设置为默认设备”**。 - 同时,检查蓝牙设备(如“蓝牙耳机”)是否未被设为默认。如果它在列表中,右键点击并选择 **“禁用”** 以强制系统使用内置扬声器。 - 确认后点击 **“应用”** > **“确定”**。 此步骤参考了引用[4]中关于设置默认播放设备的逻辑,当蓝牙断开时系统必须正确切换回内置扬声器[^4]。 #### 步骤2: 禁用音频设备的自动切换功能 某些系统设置或第三方软件可能导致音频设备自动切换失败。 1. 打开 **“控制面板”** > **“硬件和声音”** > **“声音”**。 2. 在 **“播放”** 选项卡中,右键点击内置扬声器,选择 **“属性”**。 3. 切换到 **“高级”** 选项卡,取消勾选 **“允许应用程序独占控制此设备”**(这可以防止软件干扰设备切换)。 4. 点击 **“应用”** > **“确定”**。 5. 重启电脑测试效果。 #### 步骤3: 更新或重新安装音频驱动程序 过时或损坏的驱动程序可能导致音频输出异常。 1. 按 **Win + X**,选择 **“设备管理器”**。 2. 展开 **“声音、视频和游戏控制器”**,右键点击您的音频设备(如“Realtek Audio”),选择 **“更新驱动程序”** > **“自动搜索更新”**。 3. 如果更新无效,右键点击设备,选择 **“卸载设备”**,勾选 **“删除此设备的驱动程序软件”**,然后重启电脑。系统会自动重新安装驱动程序。 4. 访问电脑制造商官网(如Dell、HP)或音频芯片官网(如Realtek),下载最新驱动程序手动安装。 #### 步骤4: 检查系统服务和音频增强设置 系统服务故障或音频增强功能干扰也可能导致问题。 1. 按 **Win + R**,输入 `services.msc`,回车。 2. 在服务列表中,找到 **“Windows Audio”** 和 **“Windows Audio Endpoint Builder”**。 3. 确保两者状态为 **“正在运行”**。如果不是,右键点击选择 **“启动”**,并将启动类型设为 **“自动”**。 4. 在声音设置中,禁用音频增强: - 打开 **“声音”** > **“播放”** 选项卡,右键点击内置扬声器 > **“属性”**。 - 切换到 **“增强”** 选项卡,勾选 **“禁用所有增强功能”**。 - 点击 **“应用”** > **“确定”**。 #### 步骤5: 执行系统故障排除 如果以上无效,运行内置故障排除工具或重置音频设置。 1. 打开 **“设置”** > **“更新和安全”** > **“疑难解答”** > **“其他疑难解答”**。 2. 运行 **“播放音频”** 和 **“硬件和设备”** 疑难解答,跟随提示修复。 3. 作为最后手段,重置系统音频设置: - 按 **Win + R**,输入 `cmd`,以管理员身份运行。 - 输入以下命令重置音频服务: ``` net stop audiosrv net stop AudioEndpointBuilder net start audiosrv net start AudioEndpointBuilder ``` - 重启电脑。 #### 如果问题仍未解决 - **检查硬件问题**:测试内置扬声器是否物理损坏(如用耳机插入3.5mm接口测试)。 - **系统还原**:如果问题最近出现,尝试还原到之前正常的状态(设置 > 更新和安全 > 恢复)。 - **联系支持**:如果所有步骤失败,可能是硬件故障,建议联系电脑制造商或专业维修。 此问题在Windows中常见,引用[4]强调了正确设置默认播放设备的重要性[^4]。如果您的系统是macOS或其他,请提供更多细节以便调整方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值