问题:报表系统导出几十万大数据量会导致系统卡死,需要进行优化
解决方案:1、异步处理 2、分批处理 3、分文件处理(暂时没做)
在springboot项目中,实现异步处理特别简单,加两个注解(@EnableAsync、@Async)就完事儿了,在传统的web项目中,实现异步处理有点点复杂。
xmlns:task="http://www.springframework.org/schema/task"
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
TIPS:调用方法和异步方法不能在同一个类。
开始我考虑导出CSV(Comma-Separated Values)文件,如果不要求格式、样式、公式等等,会比POI快很多,现在项目使用的是POI的XSSF,后面又了解到POI的HSSF和SXSSF。此时,我在想这三者有什么关系和区别?
HSSF:Excel97-2003版本,扩展名为.xls。一个sheet最大行数65536,最大列数256。
XSSF:Excel2007版本开始,扩展名为.xlsx。一个sheet最大行数1048576,最大列数16384。
SXSSF:是在XSSF基础上,POI3.8版本开始提供的支持低内存占用的操作方式,扩展名为.xlsx。
Excel版本兼容性是向下兼容。
重点讲一下SXSSF,因为是今天要使用的技术:
SXSSF扩展自XSSF,用于当非常大的工作表要导出且内存受限制的时候。SXSSF占用很少的内存是因为它限制只能访问滑动窗口的数据,而XSSF可以访问文档中所有的数据。那些不在滑动窗口中的数据是不能访问的,因为它们已经被写到磁盘上了。
我们可以通过SXSSFWorkbook workbook = new SXSSFWorkbook(int windowSize);设置滑动窗口大小,默认是100。如果设置为-1,则表示不限,没有记录被自动刷新到磁盘,除非你手动调用flushRow()刷新。当通过sheet.createRow();创建新行时,总的行数可能会超过窗口大小,这个时候行号最低的那行会被刷新到磁盘而且不能通过getRow()访问。
我们也可以通过sheet.setRandomAccessWindowSize(int windowSize);设置每个工作表的窗口大小。
注意事项:SXSSF会产生临时文件,必须明确清理,调用workbook.dispose();方法。
package com.km.util;
import com.alibaba.fastjson.JSONObject;
import com.km.entity.DataExport;
import com.km.entity.HospDrugSales;
import com.km.entity.RequestData;
import com.km.service.HandleExcelDataService;
import org.apache.log4j.Logger;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class ExportExcelUtils {
protected static Logger logger = Logger.getLogger(ExportExcelUtils.class);
private static ThreadLocal startTime = new ThreadLocal<>();
public static void exportExcel(Map data, List> list, RequestData requestData){
logger.info("导出数据开始,导出数量:" + list.size());
startTime.set(System.currentTimeMillis());
OutputStream out = null;
SXSSFWorkbook workbook = null;
try {
ArrayList
DROP TABLE IF EXISTS `data_export`;
CREATE TABLE `data_export` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` varchar(10) DEFAULT NULL COMMENT '用户Id',
`user_name` varchar(10) DEFAULT NULL COMMENT '用户名称',
`export_module` varchar(20) DEFAULT NULL COMMENT '数据模块',
`export_param` varchar(500) DEFAULT NULL COMMENT '数据参数',
`export_address` varchar(100) DEFAULT NULL COMMENT '数据地址',
`status` tinyint(3) DEFAULT '0' COMMENT '导出状态 0:未导出 1:已导出 2:导出错误',
`export_amount` varchar(10) DEFAULT '0' COMMENT '导出数量',
`export_used_time` varchar(20) DEFAULT NULL COMMENT '导出耗时,单位:秒',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`ext_fields` VARCHAR(500) COMMENT '扩展字段',
PRIMARY KEY (`id`),
KEY `user_id`(`user_id`),
KEY `status`(`status`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='报表数据记录表';
地址1:/project_name
地址2:/home/apache-tomcat-7.0.77/project_name
地址3:/home/apache-tomcat-7.0.77/webapps/project_name/
我将文件放在了webapps的excel目录:
2.1、HTML
下载
2.2、JS组装Form表单
function download(id, path){
var form = $("");
form.attr("action", "download.html");
form.attr("method", "post");
var idInput = $("");
var pathInput = $("");
idInput.attr("value", id);
pathInput.attr("value", path);
form.append(pathInput);
form.append(idInput);
form.appendTo("body");
form.hide();
form.submit();
}
2.3、后台代码
/**
* 下载
* @param id
* @param path
* @param response
*/
@RequestMapping("/download")
public void download(String id, String path, HttpServletResponse response){
DataExport dataExport = new DataExport();
File file = new File(path);
dataExport.setStatus(Byte.valueOf("1"));
if (file.exists()){
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())){
String fileName = file.getName();
byte[] buffer = new byte[inputStream.available()];
inputStream.read(buffer);
response.reset();
response.addHeader("Content-Disposition", "attachment; filename=" + new String(fileName.getBytes("utf-8"), "iso-8859-1"));
response.addHeader("Content-Length", String.valueOf(file.length()));
response.setContentType("application/octet-stream");
outputStream.write(buffer);
} catch (FileNotFoundException e) {
logger.error("FileNotFoundException, 文件路径:" + path + ", exception message: " + e.getMessage());
dataExport.setStatus(Byte.valueOf("2"));
} catch (IOException e) {
logger.error("IOException, 文件路径:" + path + ", exception message: " + e.getMessage());
dataExport.setStatus(Byte.valueOf("2"));
}
}
dataExport.setId(Long.valueOf(id));
reportDownloadService.updateDownloadStatus(dataExport);
}