MyBatis 流式查询实战:ResultHandler 处理海量数据

概述

Mybatis 的默认查询行为是"全量加载":JDBC 驱动把整个 ResultSet 缓存到客户端内存,Mybatis 再逐行映射成 Java 对象塞进 List。数据量少的时候没有问题,但是数据量一旦多起来,这就是一颗内存炸弹

其中一个解决办法就是 Mybatis 的 ResultHandler 流式查询。它让结果集不再一次性加载,而是每读取一条数据就回调一次 handleResult 方法,处理完立即释放

本文包含完整的测试代码:XML 配置、Mapper 接口定义、Service 回调实现、测试类调用。拿来就能跑,跑通就能用。

示例

SQL XML 配置文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="wxw.mengyuan.dsf.entity.operations.systemmonitor.thread.mapper.ThreadMapper">
  <!-- 流式查询 -->  <!-- fetchSize:设置为 Integer 的最小值,代表开启流式查询 -->
  <select id="streamQuery" resultType="wxw.mengyuan.dsf.entity.operations.systemmonitor.thread.entity.ThreadEntity" fetchSize="-2147483648">
    SELECT * FROM dsf_thread WHERE COLLECT_TIME BETWEEN #{start} AND #{end} ORDER BY  COLLECT_TIME DESC, ID DESC
  </select>
</mapper>

Mapper 数据类

java 复制代码
/** * 线程信息数据类 */
@Mapper
@Repository
public interface ThreadMapper extends BaseMapper<ThreadEntity> {
  /**
   * 流式读取
   * @param start 开始时间
   * @param end 结束时间
   * @param handler 结果集处理器
   */
  // 注意事项
  // (1) 返回结果必须为 void
  // (2) 在传入参数之后,必须使用 ResultHandler 对象接收返回结果
  void streamQuery(@NonNull @Param("start") LocalDateTime start, @NonNull @Param("end") LocalDateTime end, ResultHandler<SqlLogEntity> handler);
}

Service 业务类

java 复制代码
/**
 * 线程信息业务类
 */
@Slf4j
@Service
public class ThreadService extends ServiceImpl<ThreadMapper, ThreadEntity> {

  /**
   * 流式读取
   * @param start 开始时间
   * @param end 结束时间
   */
  public void streamQuery(@NonNull LocalDateTime start, @NonNull LocalDateTime end) {
    long startTime = System.currentTimeMillis();
    AtomicInteger count = new AtomicInteger(0);
    this.baseMapper.streamQuery(start, end, new ResultHandler<SqlLogEntity>() {
      @Override
      public void handleResult(ResultContext<? extends SqlLogEntity> resultContext) {
        // 打印日志        log.warn("当前已经遍历了 {} 条数据", count.incrementAndGet());
        // 获取一条数据:包装好的实体类对象
        // System.out.println(resultContext.getResultObject());
        // 满足某种条件时,停止数据获取(关闭ResultSet,后续不会再进入当前回调函数)
        if (count.get() >= 1000000) {
          resultContext.stop();
        }
      }
    });
    log.warn("耗时 {} 毫秒", System.currentTimeMillis() - startTime);
  }
}

测试类

java 复制代码
/**
 * Mybatis案例
 */
@SpringBootTest(classes = OperationsApplication.class)
public class MybatisDemo {
  /** 线程信息业务类 */
  @Autowired
  private ThreadService threadService;
  @Test
  public void mybatisTest() {
    threadService.streamQuery(LocalDateTime.now().minusYears(1), LocalDateTime.now());
  }
}

测试结果示例

相关推荐
天天被压力1 小时前
【跨市场数据实战 #08】可转债折价机会怎么筛:3个接口抓比价、列表和实时盘口
java·人工智能·python
张某布响丸辣2 小时前
附件下载的安全边界:路径白名单、NAS 哨兵文件与 Redis Lua 一次性令牌
java·redis·redis lua·nas哨兵
YatHinLay2 小时前
Spring Boot + Calcite 实现跨库查询
java
酷炫码神3 小时前
MosMos 创意效果与能力边界展示
java
Wang's Blog3 小时前
Java 接入Redis: 使用Jedis操作Redis
java·服务器·redis
事圆则缓3 小时前
Java 8 Lambda、Stream、Optional 与 Android 边界:从回调语法到运行时兼容
android·java
shehuiyuelaiyuehao3 小时前
算法50,分治归并,数组中的逆序对
java·算法
烽学长3 小时前
(附源码)基于Springboot+vue的图书阅读分享系统的设计与实现
java·spring boot·后端
Ivanqhz3 小时前
数据搬运是性能关键
java·服务器·网络·人工智能·深度学习