JAVA大数据量导出问题
1问题背景
- 最近公司在做一个导出的功能,是这样子的,公司内部有很多套系统,现在需要开发了一个门户系统,希望能连接各个系统,在门户系统上希望可以给其他子系统增加一个导出功能,支持对其他各个系统的数据导出。
- 由此带来几个问题:
- 因为是生产系统,所以只能对需要使用该功能的系统 将其数据源配置在jboss 上(因种种安全上的考量,JDBC的直连方式不被允许)
- 只能对其他生产系统进行查操作,禁止一切修改
- 查询的sql语句由消费系统来写,由此带来的性能问题等引发了大数据量查询 不能造成本系统 服务压力,已经使用者系统压力的问题解决
2问题解决
本篇主要对大数量查询导出问题作出记录,jboss数据源获取方式 和使用不做赘述。
1 分页查询
因为不确定sql 段落内容 和 数据量情况,采用每100条数据为一次查询,然后一万条数据写入一张表。
//每页最大数
int rowCount = 100;
int pageCount ;
int excelCount = 10000;
int sign = 0;
int searchCount ;
//
if (count % excelCount == 0) {
pageCount = count / excelCount;
} else {
pageCount = count / excelCount + 1;
}
if (count % rowCount == 0) {
searchCount = count / rowCount;
} else {
searchCount = count / rowCount + 1;
}
//列头
List<String> headers = new ArrayList<>();
//需要压缩的文件名
String zipName = StringFormateUtil.getDateTime() + "";
String tempPath = ReportTaskUtil.PATH_FILE + "/" + zipName;
//判断临时文件夹
File file = new File(tempPath);
//新建一个临时文件夹
ReportTaskUtil.judeDirExists(file);
Map<String, Object> result = new HashMap<>();
result.put("tempPath", tempPath);
result.put("zipName", zipName);
response.getData().setRows(result);
for (int i = 0; i < pageCount; i++) {
if(Thread.currentThread().isInterrupted()){
throw new InterruptedException("线程强制中断");
}
//每一页对应一个 excel
ExcelUtil07 excelUtil07 = null;
//查 100 条每次,防止 查询过大 造成 系统压力
for (int m = 0; m <100 ; m++) {
if(searchCount==0){
break;
}
searchCount--;
String exeSql2 = String.format(" select * from (%s) a limit %d, %d", sql, sign, rowCount);
try (PreparedStatement preparedStatement2 = conn.prepareStatement(exeSql2);
ResultSet rs2 = preparedStatement2.executeQuery();) {
//查询列集合第一次
if(firstQuery){
//获取查询的字段
ResultSetMetaData rsmd = rs2.getMetaData();
headers = getHeaders(rsmd);
firstQuery = false;
//excel 格式 名称 等设置
excelUtil07 = new ExcelUtil07(i+"_"+reportName, headers);
}
sign += rowCount;
// 行的集合
List<List<String>> rows = new ArrayList<>();
while (rs2.next()) {
List<String> v = new ArrayList<>();
for (int j = 0; j < headers.size(); j++) {
v.add(rs2.getString(j + 1));
}
rows.add(v);
}
//每一百行 写入一次
if(!excelUtil07.create07Xlsx(rows)){
LOGGER.error("07excel 生成失败");
response.setSuccess(false);
response.setErrorMsg("07excel 生成失败");
return response;
}
} catch (Exception e) {
LOGGER.error(" 执行异常", e);
response.setSuccess(false);
response.setErrorMsg(" 执行异常");
break;
}
}
excelUtil07.export(tempPath);
firstQuery = true;
}
2 excel 内存问题
为了尽可能减少内存的开销,防止同一时间存在太多对象,我们这里使用SXSSFWorkbook 类,该类接收一个int 类型的 参数,从源码可以知道 此参数可以控制内存中的对象,多余数量的对象会存在磁盘上
@param rowAccessWindowSize the number of rows that are kept in memory until flushed out, see above.
package com.suning.bi.util;
import com.suning.bi.entity.ReportResponse;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
/**
* @author 17103349
*/
public class ExcelUtil07 {
private static Logger LOGGER = LoggerFactory.getLogger(ExcelUtil07.class);
private SXSSFWorkbook sxssfWorkbook;
private SXSSFCell sxssfCell;
private SXSSFSheet sheet;
private SXSSFRow sxssfRow;
private String fileName;
private int numStart = 0;
/**
* @param fileName 文件名
* @param titles 标题头
*/
public ExcelUtil07(String fileName, List<String> titles) {
this.fileName = fileName;
//设置Workbook 超出100 将缓存在磁盘,保证 row最多有100在内存中,防止内存开销过大
sxssfWorkbook = new SXSSFWorkbook(100);
sxssfCell = null;
sxssfRow = null;
sheet = sxssfWorkbook.createSheet(fileName);
CellStyle style = sxssfWorkbook.createCellStyle();
style.setFillForegroundColor(HSSFColor.SEA_GREEN.index);
style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
style.setAlignment(HorizontalAlignment.CENTER); // 居中
style.setBorderBottom(HSSFCellStyle.BORDER_THIN);// 下边框
style.setBorderLeft(HSSFCellStyle.BORDER_THIN);// 左边框
style.setBorderRight(HSSFCellStyle.BORDER_THIN);// 右边框
style.setBorderTop(HSSFCellStyle.BORDER_THIN);// 上边框
Font font = sxssfWorkbook.createFont();
font.setFontName("仿宋_GB2312");
//粗体显示
font.setBold(true);
//字体大小
font.setFontHeightInPoints((short) 12);
style.setFont(font);
//设置自动换行
style.setWrapText(true);
sxssfRow = sheet.createRow(0);
//设置头
for (int a = 0; a < titles.size(); a++) {
sheet.setColumnWidth(a, 3766);
SXSSFCell sxssfCell = sxssfRow.createCell(a);
sxssfCell.setCellValue(titles.get(a));
sxssfCell.setCellStyle(style);
}
}
/**
* 07版本excel 生成 (poi 3.8以后 有临时文件)
*
* @param dataLists 数据集
* @return
*/
public boolean create07Xlsx(List<List<String>> dataLists) {
try {
int size = dataLists.size();
for (int a = 1; a <=size; a++) {
sxssfRow = sheet.createRow(a+numStart);
List<String> data = dataLists.get(a - 1);
int cell = 0;
for (int i = 0; i < data.size(); i++) {
Object valueObject = data.get(i);
sxssfCell = sxssfRow.createCell(cell);
sxssfCell.setCellValue(valueObject == null ? "" : valueObject.toString());
sxssfCell.getCellStyle().setWrapText(true);
cell++;
}
//数据清理
data.clear();
}
numStart+=size;
//数据清理 回收对象
dataLists.clear();
//设置空引用
} catch (Exception e) {
LOGGER.error("xlsx 文件{}生成失败", fileName, e);
return false;
}
return true;
}
/**
* 导出正式文件
* @return
*/
public boolean export(String path){
try {
FileOutputStream fos = new FileOutputStream(path + "/" + fileName+ReportTaskUtil.XLSX_TYPE);
sxssfWorkbook.write(fos);
fos.close();
sxssfWorkbook.dispose();
} catch (IOException e) {
LOGGER.error("xlsx 正式文件{}生成失败", fileName, e);
return false;
}
return true;
}
}
3 生成的多个文件,对其进行打包处理上传ftp服务器
public boolean fileToZip(String sourceFilePath, String zipFilePath, String fileName) {
boolean flag = false;
File sourceFile = new File(sourceFilePath);
if (sourceFile.exists() == false) {
LOGGER.info("待压缩的文件目录:" + sourceFilePath + "不存在.");
sourceFile.mkdir(); // 新建目录
}
File zipFile = new File(zipFilePath + "/" + fileName + ".zip");
if (zipFile.exists()) {
LOGGER.info(zipFilePath + "目录下存在名字为:" + fileName + ".zip" + "打包文件.");
} else {
File[] sourceFiles = sourceFile.listFiles();
if (null == sourceFiles || sourceFiles.length < 1) {
LOGGER.info("待压缩的文件目录:" + sourceFilePath + "里面不存在文件,无需压缩.");
} else {
try (FileOutputStream fos = new FileOutputStream(zipFile); ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos));) {
byte[] bufs = new byte[1024 * 10];
for (int i = 0; i < sourceFiles.length; i++) {
String name = sourceFiles[i].getName();
ZipEntry zipEntry = new ZipEntry(name);
zos.putNextEntry(zipEntry);
try (FileInputStream fis = new FileInputStream(sourceFiles[i]); BufferedInputStream bis = new BufferedInputStream(fis, 1024 * 10);) {
int read = 0;
while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
zos.write(bufs, 0, read);
}
} catch (Exception e) {
flag = false;
LOGGER.error("IO异常", e);
}
}
} catch (IOException e) {
flag = false;
LOGGER.error("IO异常", e);
}
flag = true;
}
}
return flag;
}
3 遗留问题
这里有个遗留问题,因为笔者的任务序列 使用 队列 做的,笔者在有想过在遇到 sql 执行 效率和性能问题时,强制 终止线程 ,但是 对于 stop 和 interrupt 方式 没有考虑好,interrupt 不能保证 线程 能立刻马上终止,stop则会遇到线程不安全和 资源释放的问题,现在还在纠结 采用的方案。
其次,因为其他生产系统不受控制,对于其他系统的数据库 压力问题 ,在执行过程中不可控 ,也比较糟心。
4 最后
写到这里,基本就记录完毕了,笔者不是什么大神。。很普通,有什么不对和改进的地方,希望能和大家一起探讨学习