当一次要导出非常多数据的时候,例如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}