java?Export大量數(shù)據(jù)導(dǎo)出和打包
項(xiàng)目需求
導(dǎo)出生成大批量數(shù)據(jù)的文件,一個(gè)Excel中最多存有五十萬(wàn)條數(shù)據(jù),查詢多余五十萬(wàn)的數(shù)據(jù)寫多個(gè)Excel中。導(dǎo)出完成是生成的多個(gè)Excel文件打包壓縮成zip,而后更新導(dǎo)出記錄中的壓縮文件路徑。
大數(shù)據(jù)量文件一般采用異步生成文件,導(dǎo)出時(shí)首先授權(quán)生成一個(gè)流水號(hào),而后將數(shù)據(jù)攜帶流水號(hào)請(qǐng)求導(dǎo)出接口。
拋開實(shí)際業(yè)務(wù),做成一個(gè)比較公共的導(dǎo)出功能。
參數(shù)說(shuō)明
{
"className": "ValideData", //導(dǎo)出的數(shù)據(jù)的實(shí)體類,類中有別名和順序相關(guān)的注解
"createUser": "", //操作人
"downLoadNo": "202203181504732568468066304", //下載流水號(hào)
"fileName": "機(jī)卡綁定", //文件名 fileName+HHmmssSSS.xlsx
"keys": [ //redis key的數(shù)據(jù),分批獲取數(shù)據(jù)
],
"remark": "機(jī)卡綁定", //備注(不關(guān)注)
"type": "機(jī)卡綁定" //導(dǎo)出類型(不關(guān)注)
}坐標(biāo)
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.21</version> </dependency>
注:拋開導(dǎo)出前的參數(shù)校驗(yàn),只關(guān)注導(dǎo)出操作 。
主要代碼
邏輯說(shuō)明:
- 導(dǎo)出前將請(qǐng)求參數(shù)更新到導(dǎo)出記錄中。
- 類加載器加載需要導(dǎo)出數(shù)據(jù)的實(shí)體類
- 設(shè)置一個(gè)數(shù)據(jù)量指針,記錄到每個(gè)文件的數(shù)據(jù)量
- 達(dá)到閾值時(shí)指定文件寫出到磁盤并清緩。
- 重置數(shù)據(jù)量指針,新增一條文件記錄(循環(huán))
- 數(shù)據(jù)量指針未到閾值時(shí)但數(shù)據(jù)已經(jīng)查詢完成---->>寫入剩余數(shù)據(jù)
- 查詢?cè)摿魉?hào)的所有文件記錄
- 壓縮文件并返回壓縮文件地址
- 更新到導(dǎo)出記錄中
主流程
public void bigDataExport(PortDto dto) throws Exception {
long start = System.currentTimeMillis();
log.info("開始導(dǎo)出,批次號(hào):<{}>, 開始時(shí)間:{}", dto.getDownLoadNo(), DateUtil.now());
//修改導(dǎo)出記錄
LambdaUpdateWrapper<PortDto> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(PortDto::getDownLoadNo, dto.getDownLoadNo());
//生成導(dǎo)出記錄
int row = this.baseMapper.update(dto, updateWrapper);
if (row > 0) {
log.info("批次號(hào):<{}>準(zhǔn)備生成文件", dto.getDownLoadNo());
try {
Iterator<String> iterator = keys.iterator();
Workbook workbook = null;
ExportParams params = new ExportParams();
//加載導(dǎo)出數(shù)據(jù)實(shí)體類
Class<?> aClass = Class.forName(entityBasePackage + dto.getClassName());
int element = 0;
while (iterator.hasNext()) {
String key = iterator.next();
Collection<?> list = getList(key, aClass);
element += list.size();
workbook = ExcelExportUtil.exportBigExcel(params, aClass, list);
//文件數(shù)據(jù)達(dá)到閾值
if (element >= maxDataCount) {
String fileName = dto.getFileName() + "_" + DateUtil.format(new Date(),
"HHmmssSSS") + ".xlsx";
ExcelExportUtil.closeExportBigExcel();
FileOutputStream fos =
new FileOutputStream(fileProp.getPath().getPath() + fileName);
workbook.write(fos);
fos.close();
element = 0;
//更新地址
Map<String, Object> map = new HashMap<>();
map.put("downloadNo", dto.getDownLoadNo());
map.put("filePath", fileProp.getPath().getPath() + fileName);
map.put("createTime", new Date());
this.baseMapper.insertPathRecord(map);
log.info("文件寫入完成,文件名:{}", fileName);
continue;
}
iterator.remove();
}
//寫入剩余文件
if (element != 0) {
String fileName = dto.getFileName() + "_" + DateUtil.format(new Date(),
"HHmmssSSS") + ".xlsx";
ExcelExportUtil.closeExportBigExcel();
FileOutputStream fos = new FileOutputStream(fileProp.getPath().getPath() + fileName);
workbook.write(fos);
fos.close();
element = 0;
//更新地址
Map<String, Object> map = new HashMap<>();
map.put("downloadNo", dto.getDownLoadNo());
map.put("filePath", fileProp.getPath().getPath() + fileName);
map.put("createTime", new Date());
this.baseMapper.insertPathRecord(map);
log.info("文件寫入完成,文件名:{}", fileName);
}
long end = System.currentTimeMillis();
log.info("導(dǎo)出結(jié)束,批次號(hào):<{}>, 結(jié)束時(shí)間:{}, 耗時(shí):{}", dto.getDownLoadNo(), DateTime.of(end),
DateUtil.formatBetween(end - start));
} catch (Exception e) {
log.info("批次號(hào)<{}>導(dǎo)出異常:", dto.getDownLoadNo(), e);
throw new BusinessException("");
} finally {
log.info("批次號(hào)<{}>生成文件結(jié)束,準(zhǔn)備壓縮文件,修改狀態(tài)", dto.getDownLoadNo());
//合并文件到導(dǎo)出文件記錄主表
//當(dāng)只有一個(gè)文件記錄時(shí)直接更新主表文件地址
List<PortDto> recordList = exportDao.getPathRecord(dto);
if (recordList.size() > 1) {
//zipPath
dto.setFilePath(zcat(dto, recordList));
} else {
//xlsxPath
dto.setFilePath(recordList.size()==0? "":recordList.get(0).getFilePath());
}
updateWrapper.clear();
updateWrapper.set(PortDto::getFilePath, dto.getFilePath());
updateWrapper.set(PortDto::getSuccessTime, new Date());
updateWrapper.set(PortDto::getStatus, "1");
updateWrapper.eq(PortDto::getDownLoadNo, dto.getDownLoadNo());
this.baseMapper.update(null, updateWrapper);
log.info("批次號(hào)<{}>更新下載記錄表文件地址,修改狀態(tài)成功", dto.getDownLoadNo());
}
}
}文件壓縮
/**
* 多文件壓縮
* @param dto 導(dǎo)出信息
* @Param recordList 文件路徑
* @return void
* @throws
* @author Surpass
* @date 2022/3/17 9:59
*/
private String zcat(PortDto dto, List<PortDto> recordList) throws Exception {
String fileName = dto.getFileName() + "_" + DateUtil.format(new Date(), "HHmmssSSS") + ".zip";
String zipPath = fileProp.getPath().getPath() + fileName;
Archiver archiver = CompressUtil.createArchiver(
CharsetUtil.CHARSET_UTF_8,
ArchiveStreamFactory.ZIP,
new File(zipPath)
);
for (PortDto portDto : recordList) {
archiver.add(FileUtil.file(portDto.getFilePath()));
}
archiver.finish();
archiver.close();
return zipPath;
}查詢數(shù)據(jù)
/**
* 查詢r(jià)edis數(shù)據(jù)
* @param key
* @param cls
* @return java.util.Collection<?>
* @throws
* @author Surpass
* @date 2022/3/18 15:51
*/
private Collection<?> getList(String key, Class<?> cls) {
List<String> list = redis.getList(key);
return list.stream().map(item -> JSONObject.parseObject(item, cls)).collect(Collectors.toList());
}補(bǔ)充
導(dǎo)出還設(shè)置了隊(duì)列計(jì)數(shù)器來(lái)限制同一時(shí)間最大的導(dǎo)出請(qǐng)求,使用aop在申請(qǐng)流水號(hào)時(shí)計(jì)數(shù)器+1,導(dǎo)出完成或者異常時(shí)隊(duì)列計(jì)數(shù)器-1。導(dǎo)出完成后根據(jù)操作人發(fā)送郵件通知導(dǎo)出結(jié)果。
以上就是java Export大量數(shù)據(jù)導(dǎo)出和打包的詳細(xì)內(nèi)容,更多關(guān)于java Export數(shù)據(jù)導(dǎo)出打包的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在Java Spring框架中實(shí)現(xiàn)BLOB類型的附件下載的詳細(xì)步驟
在Java Spring框架中實(shí)現(xiàn)BLOB(Binary Large Object)類型的附件下載,通常涉及到幾個(gè)關(guān)鍵步驟,下面將詳細(xì)介紹這些步驟,并結(jié)合具體的代碼示例來(lái)說(shuō)明如何在Spring Boot應(yīng)用程序中實(shí)現(xiàn)這一功能,需要的朋友可以參考下2025-10-10
Java Collection 移除元素方法及注意事項(xiàng)
這篇文章主要介紹了Java Collection 移除元素方法及注意事項(xiàng),通過(guò)一個(gè)簡(jiǎn)單實(shí)例給大家講解,需要的朋友可以參考下2020-01-01
Java基于Spire.XLS實(shí)現(xiàn)從Excel到PDF的發(fā)票自動(dòng)化生成
隨著業(yè)務(wù)數(shù)字化進(jìn)程的不斷推進(jìn),許多企業(yè)開始嘗試用自動(dòng)化手段替代手工開票,本文將基于 Spire.XLS for Java 介紹如何快速實(shí)現(xiàn)從 Excel 到 PDF的發(fā)票生成自動(dòng)化方法,感興趣的小伙伴可以了解下2025-06-06
java實(shí)現(xiàn)壓縮字符串和java字符串過(guò)濾
這篇文章主要介紹了java實(shí)現(xiàn)壓縮字符串和java字符串過(guò)濾,需要的朋友可以參考下2014-04-04
SpringBoot監(jiān)控所有線程池的四種解決方案及代碼案例
這篇文章介紹了四種監(jiān)控Spring Boot中所有線程池的解決方案,并提供了代碼案例,推薦使用自動(dòng)發(fā)現(xiàn)機(jī)制來(lái)監(jiān)控所有線程池,需要的朋友可以參考下2025-12-12
IDEA調(diào)試功能使用總結(jié)(step?over/step?into/force?step?into/step?o
本文主要介紹了IDEA調(diào)試功能使用總結(jié)(step?over/step?into/force?step?into/step?out),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Java Class.forName()用法和newInstance()方法原理解析
這篇文章主要介紹了Java Class.forName()用法和newInstance()方法原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
在SpringBoot中使用@Value注解來(lái)設(shè)置默認(rèn)值的方法
Spring Boot提供了一種使用注解設(shè)置默認(rèn)值的方式,即使用 @Value 注解,下面這篇文章主要給大家介紹了關(guān)于如何在SpringBoot中使用@Value注解來(lái)設(shè)置默認(rèn)值的相關(guān)資料,需要的朋友可以參考下2023-10-10

