前言
需求:用户在UI界面上选择想要导出的列,然后点击导出按钮,就能导出用户想要的数据。
效果展示
可能会产生的问题
1.如果同步到数据,接口很容易造成超时。
2.如果把数据一次性装载到内存里,很容易造成OOM与GC。
3.如果数据量太大sql语句查询也会很慢。
以下是批量导出解决的方案,如有更好的办法请支持。
1.在pom文件中导入poi的包
<poi.version>4.1.2</poi.version>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>${poi.version}</version>
</dependency>
2.查询导出数据条数
select count(id) from you_table_name;
3.通过总条数计算需要进行几次分页查询
每次查询20w条数据,每次向excel输出1w条,减轻对内存的压力,已经减少用户等待时长。
int pageSize = 200000; // 每页显示的记录数
if (totalCount > 200000) {
int totalPages = (int) Math.ceil((double) totalCount / pageSize);
for (int page = 1; page <= totalPages; page++) {
int offset = (page - 1) * pageSize;
//组装SQL语句
Map<String, Object> sqlParamsMap = getDownloadOrgDataLimit(filters, filedList, offset, pageSize);
String sql = (String) sqlParamsMap.get("sql");
//查询数据
List<Map<String, String>> dataset = listOfSQL(sql.toString());
int totalSize = dataset.size();
//定义每次只输出1万条
int batchSize = 10000;
for (int i = 0; i < totalSize; i += batchSize) {
int endIndex = Math.min(i + batchSize, totalSize);
//批次
List batch = Arrays.asList(dataset.toArray()).subList(i, endIndex);
Iterator<Map> it = batch.iterator();
while (it.hasNext()) {
Row row = sheet.createRow(index);
//循环输出数据...
}
}
}
}
4.为了让UI界面更直观的观察到导出进度可以加一个进度条
//导出的进度条信息
double dPercent=(double)index/totalCount; //将计算出来的数转换成double
int percent=(int)(dPercent*100); //再乘上100取整
request.getSession().setAttribute("curCount", index);
request.getSession().setAttribute("percent", percent); //比如这里是50
request.getSession().setAttribute("percentText",percent+"%");//这里是50%
//...也可以增加当前导出条数,剩余条数
5.前端需要频繁获取进度条进度
//下载数据
downloadOrgData(){
//将刷新进度条状态打开
this.downloadFlag=true;
//执行进度条刷新方法
this.flushProgress();
axios.get("/org/orgDataDownload/",{
params:{
filedList: this.filedList.toString()
},
timeout:600000,
responseType:'blob'
}).then(res=>{
var blob = new Blob([res.data])
let fileName = decodeURI(res.headers['content-disposition'].split("filename= ")[1])
var downloadElement = document.createElement('a');
var href = window.URL.createObjectURL(blob); //创建下载的链接
downloadElement.href = href;
downloadElement.download = fileName; //下载后文件名
document.body.appendChild(downloadElement);
downloadElement.click(); //点击下载
document.body.removeChild(downloadElement); //下载完成移除元素
window.URL.revokeObjectURL(href); //释放掉blob对象
this.downloadFlag=false; //关闭状态
})
},
flushProgress(){
$.ajaxSettings.async = false;
//刷新进度条
if (this.downloadFlag){
window.setTimeout(function(){
var timer=window.setInterval(function(){
axios.get("/org/flushProgress").then(res=>{
let progress = res.data.data.map.percent;
const progressBar = document.querySelector('.progress');
const progressText = document.querySelector('.progress-text');
if (progress===null){
progressText.innerText = `数据准备中`;
}else if (progress===100){
progressText.innerText = `文档下载中`;
} else {
progressBar.style.width = `${progress}%`;
progressText.innerText = `${progress}%`;
}
if(res.data.data.map.percent=="100"){
window.clearInterval(timer);
}
})
},800);
},800);
}
$.ajaxSettings.async = true;
},
其他方案:如果请求超时时间太长,配置也不能修改的情况下,可以使用异步导出。
1.导出成功后将excel上传到OSS,用户直接下载OSS中文件。
2.导出成功后将消息发送到mq中,mq消费消息时通知用户下载excel.