背景
最近有个现网问题,管理系统导出大量数据时候,出现了oom。分析代码需要优化的地方:
-
使用hibernate查询大量数据,会生成大量对象,最终导致oom,此处需要分批查询。
-
使用工具包 :poi-ooxml。SXSSFWorkbook能够在内存达到_randomAccessWindowSize会自动刷到磁盘,但是代码中先在内存中创建全量的row然后再往SXSSFWorkbook中写,依然会耗尽内存。此处需要分批往SXSSFWorkbook中写。
下面是SXSSFSheet的createRow方法: -
如果我们使用多线程分批处理,SXSSFSheet不支持多线程写,它只会生成一个临时文件,SXSSFWorkbook的分页可以支持并发写,这又不符合需求。因此这里我改写SXSSFWorkbook源码,为每个线程分别创建一个临时文件,最后导出的时候再将多个文件流合并成一个文件流。
代码
- 导入pom:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
- 多线程处理:
public void exportOrderExcelData(Map<String, String> params, OrderExcelView orderExcelView) throws ExecutionException, InterruptedException, YYZXException {
List<Object> paramValues = new ArrayList<Object>();
String hql = "";
if (StringUtils.isNotBlank(params.get("estimationFrom"))) {
hql = OrderQuery.parseQueryByEstimationFrom(params, paramValues);
} else {
hql = parseQueryCondition(params, paramValues);
}
String countHql = platformReadOnlyDao.toCountHQL(hql);
Integer countNums = platformReadOnlyDao.getCommonDao().findCount(countHql, paramValues.toArray());
if (countNums > 2000000) {
throw new YYZXException(RetCode.ORDER_NUM_OVER_ERROR.getCode(), RetCode.ORDER_NUM_OVER_ERROR.getDesc());
}
List<SujiOrganizationConfig> sujiConfigList = platformReadOnlyDao.find("from SujiOrganizationConfig");
if (countNums < max_send) {
List<Object> datas = platformReadOnlyDao.find(hql, paramValues.toArray());
ObjectStreamToSXSSFWorkbook.objectToSXSSFWorkbook(orderExcelView, getOrderRowLists(rebuildDatas(datas), sujiConfigList), 0, max_send,countNums);
} else {
int limit = countStep(countNums);
CountDownLatch countDownLatch = new CountDownLatch(limit);
for (int i = 0; i < limit; i++) {
int finalI = i;
String finalHql = hql;
CompletableFuture.runAsync(() -> {
try {
List<Object> datas = platformReadOnlyDao.findWithLimit(finalHql, finalI * max_send, max_send, paramValues.toArray());
ObjectStreamToSXSSFWorkbook.objectToSXSSFWorkbook(orderExcelView, getOrderRowLists(rebuildDatas(datas), sujiConfigList), finalI, max_send, countNums);
} finally {
countDownLatch.countDown();
}
}, executor);
}
countDownLatch.await();
}
}
/**
* 计算切分次数
*/
private Integer countStep(Integer size) {
return (size + max_send - 1) / max_send;
}
- 修改SXSSFWorkbook、SXSSFSheet部分源码:
在SXSSFSheet类中增加_writerMap和_rowsMap键为线程index。为每个线程创建独立的_rows和_writer。
修改SXSSFWorkbook类的copyStreamAndInjectWorksheet方法,使之支持多个文件流copy.
总结
日常开发中应该多阅读源码。