實現萬行級excel導出---poi--ooxm的應用和採坑

xl_echo編輯整理,歡迎轉載,轉載請聲明文章來源。歡迎添加echo微信(微信號:t2421499075)交流學習。 百戰不敗,依不自稱常勝,百敗不頹,依能奮力前行。——這纔是真正的堪稱強大!!java

閱讀建議:若是系統沒有作過相關導入導出,又須要這個量級的能夠直接閱讀底部的poi-ooxm的應用

excel使用poi直接導出最大值是多少?估計不少人不會關注這個問題,由於不多有業務要求excel直接導出上萬條數據,更甚者可能沒有聽過excel直接導出100w條數據。這裏給你們介紹一個引用場景和博主採坑的經歷。spring

需求描述

公司要求實現一個銀行流水的導出功能,看上去就是一個簡單的按鈕,可是這個需求有幾點要求:apache

  • 導出的文件格式須要爲xlsx
  • 每次導出最低量爲5w條,且爲一個表
  • 導出的時候須要以文件下載的形式在瀏覽器下載

樓主開發環境:jdk1.8, idea2018.1,springboot1.5x,dubbox瀏覽器

對於poi-3.9的一次嘗試

剛開始的時候,沒有過多的關注5w條這個數量,直接使用的poi-3.9。在整個開發過程當中基本沒有碰到問題。在測試的時候,碰到了一個不能知足需求的問題。當我直接下載5w條的時候,程序直接報錯。通過不斷的測試,發現3.9的直接使用,最高下載值爲6000+(這個最高值和電腦性能有必定的關係,不過出入不會很大)。springboot

測試結論poi-3.9的天花板 6000+

很明顯上面的嘗試不能作出與需求相關的功能,通過百度,發現easyexcel是一個不錯的解決方案。微信

對於easyexcel的一次嘗試

導入的easyexcel爲app

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>1.1.1</version>
</dependency>複製代碼

使用該依賴的時候,我參考了覺得博主的代碼,代碼地址爲:https://blog.csdn.net/qq_35206261/article/details/88579151。當我將他的百萬行級別的解決方案搬到個人項目中的時候,發現一切好像沒有問題。可是當我啓動的時候發現一直報錯java.lang.NoSuchMethodError: org.apache.poi.ss.usermodel.Font.setBold(Z)V。(這裏的錯誤來源於公司項目原有的項目導入依賴衝突,若是沒有作過導入導出相關功能的應該不會出現)。通過排查發現,問題出如今依賴上面。公司本來是有導入和導出功能的ide

引入的依賴以下:工具

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.9</version>
</dependency>複製代碼

衝突以下圖所示:圖1性能

坑一poi-ooxml沒有向下兼容

點擊進入easyexcel的依賴發現,它的底層是依賴的poi3.17的版本。當我一直覺得poi是可以向下兼容的時候,最終每次啓動和編譯的時候出現的問題都指向了並無向下兼容。底層的poi-ooxml版本爲3.17,如圖所示圖2

這個時候咱們能夠若是還要去使用easyexcel,那咱們須要更改版本或者解決poi-ooxml的版本兼容問題。

通過不兼容問題以後,看公司原有poi-ooxml的應用因而決定使用poi-ooxml的解決方案。

poi-ooxml的應用

通過以上幾個坑以後,因而決定使用poi-ooxml的3.9版本可以兼容的解決方案,百度以後發現有一個以3.8版本基礎的應用。通過上面的不兼容以後,仍是決定試一試。因而採用了該博主文章中的一段代碼,文章地址:https://blog.csdn.net/happyljw/article/details/52809244。

這篇文章中描述了一個思路,同時也給出了一段博主提供的實現代碼,相對來講,若是做爲測試問題不大,最後根據需求進行調整,修改了部分實現的步驟,同時也新增了一些新的實現和限制。代碼以下:

