项目场景:
导出40万以上的数据到xlsx问题描述:
实施地出现导出40万数据到excel中时,CPU占不超过100%,时间超过半个小时,系统中其他功能模块卡顿 @EcpGetMapping("/exportResultDetail")
public void exportResultDetail(final HttpServletRequest req, final HttpServletResponse resp, @RequestParam final String operParams) {
byte[] exportBytes = null;
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
try {
JSONObject jsonObj = JSONObject.parseObject(operParams);
String fileName = String.valueOf(jsonObj.get("fileName"));
String fileType = String.valueOf(jsonObj.get("fileType"));
String compId = String.valueOf(jsonObj.get("compId"));
String modelId = String.valueOf(jsonObj.get("modelId"));
List<Map> colList = this.mainResultService
.findColListByModelId(compId, modelId);
String queryParamStr = String.valueOf(jsonObj.get("queryParam"));
Map queryParam = JSONUtil.fromJsonString(queryParamStr, Map.class);
// fileName = new String(fileName.getBytes("iso8859-1"),"utf8");
Map<String, Object> queryResult = this.tAmModelMainResultService
.queryDetailList(queryParam);
List<Map> list = (List<Map>) queryResult.get("data");
final Workbook exportWorkbook = AmExcelHelpUtil.createWorkbook(fileType);
exportWorkbook.createSheet(fileName);
final CellStyle headCellStyle = exportWorkbook.createCellStyle();
final CellStyle contentCellStyle = exportWorkbook.createCellStyle();
final CellStyle contentCenterCellStyle = exportWorkbook.createCellStyle();
final CellStyle contentRightCellStyle = exportWorkbook.createCellStyle();
final Font headCellFont = exportWorkbook.createFont();
final Font contentFont = exportWorkbook.createFont();
final Sheet manuSheetNew = exportWorkbook.getSheetAt(0);
manuSheetNew.setDefaultRowHeightInPoints((short) 25);
manuSheetNew.setDefaultColumnWidth(25);
final Row headRow = manuSheetNew.createRow(0); // 标题行
int colCount = colList.size();
for (int j = 0; j < colCount; j++) {
final Cell cell = headRow.createCell(j);
headCellStyle.setAlignment(CellStyle.ALIGN_CENTER);
headCellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
headCellStyle.setWrapText(true);
headCellFont.setFontHeightInPoints((short)10);
headCellFont.setFontName("微软雅黑");
headCellFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
headCellStyle.setFont(headCellFont);
headCellStyle.setBorderBottom(CellStyle.BORDER_THIN);
headCellStyle.setBorderLeft(CellStyle.BORDER_THIN);
headCellStyle.setBorderRight(CellStyle.BORDER_THIN);
headCellStyle.setBorderTop(CellStyle.BORDER_THIN);
headCellStyle.setFillForegroundColor(HSSFColor.LIGHT_CORNFLOWER_BLUE.index);
headCellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
cell.setCellStyle(headCellStyle);
Map colMap = colList.get(j);
String colCnName = String.valueOf(colMap.get("colcnname"));
excelHelpUtil.setCellValue(fileType, manuSheetNew, 0, j, colCnName);
}
if (list != null && list.size() > 0) {
int indx = 1;
for (Map dataMap : list) {
if (dataMap == null) {
continue;
}
final Row row = manuSheetNew.createRow(indx);
row.setHeightInPoints((short) 25);
for (int j = 0; j < colCount; j++) {
final Cell cell = row.createCell(j);
Map colMap = colList.get(j);
String colEnName = String.valueOf(colMap.get("colenname")).toLowerCase();
String dataType = String.valueOf(colMap.get("coldatatype"));
if ("1,2".contains(dataType)) {
contentCenterCellStyle.setAlignment(CellStyle.ALIGN_CENTER);
contentCenterCellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
contentCenterCellStyle.setWrapText(true);
contentFont.setFontHeightInPoints((short) 10);
contentFont.setFontName("微软雅黑");
contentCenterCellStyle.setFont(contentFont);
contentCenterCellStyle.setBorderBottom(CellStyle.BORDER_THIN);
contentCenterCellStyle.setBorderLeft(CellStyle.BORDER_THIN);
contentCenterCellStyle.setBorderRight(CellStyle.BORDER_THIN);
contentCenterCellStyle.setBorderTop(CellStyle.BORDER_THIN);
cell.setCellStyle(contentCenterCellStyle);
} else if ("3".equals(dataType)) {
contentRightCellStyle.setAlignment(CellStyle.ALIGN_RIGHT);
contentRightCellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
contentRightCellStyle.setWrapText(true);
contentFont.setFontHeightInPoints((short) 10);
contentFont.setFontName("微软雅黑");
contentRightCellStyle.setFont(contentFont);
contentRightCellStyle.setBorderBottom(CellStyle.BORDER_THIN);
contentRightCellStyle.setBorderLeft(CellStyle.BORDER_THIN);
contentRightCellStyle.setBorderRight(CellStyle.BORDER_THIN);
contentRightCellStyle.setBorderTop(CellStyle.BORDER_THIN);
cell.setCellStyle(contentRightCellStyle);
} else {
contentCellStyle.setAlignment(CellStyle.ALIGN_LEFT);
contentCellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
contentCellStyle.setWrapText(true);
contentFont.setFontHeightInPoints((short) 10);
contentFont.setFontName("微软雅黑");
contentCellStyle.setFont(contentFont);
contentCellStyle.setBorderBottom(CellStyle.BORDER_THIN);
contentCellStyle.setBorderLeft(CellStyle.BORDER_THIN);
contentCellStyle.setBorderRight(CellStyle.BORDER_THIN);
contentCellStyle.setBorderTop(CellStyle.BORDER_THIN);
cell.setCellStyle(contentCellStyle);
}
excelHelpUtil.setCellValue(fileType,
manuSheetNew, indx, j, String.valueOf(dataMap.get(colEnName)));
}
indx++;
}
}
exportWorkbook.write(byteArrayOut);
exportBytes = byteArrayOut.toByteArray();
resp.setStatus(200);
resp.setContentType("application/x-download;charset=utf8");
resp.addHeader("Content-Disposition", String.format("attachment; filename=\"%s\"",
URLEncoder.encode(fileName + "." + fileType, "UTF-8")));
resp.getOutputStream().write(exportBytes);
} catch (IOException e) {
e.printStackTrace();
}
}
原因分析:
- 大数据量导出应该使用poi 3.8以上提供的类SXSSFWorkbook,2007版 XSSFWorkbook不适用于数据量较大的数据导出。
- 创建样式对象时,应重复使用,不要每次填充cell时都新建一个样式对象。
解决方案:
/**
*
* @param req
* @param resp
* @param operParams
*/
@EcpGetMapping("/exportResultDetailForExcel")
public void exportResultDetailForExcel(final HttpServletRequest req, final HttpServletResponse resp, @RequestParam final String operParams) {
byte[] exportBytes = null;
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
SXSSFWorkbook exportWorkbook = null;
try {
JSONObject jsonObj = JSONObject.parseObject(operParams);
String fileName = String.valueOf(jsonObj.get("fileName"));
String fileType = String.valueOf(jsonObj.get("fileType"));
String compId = String.valueOf(jsonObj.get("compId"));
String modelId = String.valueOf(jsonObj.get("modelId"));
List<Map> colList = this.mainResultService
.findColListByModelId(compId, modelId);
String queryParamStr = String.valueOf(jsonObj.get("queryParam"));
Map queryParam = JSONUtil.fromJsonString(queryParamStr, Map.class);
Map<String, Object> queryResult = this.tAmModelMainResultService
.queryDetailList(queryParam);
List<Map> list = (List<Map>) queryResult.get("data");
exportWorkbook = new SXSSFWorkbook(2000);
exportWorkbook.setCompressTempFiles(false);
exportWorkbook.createSheet(fileName);
final CellStyle headCellStyle = exportWorkbook.createCellStyle();
final CellStyle contentCenterCellStyle = exportWorkbook.createCellStyle();
final Font headCellFont = exportWorkbook.createFont();
headCellStyle.setAlignment(CellStyle.ALIGN_CENTER);
headCellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
headCellStyle.setWrapText(true);
headCellFont.setFontHeightInPoints((short)10);
headCellFont.setFontName("微软雅黑");
headCellFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
headCellStyle.setFont(headCellFont);
headCellStyle.setBorderBottom(CellStyle.BORDER_THIN);
headCellStyle.setBorderLeft(CellStyle.BORDER_THIN);
headCellStyle.setBorderRight(CellStyle.BORDER_THIN);
headCellStyle.setBorderTop(CellStyle.BORDER_THIN);
headCellStyle.setFillForegroundColor(HSSFColor.LIGHT_CORNFLOWER_BLUE.index);
headCellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
final Font contentFont = exportWorkbook.createFont();
//居中
contentFont.setFontHeightInPoints((short) 10);
contentFont.setFontName("微软雅黑");
contentCenterCellStyle.setAlignment(CellStyle.ALIGN_CENTER);
contentCenterCellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
contentCenterCellStyle.setWrapText(true);
contentCenterCellStyle.setFont(contentFont);
contentCenterCellStyle.setBorderBottom(CellStyle.BORDER_THIN);
contentCenterCellStyle.setBorderLeft(CellStyle.BORDER_THIN);
contentCenterCellStyle.setBorderRight(CellStyle.BORDER_THIN);
contentCenterCellStyle.setBorderTop(CellStyle.BORDER_THIN);
//居右
final CellStyle contentRightCellStyle = exportWorkbook.createCellStyle();
contentRightCellStyle.setAlignment(CellStyle.ALIGN_RIGHT);
contentRightCellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
contentRightCellStyle.setWrapText(true);
contentRightCellStyle.setBorderBottom(CellStyle.BORDER_THIN);
contentRightCellStyle.setBorderLeft(CellStyle.BORDER_THIN);
contentRightCellStyle.setBorderRight(CellStyle.BORDER_THIN);
contentRightCellStyle.setBorderTop(CellStyle.BORDER_THIN);
contentFont.setFontHeightInPoints((short) 10);
contentFont.setFontName("微软雅黑");
contentRightCellStyle.setFont(contentFont);
//居左
final CellStyle contentCellStyle = exportWorkbook.createCellStyle();
contentCellStyle.setAlignment(CellStyle.ALIGN_LEFT);
contentCellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
contentCellStyle.setWrapText(true);
contentFont.setFontHeightInPoints((short) 10);
contentFont.setFontName("微软雅黑");
contentCellStyle.setFont(contentFont);
contentCellStyle.setBorderBottom(CellStyle.BORDER_THIN);
contentCellStyle.setBorderLeft(CellStyle.BORDER_THIN);
contentCellStyle.setBorderRight(CellStyle.BORDER_THIN);
contentCellStyle.setBorderTop(CellStyle.BORDER_THIN);
final Sheet manuSheetNew = exportWorkbook.getSheetAt(0);
manuSheetNew.setDefaultRowHeightInPoints((short) 25);
manuSheetNew.setDefaultColumnWidth(25);
final Row headRow = manuSheetNew.createRow(0); // 标题行
int colCount = colList.size();
for (int j = 0; j < colCount; j++) {
final Cell cell = headRow.createCell(j);
cell.setCellStyle(headCellStyle);
Map colMap = colList.get(j);
String colCnName = String.valueOf(colMap.get("colcnname"));
excelHelpUtil.setSXSSFCellValue(fileType, manuSheetNew, 0, j, colCnName);
}
if (list != null && list.size() > 0) {
int indx = 1;
for (Map dataMap : list) {
if (dataMap == null) {
continue;
}
final Row row = manuSheetNew.createRow(indx);
row.setHeightInPoints((short) 25);
for (int j = 0; j < colCount; j++) {
final Cell cell = row.createCell(j);
Map colMap = colList.get(j);
String colEnName = String.valueOf(colMap.get("colenname")).toLowerCase();
String dataType = String.valueOf(colMap.get("coldatatype"));
if ("1,2".contains(dataType)) {
cell.setCellStyle(contentCenterCellStyle);
} else if ("3".equals(dataType)) {
cell.setCellStyle(contentRightCellStyle);
} else {
cell.setCellStyle(contentCellStyle);
}
excelHelpUtil.setSXSSFCellValue(fileType,
manuSheetNew, indx, j, String.valueOf(dataMap.get(colEnName)));
}
indx++;
}
}
exportWorkbook.write(byteArrayOut);
exportBytes = byteArrayOut.toByteArray();
resp.setStatus(200);
resp.setContentType("application/x-download;charset=utf8");
resp.addHeader("Content-Disposition", String.format("attachment; filename=\"%s\"",
URLEncoder.encode(fileName + "." + fileType, "UTF-8")));
resp.getOutputStream().write(exportBytes);
} catch (IOException e) {
e.printStackTrace();
} finally{
if (exportWorkbook != null) {
try {
//删除磁盘上的文件
deleteSXSSFTempFiles(exportWorkbook);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* Returns a private attribute of a class
* @param containingClass The class that contains the private attribute to retrieve
* @param fieldToGet The name of the attribute to get
* @return The private attribute
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
public static Object getPrivateAttribute(final Object containingClass, final String fieldToGet) throws NoSuchFieldException, IllegalAccessException {
//get the field of the containingClass instance
Field declaredField = containingClass.getClass().getDeclaredField(fieldToGet);
//set it as accessible
declaredField.setAccessible(true);
//access it
Object get = declaredField.get(containingClass);
//return it!
return get;
}
/**
* 删除缓存文件 Deletes all temporary files of the SXSSFWorkbook instance.
* @param workbook
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
public static void deleteSXSSFTempFiles(final SXSSFWorkbook workbook) throws NoSuchFieldException, IllegalAccessException {
int numberOfSheets = workbook.getNumberOfSheets();
//iterate through all sheets (each sheet as a temp file)
for (int i = 0; i < numberOfSheets; i++) {
Sheet sheetAt = workbook.getSheetAt(i);
//delete only if the sheet is written by stream
if (sheetAt instanceof SXSSFSheet) {
SheetDataWriter sdw = (SheetDataWriter) getPrivateAttribute(sheetAt,"_writer");
//如果exportWorkbook.setCompressTempFiles(true);
//下一行代码就会报错
File f = (File) getPrivateAttribute(sdw,"_fd");
try {
f.delete();
} catch (Exception ex) {
//could not delete the file
}
}
}
}
说明:由于poi 3.8 没有 dispose()方法,所以需要写清除硬盘上存的文件,poi 3.8以上就可以直接使用。
exportWorkbook = new SXSSFWorkbook(2000);
就是将超过2000条的数据存入到电脑磁盘上,2000以内的数据放入缓存中,这样就可以减少内存的占用。创建样式的话,不需要放在for循环中,这样就解决问题了,36万的数据只需要10几秒就导出完成,提高了导出的性能。