文章目录
- 作者
- 文章推荐理由
- 代码实现
- 改进版实现
作者
物流项目组 徐连臣
文章推荐理由
项目中报表导出很常用,有的开发不注意全量导出如果数据量情况较多的情况往往会导致系统内存溢出,他这个导出是类似分页的模式,每查一页就写到文件,就规避了内存溢出的问题,而且用了反射,通用性较强。推荐人:老鹰。
代码实现
Excel导出时,如果一次性查询的数据量过大,容易造成内存溢出,导致系统服务挂了,如果Excel的一个sheet页的数据很大的话,打开Excel时加载数据就会花很长时间,如何分批查,分批插呢?使用SXSSFWorkbook可以完成这样的功能。话不多说,还是直接上代码吧。
private static final String[] IMPORT_PAY_TITLE =
{"##名称", "导入时间", "#期", "####单位", "###费", "####费", "申请状态", "支付状态"};
// 根据条件查询要导出的总条数
int totalCount = settlementRecordRepository.countByCondition(queryDTO);
// 定义每页数据数量
int pageSize = queryDTO.getPageSize();
// 总数量除以每页显示条数等于页数
int totalPage =
totalCount % pageSize > 0 ? totalCount / pageSize + 1 : totalCount / pageSize;
SXSSFWorkbook wb = new SXSSFWorkbook();
// 循环获取产生每页数据
for (int i = 0; i < totalPage; i++) {
queryDTO.setPageNum(i + 1);
List<ImportPayRecordVO> recordVOList = getPayRecordPageList(queryDTO).getList();
// 新建sheet
Sheet sheet;
sheet = wb.createSheet("Sheet" + (i + 1));
// 第0行
Row header = sheet.createRow(0);
// 产生标题列,每个sheet页产生一个标题
Cell cell;
for (int j = 0; j < IMPORT_PAY_TITLE.length; j++) {
cell = header.createCell(j);
cell.setCellValue(IMPORT_PAY_TITLE[j]);
}
// 迭代数据
if (!CollectionUtils.isEmpty(recordVOList)) {
int rowNum = 1;
for (ImportPayRecordVO recordVO : recordVOList) {
sheet.setDefaultColumnWidth(17);
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(recordVO.getTitle());
row.createCell(1).setCellValue(DateUtil.format(recordVO.getGmtCreate(),
DateFormatterEnum.DATE_WITH_TIME.getCode()));
row.createCell(2).setCellValue(recordVO.getReconRange());
row.createCell(3).setCellValue(recordVO.getTransSettlementName());
row.createCell(4).setCellValue(recordVO.getActualPrice());
row.createCell(5).setCellValue(recordVO.getOnlinePrice());
row.createCell(6).setCellValue(recordVO.getApplyStatusText());
row.createCell(7).setCellValue(recordVO.getAuditStatusText());
}
}
}
wb.write(outputStream);
outputStream.close();
实现过程就是分页查询,分页插入数据。查询出来的第一页结果插入到sheet1页,查询出来的第二页结果插入到
sheet2页中,每页的条数可以自己定义,这样就避免了一次性查询数据量过大导致内存溢出的问题。
改进版实现
前面写了一个大数据量Excel导出分页查,分页插的方法,今天优化了一下,提取成一个公共方法,导出列也可以自定义。代码如下:
String gridName = "#####列表";
List<KvObject> kvObjects = gridFieldApi.queryFieldCaptions(gridName);
PageInfo<TruckReconVO> pageList = getTruckReconPageList(queryDTO);
// 查询总条数
int totalCount = (int) pageList.getTotal();
// 定义每页数据数量
int pageSize = queryDTO.getPageSize();
// 总数量除以每页显示条数等于页数
int totalPage =
totalCount % pageSize > 0 ? totalCount / pageSize + 1 : totalCount / pageSize;
SXSSFWorkbook wb = new SXSSFWorkbook();
// 循环获取产生每页数据
for (int i = 0; i < totalPage; i++) {
queryDTO.setPageNum(i + 1);
List<TruckReconVO> reconVOList;
if (i == 0) {
reconVOList = pageList.getList();
} else {
reconVOList = getTruckReconPageList(queryDTO).getList();
}
ExcelUtil.setSheetContent(wb, kvObjects, reconVOList, i + 1);
}
wb.write(outputStream);
表头参数可以让前端传(更灵活),可以查询系统表头列,也可以查询用户自定义表头列,看自己需要(提供的API接口实现)。这样如果页面展示增减列就不用每次改代码,重新打包发布了。对于总条数的查询,可以分页查询一次,从这里获取到总条数,避免需求修改查询条件改变了,根据条件查询总条数(另一个接口)有遗漏修改的地方(线上出现了这种情况。。555。。。)。
ExcelUtil工具类如下:
/**
* 导出Excel
*
* @param wb
* @param kvObjects 表头参数
* @param dataList 导出的数据
* @param pageNum 导出页码
* @param <T>
* @throws Exception
*/
public static <T> void setSheetContent(SXSSFWorkbook wb, List<KvObject> kvObjects, List<T> dataList,
int pageNum) throws Exception {
// 新建sheet
Sheet sheet;
sheet = wb.createSheet("Sheet" + pageNum);
// 第0行
Row header = sheet.createRow(0);
// 产生标题列,每个sheet页产生一个标题
Cell cell;
// 列数
int columnSize = kvObjects.size();
for (int j = 0; j < columnSize; j++) {
cell = header.createCell(j);
cell.setCellValue((String) kvObjects.get(j).getValue());
}
// 迭代数据
if (!CollectionUtils.isEmpty(dataList)) {
int rowNum = 1;
for (T recordVO : dataList) {
sheet.setDefaultColumnWidth(17);
Row row = sheet.createRow(rowNum++);
for (int k = 0; k < columnSize; k++) {
Field field = recordVO.getClass()
.getDeclaredField((String) kvObjects.get(k).getKey());
// 设置对象的访问权限,保证对private的属性的访问
field.setAccessible(true);
Object o = field.get(recordVO);
cell = row.createCell(k);
if (o == null) {
cell.setCellValue("");
} else if (o instanceof Date) {
cell.setCellValue(DateUtil.format((Date) o,
DateFormatterEnum.DATE_WITH_TIME.getCode()));
} else if (o instanceof String) {
cell.setCellValue(String.valueOf(o));
} else if (o instanceof Integer) {
cell.setCellValue((int) o);
} else if (o instanceof Double) {
cell.setCellValue((double) o);
}
}
}
}
}
主要是利用反射获取表头对应列的属性值,填充到Excel每个单元格内。