@ResponseBody
@RequestMapping(value = "/exportDataMoreThan1000")
public void readMoreThan1000RowBySheet(@RequestParam(value = "start") Integer start,
                                       @RequestParam(value = "limit") Integer limit,
                                       HttpServletResponse response) throws Exception {
    //內存中只建立100個對象,寫臨時文件,當超過100條,就將內存中不用的對象釋放。
    Workbook wb = new SXSSFWorkbook(100);
    //工做表對象
    final Sheet[] sheet = {null};
    //行對象
    final Row[] nRow = {null};
    //列對象
    final Cell[] nCell = {null};
    //總行號
    final int[] rowNo = {0};
    //頁行號
    final int[] pageRowNo = {0};

    List<BankDto> list = new ArrayList<>(50000);
    //數據源
    list = BankServer.getList();
    list.forEach(it -> {
        if (rowNo[0] % 10001 == 0) {
            sheet[0] = wb.createSheet("個人第" + (rowNo[0] / 10001 + 1) + "個工做簿");
            sheet[0] = wb.getSheetAt(rowNo[0] / 10001);
            //每當新建了工做表就將當前工做表的行號重置爲0
            pageRowNo[0] = 0;
        }
        rowNo[0]++;
        nRow[0] = sheet[0].createRow(pageRowNo[0]++);
        //這一步很關鍵,若是沒有就沒有表頭。
        if (pageRowNo[0] == 1) {
            for (int j = 0; j < 9; j++) {
                nCell[0] = nRow[0].createCell(j);
                if (j == 0) nCell[0].setCellValue("編號");
                if (j == 1) nCell[0].setCellValue("編號");
                if (j == 2) nCell[0].setCellValue("編號");
                if (j == 3) nCell[0].setCellValue("編號");
                if (j == 4) nCell[0].setCellValue("編號");
                if (j == 5) nCell[0].setCellValue("編號");
                if (j == 6) nCell[0].setCellValue("編號");
                if (j == 7) nCell[0].setCellValue("編號");
                if (j == 8) nCell[0].setCellValue("備註");
            }
            rowNo[0]++;
            nRow[0] = sheet[0].createRow(pageRowNo[0]++);
        }
        // 輸出每行,每行有9列數據
        for (int j = 0; j < 9; j++) {
            nCell[0] = nRow[0].createCell(j);
            if (j == 0) nCell[0].setCellValue(it.getPaycode());
            if (j == 1) nCell[0].setCellValue(it.getPaycode());
            if (j == 2) nCell[0].setCellValue(it.getPaycode());
            if (j == 3) nCell[0].setCellValue(it.getPaycode());
            if (j == 4) nCell[0].setCellValue(it.getPaycode());
            if (j == 5) nCell[0].setCellValue(it.getPaycode());
            if (j == 6) nCell[0].setCellValue(it.getPaycode());
            if (j == 7) nCell[0].setCellValue(it.getPaycode());
            if (j == 8) nCell[0].setCellValue(it.getRemark());
        }
    });
    String fileName = "銀行流水錶.xlsx";
    //設置請求頭
    response.setHeader("content-Type", "application/vnd.ms-excel");
    response.setContentType("application/vnd.ms-excel;charset=utf-8");
    response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".xlsx");
    ServletOutputStream outputStream = response.getOutputStream();
    wb.write(outputStream);
    outputStream.flush();
    outputStream.close();
}複製代碼

這裏簡化了數據源的操做,若是須要能夠根據本身的業務進行修改,個人實際實現裏對數據源操做相對複查,不只進行了分片請求(避免dubbox超時),同時還對數據進行了不少處理。這裏的操做有一個亮點,那就是進行了多Sheet的分割。

注意:這裏的開發環境是jdk1.8

當完成以上的代碼編寫以後,使用工具測試,發現已經實現了我須要的功能。目前的一個下載量10w之內都沒有什麼太大的問題,實測10w數據20s。若是業務邏輯簡單些還會提高一倍的速度。

總結:

  • 萬行級的解決方案有兩種
  • * poi-ooxml
  • * easyexc
  • 若是使用其中的某一種要注意是否引入了另一種,可能會不兼容。
  • poi-ooxml3.9和poi-ooxml3.17不能完美向下兼容
  • poi3.17最大下載峯值6000+
相關文章
相關標籤/搜索