java?Export大量數(shù)據(jù)導(dǎo)出和打包
項(xiàng)目需求
導(dǎo)出生成大批量數(shù)據(jù)的文件,一個Excel中最多存有五十萬條數(shù)據(jù),查詢多余五十萬的數(shù)據(jù)寫多個Excel中。導(dǎo)出完成是生成的多個Excel文件打包壓縮成zip,而后更新導(dǎo)出記錄中的壓縮文件路徑。
大數(shù)據(jù)量文件一般采用異步生成文件,導(dǎo)出時首先授權(quán)生成一個流水號,而后將數(shù)據(jù)攜帶流水號請求導(dǎo)出接口。
拋開實(shí)際業(yè)務(wù),做成一個比較公共的導(dǎo)出功能。
參數(shù)說明
{ "className": "ValideData", //導(dǎo)出的數(shù)據(jù)的實(shí)體類,類中有別名和順序相關(guān)的注解 "createUser": "", //操作人 "downLoadNo": "202203181504732568468066304", //下載流水號 "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)出操作 。
主要代碼
邏輯說明:
- 導(dǎo)出前將請求參數(shù)更新到導(dǎo)出記錄中。
- 類加載器加載需要導(dǎo)出數(shù)據(jù)的實(shí)體類
- 設(shè)置一個數(shù)據(jù)量指針,記錄到每個文件的數(shù)據(jù)量
- 達(dá)到閾值時指定文件寫出到磁盤并清緩。
- 重置數(shù)據(jù)量指針,新增一條文件記錄(循環(huán))
- 數(shù)據(jù)量指針未到閾值時但數(shù)據(jù)已經(jīng)查詢完成---->>寫入剩余數(shù)據(jù)
- 查詢該流水號的所有文件記錄
- 壓縮文件并返回壓縮文件地址
- 更新到導(dǎo)出記錄中
主流程
public void bigDataExport(PortDto dto) throws Exception { long start = System.currentTimeMillis(); log.info("開始導(dǎo)出,批次號:<{}>, 開始時間:{}", 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("批次號:<{}>準(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é)束,批次號:<{}>, 結(jié)束時間:{}, 耗時:{}", dto.getDownLoadNo(), DateTime.of(end), DateUtil.formatBetween(end - start)); } catch (Exception e) { log.info("批次號<{}>導(dǎo)出異常:", dto.getDownLoadNo(), e); throw new BusinessException(""); } finally { log.info("批次號<{}>生成文件結(jié)束,準(zhǔn)備壓縮文件,修改狀態(tài)", dto.getDownLoadNo()); //合并文件到導(dǎo)出文件記錄主表 //當(dāng)只有一個文件記錄時直接更新主表文件地址 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("批次號<{}>更新下載記錄表文件地址,修改狀態(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ù)
/** * 查詢redis數(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ù)器來限制同一時間最大的導(dǎo)出請求,使用aop在申請流水號時計(jì)數(shù)器+1,導(dǎo)出完成或者異常時隊(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)出打包的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot基于AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源動態(tài)切換
本文主要介紹了SpringBoot基于AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源動態(tài)切換,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05Spring?Boot實(shí)現(xiàn)分布式系統(tǒng)中的服務(wù)發(fā)現(xiàn)和注冊(最新推薦)
在本文中,我們深入探討了Spring?Boot如何實(shí)現(xiàn)分布式系統(tǒng)中的服務(wù)發(fā)現(xiàn)和注冊,我們使用Eureka作為服務(wù)注冊中心,Ribbon作為負(fù)載均衡器,Hystrix作為熔斷器,成功地實(shí)現(xiàn)了服務(wù)發(fā)現(xiàn)、服務(wù)注冊、負(fù)載均衡和服務(wù)熔斷等功能,需要的朋友參考下吧2023-06-06Springboot并發(fā)調(diào)優(yōu)之大事務(wù)和長連接
這篇文章主要介紹了Springboot并發(fā)調(diào)優(yōu)之大事務(wù)和長連接,重點(diǎn)分享長事務(wù)以及長連接導(dǎo)致的并發(fā)排查和優(yōu)化思路和示例,具有一定的參考價值,感興趣的可以了解一下2022-05-05Java Swing JSlider滑塊的實(shí)現(xiàn)示例
這篇文章主要介紹了Java Swing JSlider滑塊的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12Java查找不重復(fù)無序數(shù)組中是否存在兩個數(shù)字的和為某個值
今天小編就為大家分享一篇關(guān)于Java查找不重復(fù)無序數(shù)組中是否存在兩個數(shù)字的和為某個值,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01如何使用攔截器獲取請求的入?yún)⒉⑵滢D(zhuǎn)化為Java對象詳解
這篇文章主要介紹了如何使用攔截器獲取請求的入?yún)⒉⑵滢D(zhuǎn)化為Java對象的相關(guān)資料,文中介紹了兩種實(shí)現(xiàn)的方法,并給出了詳細(xì)的代碼示例,需要的朋友可以參考下2025-02-02Java實(shí)現(xiàn)布隆過濾器的幾種方式總結(jié)
這篇文章給大家總結(jié)了幾種Java實(shí)現(xiàn)布隆過濾器的方式,手動硬編碼實(shí)現(xiàn),引入Guava實(shí)現(xiàn),引入hutool實(shí)現(xiàn),通過redis實(shí)現(xiàn)等幾種方式,文中有詳細(xì)的代碼和圖解,需要的朋友可以參考下2023-07-07