执子之手

与子偕老


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

  • 搜索
close

使用MyBatis ResultHandler解决巨大结果集导出Excel引发的OOM

时间: 2020-09-04   |   分类: 开发     |   阅读: 923 字 ~2分钟   |   访问: 0

当一次要导出非常多数据的时候,例如100w条,如果使用MyBatis将全部结果都查询到list中,经常会导致内存溢出。这个时候,我们可以使用MyBatis的ResultHandler来使用游标方式访问数据,从而避免OOM。

ResultHandler是MyBatis提供的一个接口,通过该接口可以让MyBatis以流式的方式处理结果集,而不必等待整个结果集全部准备完毕,在准备好一条记录后就调用该接口中的handleResult方法:

1void handleResult(ResultContext<? extends T> resultContext);

要使用ResultHandler需要在Mapper层声明的时候做一定的处理。这里简单描述一下。

1. 基于注解

 1public interface OrderMapper extends BaseMapper<Order> {
 2    /**
 3     * 导出订单功能
 4     * @param wrapper
 5     * @param handler
 6     */
 7    @Select("select * from tap_order ${ew.customSqlSegment}")
 8    @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
 9    @ResultType(Order.class)
10    void export(@Param("ew") Wrapper wrapper, ResultHandler<Order> handler);
11}

基于注解的声明方式看上去相当直观:

  • 使用@Select注解声明sql语句;
  • 使用@ResultType声明结果集的类型;
  • 使用@Options声明了两个配置项,其目的是开启MySQL的客户端游标。如果不配置这两项,MySQL默认还是会把整个结果集返回客户端;
  • 函数声明中增加一个ResultHandler类型的参数,返回值也改成了void类型。

2. 基于XML配置

基于XML的配置在XML文件中与之前的传统方式配置一个查询是一样的,只是需要增加fetchSize和resultSetType两个配置项以启用MySQL的客户端游标:

1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3<mapper namespace="com.eveus.tap.trade.mapper.OrderMapper">
4    <select id="export" fetchSize="-2147483648" resultSetType="FORWARD_ONLY" resultType="com.eveus.tap.trade.po.Order">
5        SELECT * FROM `tap_order` ${ew.customSqlSegment}
6    </select>
7</mapper>

在Mapper类中,定义函数接口和基于注解的一样,只是不需要注解声明。

 1public interface OrderMapper extends BaseMapper<Order> {
 2    /**
 3     * 导出订单功能
 4     * @param wrapper
 5     * @param handler
 6     */
 7    //@Select("select * from tap_order ${ew.customSqlSegment}")
 8    //@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
 9    //@ResultType(Order.class)
10    void export(@Param("ew") Wrapper wrapper, ResultHandler<Order> handler);
11}

3. 使用方法

 1class OrderServiceImpl {
 2    public ServiceResult<ExcelByte> export(Wrapper<Order> wrapper) {
 3        ExcelExporter exporter = new ExcelExporter();
 4        exporter.addColumn("订单号").withWidth(40);
 5        // 此时使用的就是ResultHandler对结果集进行处理
 6        this.getBaseMapper().export(wrapper, resultContext -> {
 7            Order order = resultContext.getResultObject();
 8            List values = new ArrayList();
 9            values.add(order.getId());
10            exporter.addRow(values);
11        });
12        if (exporter.getTotalRows()==0) {
13            return ServiceResult.fail(ResultCode.EXCEL_NO_ROWS);
14        }
15        // 转化为byte数组
16        ExcelByte excelByte = new ExcelByte();
17        excelByte.setContent(exporter.toBytes());
18        exporter.dispose();
19        return ServiceResult.ok(excelByte);
20    }
21}

附录、参考资料

  • Mybatis流式查询避免OOM
  • ResultHandler的用法
  • mybatis ResultHandler vs ResultSetHandler及自定义扩展
  • MyBatis Plus条件构造器
#Java# #MyBatis# #OOM# #ResultHandler# #内存溢出#
禁用CleanMyMacX HealthMonitor
使用Python写一个Alfred Workflow
  • 文章目录
  • 站点概览
Orchidflower

Orchidflower

Do one thing at a time, and do well.

77 日志
6 分类
84 标签
GitHub 知乎 OSC 豆瓣
  • 1. 基于注解
  • 2. 基于XML配置
  • 3. 使用方法
  • 附录、参考资料
© 2009 - 2024 执子之手
Powered by - Hugo v0.113.0
Theme by - NexT
ICP - 鲁ICP备17006463号-1
0%