1. 程式人生 > >實現萬行級excel匯出---poi--ooxm的應用和採坑

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

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


閱讀建議:如果系統沒有做過相關匯入匯出,又需要這個量級的可以直接閱讀底部的poi-ooxm的應用

excel使用poi直接匯出最大值是多少?估計很多人不會關注這個問題,因為很少有業務要求excel直接匯出上萬條資料,更甚者可能沒有聽過excel直接匯出100w條資料。這裡給大家介紹一個引用場景和博主採坑的經歷。

需求描述

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

  • 匯出的檔案格式需要為xlsx
  • 每次匯出最低量為5w條,且為一個表
  • 匯出的時候需要以檔案下載的形式在瀏覽器下載

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

對於poi-3.9的一次嘗試

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

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

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

對於easyexcel的一次嘗試

匯入的easyexcel為

<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。(這裡的錯誤來源於公司專案原有的專案匯入依賴衝突,如果沒有做過匯入匯出相關功能的應該不會出現)。經過排查發現,問題出現在依賴上面。公司原本是有匯入和匯出功能的

引入的依賴如下:

<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最大下