AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

什么,你的EasyExcel導出一萬條數據就OOM了?

來源: 責編: 時間:2024-05-16 09:10:29 155觀看
導讀前言前段時間在做一個導出的功能,本以為是平平無奇的一個功能。就用公司內部的一個導出工具類三下五除二就寫完了,做法是直接查全量數據,然后直接往Excel里寫。一開始沒多少數據也沒什么問題,但是當數據量逐漸多了起來后,

前言

前段時間在做一個導出的功能,本以為是平平無奇的一個功能。就用公司內部的一個導出工具類三下五除二就寫完了,做法是直接查全量數據,然后直接往Excel里寫。一開始沒多少數據也沒什么問題,但是當數據量逐漸多了起來后,達到一萬多條,導出的時候就會報OOM。然后我就換成了阿里開源的EasyExcel,但是導出的時候也不太穩定,偶爾也會OOM。所以應該是數據量太大了,在寫入的時候把內存占滿了。然后我就放棄了查全量數據一次性寫入Excel的做法,采用分頁查詢,分批次寫入Excel的方式,果然不會出現OOM了。QIf28資訊網——每日最新資訊28at.com

雖然這種方式不會出現OOM,但是每次導出都寫一遍重復的代碼著實有點麻煩,所以結合自己平時的使用場景,封裝了一個EasyExcel的導出工具類,這樣只要在分頁查詢的基礎上寫少量的代碼,就可以實現分批次寫入Excel,簡化代碼的編寫并且解決OOM的問題。QIf28資訊網——每日最新資訊28at.com

實現

