0%

MyBatis读取大量数据-流式读取

导出大量数据时,虚拟机频繁GC,内存耗尽,CPU爆满,可采用Mybatis数据流式读取进行优化。

方案:

分页读取出来。缺点:需要排序后分页读取,性能低下。
一次性读取出来。缺点:需要很大内存,一般计算机不行。
建立长连接,利用服务端游标,一条一条流式返回给java端。

JDBC三种读取方式:
  1. 一次全部(默认):一次获取全部。
  2. 流式:多次获取,一次一行。
  3. 游标:多次获取,一次多行。

mybatis默认采取第一种。

实现

在mapper映射文件中,编写流式查询的逻辑。

1
2
3
<select id="selectFetchSize" fetchSize="-2147483648" resultSetType="FORWARD_ONLY" resultType="com.qf.shop.cms.entity.TContent">
select * from t_content
</select>

在mapper接口文件中添加selectFetchSize方法。

1
2
// 参数 ResultHandler 是一个回调接口,也就是从游标中获得一条数据就会回调接口中的方法
void selectFetchSize(ResultHandler<TContent> handler);

自己编写一个类实现ResultHandler接口,在该接口中定义从游标获得一条数据后的回调逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* 通过流式查询每获得一条数据的回调类
*/
public class TContentResultHandler implements ResultHandler<TContent> {

/**
* 这里每集满1000条数据 往硬盘的excel文件中追加一次数据
*/
private final static int BATCH_SIZE = 1000;
/**
* 计数器
*/
private int size=0;
/**
* 存储每批数据的临时容器
*/
private List<TContent> tContents = new ArrayList<>();

/**
* 每从流式查询中获得一行结果,就会调用一次这个方法
* @param resultContext
*/
@Override
public void handleResult(ResultContext<? extends TContent> resultContext){
// 这里获取流式查询每次返回的单条结果
TContent resultObject = resultContext.getResultObject();
// 你可以看自己的项目需要分批进行处理或者单个处理,这里以分批处理为例
tContents.add(resultObject);
size++;
if (size == BATCH_SIZE) {
// 如果集满1000条就往文件中写一次
handle();
}

}

/**
* 集满1000条 执行一次的逻辑
*/
private void handle() {
try {
// 在这里可以对你获取到的批量结果数据进行需要的业务处理
// 这里的业务是 往文件中写一次
} finally {
// 处理完每批数据后后将临时清空
size = 0;
tContents.clear();
}
}

/**
* 这个方法给外面调用,用来完成最后一批数据处理
*/
public void end(){
handle();// 处理最后一批不到BATCH_SIZE的数据
}

}

在业务逻辑(service)层调用流式查询方法。

1
2
3
4
5
6
7
8
9
10
11
@Autowired
private TContentMapper contentMapper;

public void streamQuery(){
// 生成流式查询的回调对象
TContentResultHandler tContentResultHandler = new TContentResultHandler();
// 调用流式查询
contentMapper.selectFetchSize(tContentResultHandler);
// 执行完最后一批数据的逻辑
tContentResultHandler.end();
}