@Slf4jpublic abstract class EasyExcelExport<T, S> {    /**     * EasyExcel導出Excel表格,每個sheet默認最大10萬條數據     *     * @param fileName  excel文件前綴名     * @param sheetName 表頁名     */    public void easyExcelBatchExport(String fileName, String sheetName, HttpServletResponse response) {        this.easyExcelBatchExport(fileName, sheetName, 100000, response);    }    /**     * 分批次導出excel數據     *     * @param fileName  excel文件前綴名     * @param sheetSize 每個sheet的數據量,默認10萬,excel有限制不能大于1048576     * @param sheetName 表頁名     */    public void easyExcelBatchExport(String fileName, String sheetName, Integer sheetSize, HttpServletResponse response) {        fileName = fileName + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";        int currentSheet = 1;   // 當前處于第幾個sheet        int totalLine = 0;      // 總共寫入的條數        int currentBatch = 1;   // 當前寫入excel的批次(第幾頁)        int lineNum = 1;        // 行號,當前寫入的是第幾條數據        long startTime = System.currentTimeMillis();        try {            response.setCharacterEncoding("utf-8");            // 告訴瀏覽器用什么軟件可以打開此文件            response.setHeader("content-Type", "application/vnd.ms-excel");            // 下載文件的默認名稱            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]).build();            WriteSheet sheet = EasyExcel.writerSheet(sheetName).build();            while (true) {                // 獲取數據,然后currentBatch+1,下次調用就會獲取新的數據                List<S> sourceDataList = getData(currentBatch);                currentBatch++;                List<T> exportEntityList = new ArrayList<>();                if (CollUtil.isNotEmpty(sourceDataList)) {                    totalLine += sourceDataList.size();                    log.info("EasyExcel開始寫入第{}批數據,當前批次數據大小為{}", currentBatch - 1, sourceDataList.size());                    for (S sourceData : sourceDataList) {                        exportEntityList.add(convertSourceData2ExportEntity(sourceData, lineNum));                        lineNum++;                        // 當前sheet數據已經到達最大值,將當前數據全寫入當前sheet,下一條數據就會寫入新sheet                        if (lineNum > sheetSize) {                            excelWriter.write(exportEntityList, sheet);                            exportEntityList.clear();                            lineNum = 1;                            currentSheet++;                            sheet = EasyExcel.writerSheet(sheetName + currentSheet).build();                        }                    }                    // 寫入excel                    excelWriter.write(exportEntityList, sheet);                } else {                    // 未獲取到數據,結束                    break;                }            }            excelWriter.finish();        } catch (Exception e) {            log.error("EasyExcel導出異常", e);        }        log.info("EasyExcel導出數據結束,總數據量為{},耗時{}ms", totalLine, (System.currentTimeMillis() - startTime));    }    /**     * 不分批次導出excel。一次性獲取所有數據寫入excel,確定數據量不大時可以使用該方法,數據量過大時使用分批次導出,否則會OOM     *     * @param fileName  excel文件前綴名     * @param sheetName 表頁名     */    public void easyExcelExport(String fileName, String sheetName, HttpServletResponse response) {        fileName = fileName + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";        int totalLine = 0;      // 總共寫入的條數        int lineNum = 1;        // 行號,當前寫入的是第幾條數據        long startTime = System.currentTimeMillis();        try {            response.setCharacterEncoding("utf-8");            // 告訴瀏覽器用什么軟件可以打開此文件            response.setHeader("content-Type", "application/vnd.ms-excel");            // 下載文件的默認名稱            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));            List<S> sourceDataList = getData(1);            List<T> exportEntityList = new ArrayList<>();            if (CollUtil.isNotEmpty(sourceDataList)) {                totalLine += sourceDataList.size();                log.info("EasyExcel開始寫入數據,數據大小為{}", sourceDataList.size());                for (S sourceData : sourceDataList) {                    exportEntityList.add(convertSourceData2ExportEntity(sourceData, lineNum));                    lineNum++;                }            }            response.setCharacterEncoding("utf-8");            // 告訴瀏覽器用什么軟件可以打開此文件            response.setHeader("content-Type", "application/vnd.ms-excel");            // 下載文件的默認名稱            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));            EasyExcel.write(response.getOutputStream(), (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]).sheet(sheetName).doWrite(exportEntityList);        } catch (Exception e) {            log.error("EasyExcel導出異常", e);        }        log.info("EasyExcel導出數據結束,總數據量為{},耗時{}ms", totalLine, (System.currentTimeMillis() - startTime));    }    /**     * 將原數據對象轉換為需要導出的目標對象     *     * @param sourceData 原對象     * @param lineNum    行號     */    public abstract T convertSourceData2ExportEntity(S sourceData, Integer lineNum);    /**     * 獲取原始數據,通過currentBatch參數分頁獲取數據。     *     * @param currentBatch 獲取第幾批(頁)數據,通過該參數分頁查詢,每次調用自動遞增。不分批次導出時可以忽略該參數     */    public abstract List<S> getData(int currentBatch);}

首先,這是EasyExcelExport是一個抽象類,指定了泛型 T  S,T是target目標類,也就是導出時對應的類,S是source原對象所對應的類。QIf28資訊網——每日最新資訊28at.com

EasyExcelExport里還有兩個抽象方法,getData()  convertSourceData2ExportEntity() 。這兩個方法是需要在平時使用時自己去實現的,getData是數據查詢的方法,可以在這里面去實現分頁查詢的邏輯,currentBatch參數是用來控制分頁查詢頁碼的,從1開始,會自動遞增。如果確定數據量不大不需要分批次導出的話,那么getData()里只需要進行普通的查詢即可,忽略currentBatch參數不用分頁查詢。還有一個方法是convertSourceData2ExportEntity(),這個是用來將對象S轉為對象T的方法,因為從數據庫查詢或者是從其他地方獲取到的對象類型可能是S,而導出時需要的對象類型是T,所以通過該方法進行對象轉換。QIf28資訊網——每日最新資訊28at.com

最核心的是 easyExcelBatchExport() 方法,里面有一個while循環,while循環里首先會去調用getData()方法獲取數據,然后將currentBatch加1便于下次獲取數據,接下來有個for循環去進行對象的轉換并添加到exportEntityList集合中,這個集合中裝的是最終寫到Excel里的對象。當轉換完成后就將當前批次的數據寫入Excel中,然后進行下一次循環,當getData()方法未獲取到數據時,就結束循環。QIf28資訊網——每日最新資訊28at.com

同時支持指定每個sheet頁的最大行數。在對對象進行轉換時有一個判斷,當前sheet頁的數據是否到達指定值,到達后,直接寫入excel,然后新建一個sheet頁,這樣新的數據就會寫入新的sheet頁。QIf28資訊網——每日最新資訊28at.com

使用

那么如何使用這個工具類呢。很簡單,只要new出EasyExcelExport的對象,然后實現一下 convertSourceData2ExportEntity() 方法和 getData() 方法即可,然后再根據需要去調用不同的導出方法即可。導出方法有指定和不指定sheet數據頁大小的分批寫入方法 easyExcelBatchExport() 和不分批次直接一次性寫入的 easyExcelExport() 方法。QIf28資訊網——每日最新資訊28at.com

下面通過一個小案例展示一下。假設現在有個導出用戶列表的需求,數據庫User表對應的是UserPO類:QIf28資訊網——每日最新資訊28at.com

@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class UserPO {    private Long id;    /**     * 用戶編號     */    private String code;    /**     * 姓名     */    private String name;    /**     * 手機號碼     */    private String phone;    /**     * 性別。1-男,2-女     */    private Integer sex;}

導出對應的類是UserExportEntity:QIf28資訊網——每日最新資訊28at.com

@Datapublic class UserExportEntity {    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 0, value = "序號")    private Integer line;    @ColumnWidth(35)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 1, value = "用戶編號")    private String code;    @ColumnWidth(35)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 2, value = "姓名")    private String name;    @ColumnWidth(35)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 3, value = "手機號碼")    private String phone;    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 4, value = "性別")    private String sexStr;    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 5, value = "fieldA")    private String fieldA;    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 6, value = "fieldB")    private String fieldB;    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 7, value = "fieldC")    private String fieldC;    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 8, value = "fieldD")    private String fieldD;    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 9, value = "fieldE")    private String fieldE;    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 10, value = "fieldF")    private String fieldF;    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 11, value = "fieldG")    private String fieldG;    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 12, value = "fieldH")    private String fieldH;    @ColumnWidth(10)    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)    @ExcelProperty(index = 13, value = "fieldI")    private String fieldI;}

先測試一下不分批次導出,導出123456條數據。QIf28資訊網——每日最新資訊28at.com

@GetMapping("/testExport")    public void testExport(HttpServletResponse response) {        new EasyExcelExport<UserExportEntity, UserPO>() {            @Override            public UserExportEntity convertSourceData2ExportEntity(UserPO sourceData, Integer lineNum) {                UserExportEntity entity = new UserExportEntity();                entity.setLine(lineNum);                entity.setCode(sourceData.getCode());                entity.setName(sourceData.getName());                entity.setPhone(sourceData.getPhone());                entity.setSexStr(Objects.equals(sourceData.getSex(), 1) ? "男" : Objects.equals(sourceData.getSex(), 2) ? "女" : StrUtil.EMPTY);                return entity;            }            @Override            public List<UserPO> getData(int currentBatch) {                List<UserPO> userPOList = new ArrayList<>();                // 模擬查詢數據庫,假設每次查詢會查出123456條數據                for (int i = 0; i < 123456; i++) {                    userPOList.add(UserPO.builder()                            .code("USER_" + RandomUtil.randomString("1234567890", 6))                            .name(RandomUtil.randomString("qwertyuiopasdfghjklzxcvbnm", 10))                            .phone("138" + RandomUtil.randomString("1234567890", 8))                            .sex(RandomUtil.randomInt(1, 3))                            .build());                }                log.info("userPOList-->{}", JSONUtil.toJsonStr(userPOList));                return userPOList;            }        }.easyExcelExport("測試不分批次導出", "測試不分批次導出", response);    }

為了更清晰地看到效果,我將內存大小限制為128M。QIf28資訊網——每日最新資訊28at.com

調用一下測試接口,可以看到,導出十幾萬條數據時發生了OOM。QIf28資訊網——每日最新資訊28at.com

再來看看分批次導出的效果,模擬一下分頁查詢,假設有200頁數據,每頁8888條,一共是170多萬條數據。QIf28資訊網——每日最新資訊28at.com

@GetMapping("/testBatchExport")    public void testBatchExport(HttpServletResponse response) {        new EasyExcelExport<UserExportEntity, UserPO>() {            @Override            public UserExportEntity convertSourceData2ExportEntity(UserPO sourceData, Integer lineNum) {                UserExportEntity entity = new UserExportEntity();                entity.setLine(lineNum);                entity.setCode(sourceData.getCode());                entity.setName(sourceData.getName());                entity.setPhone(sourceData.getPhone());                entity.setSexStr(Objects.equals(sourceData.getSex(), 1) ? "男" : Objects.equals(sourceData.getSex(), 2) ? "女" : StrUtil.EMPTY);                return entity;            }            @Override            public List<UserPO> getData(int currentBatch) {                // 模擬分頁查詢,假設數據庫中有200頁數據                if (currentBatch <= 200) {                    List<UserPO> userPOList = new ArrayList<>();                    // 模擬查詢數據庫,假設每次查詢會查出8888條數據                    for (int i = 0; i < 8888; i++) {                        userPOList.add(UserPO.builder()                                .code("USER_" + RandomUtil.randomString("1234567890", 6))                                .name(RandomUtil.randomString("qwertyuiopasdfghjklzxcvbnm", 10))                                .phone("138" + RandomUtil.randomString("1234567890", 8))                                .sex(RandomUtil.randomInt(1, 3))                                .build());                    }                    return userPOList;                } else {                    return new ArrayList<>();                }            }        }.easyExcelBatchExport("測試分批次導出", "測試分批次導出", response);    }

通過分批次寫入Excel的方式,成功導出了170多萬條數據,相較于不分批次導出,效果顯而易見。而且通過調用工具類的方式,進一步簡化了導出時代碼的編寫。QIf28資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-88394-0.html什么,你的EasyExcel導出一萬條數據就OOM了?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Kafka六大使用場景以及核心概念,你知道幾個?

下一篇: 聊聊主流的Kafka監控框架

標簽:
  • 熱門焦點
Top