1. 程式人生 > >百萬級EXCEL匯出

百萬級EXCEL匯出

一. 簡介

          excel匯出,如果資料量在百萬級,會出現倆點記憶體溢位的問題:

          1. 查詢資料量過大,導致記憶體溢位。 該問題可以通過分批查詢來解決;

          2. 最後下載的時候大EXCEL轉換的輸出流記憶體溢位;該方式可以通過新版的SXSSFWorkbook來解決,可通過其建構函式執指定在記憶體中快取的行數,剩餘的會自動快取在硬碟的臨時目錄上;

          3. 為了能夠使用不同的mapper並分批寫資料, 採用了模板方法設計模式,在可變的匿名內部類中實現寫入邏輯。

二. 工具程式碼

2.1 pom.xml

      <!-- poi -->
      <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi</artifactId>
          <version>3.17</version>
      </dependency>
      <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi-ooxml</artifactId>
          <version>3.17</version>
      </dependency>

注: 如果是springboot2.0,則不需要poi依賴,如果是1.0,則需要poi依賴,並且poi和poi-ooxml的版本要保持一致。

          別的依賴我就不加了。

2.2 ExcelConstant

package com.yzx.caasscs.constant;

/**
 * @author qjwyss
 * @date 2018/9/19
 * @description EXCEL常量類
 */
public class ExcelConstant {

    /**
     * 每個sheet儲存的記錄數 100W
     */
    public static final Integer PER_SHEET_ROW_COUNT = 1000000;

    /**
     * 每次向EXCEL寫入的記錄數(查詢每頁資料大小) 20W
     */
    public static final Integer PER_WRITE_ROW_COUNT = 200000;


    /**
     * 每個sheet的寫入次數 5
     */
    public static final Integer PER_SHEET_WRITE_COUNT = PER_SHEET_ROW_COUNT / PER_WRITE_ROW_COUNT;


}

注: xlsx模式的excel每個sheet最多儲存104W,此處我就每個sheet儲存了 100W資料;每次查詢20W資料;

2.3 寫資料委託類

package com.yzx.caasscs.util;

import org.apache.poi.xssf.streaming.SXSSFSheet;

/**
 * @author qjwyss
 * @date 2018/9/20
 * @description EXCEL寫資料委託類
 */
public interface WriteExcelDataDelegated {

    /**
     * EXCEL寫資料委託類  針對不同的情況自行實現
     *
     * @param eachSheet     指定SHEET
     * @param startRowCount 開始行
     * @param endRowCount   結束行
     * @param currentPage   分批查詢開始頁
     * @param pageSize      分批查詢資料量
     * @throws Exception
     */
    public abstract void writeExcelData(SXSSFSheet eachSheet, Integer startRowCount, Integer endRowCount, Integer currentPage, Integer pageSize) throws Exception;


}

2.4 POI工具類

package com.yzx.caasscs.util;


import com.yzx.caasscs.constant.ExcelConstant;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;

/**
 * @author qjwyss
 * @date 2018/9/18
 * @description POI匯出工具類
 */
public class PoiUtil {

    private final static Logger logger = LoggerFactory.getLogger(PoiUtil.class);

    /**
     * 初始化EXCEL(sheet個數和標題)
     *
     * @param totalRowCount 總記錄數
     * @param titles        標題集合
     * @return XSSFWorkbook物件
     */
    public static SXSSFWorkbook initExcel(Integer totalRowCount, String[] titles) {

        // 在記憶體當中保持 100 行 , 超過的資料放到硬碟中在記憶體當中保持 100 行 , 超過的資料放到硬碟中
        SXSSFWorkbook wb = new SXSSFWorkbook(100);

        Integer sheetCount = ((totalRowCount % ExcelConstant.PER_SHEET_ROW_COUNT == 0) ?
                (totalRowCount / ExcelConstant.PER_SHEET_ROW_COUNT) : (totalRowCount / ExcelConstant.PER_SHEET_ROW_COUNT + 1));

        // 根據總記錄數建立sheet並分配標題
        for (int i = 0; i < sheetCount; i++) {
            SXSSFSheet sheet = wb.createSheet("sheet" + (i + 1));
            SXSSFRow headRow = sheet.createRow(0);

            for (int j = 0; j < titles.length; j++) {
                SXSSFCell headRowCell = headRow.createCell(j);
                headRowCell.setCellValue(titles[j]);
            }
        }

        return wb;
    }


    /**
     * 下載EXCEL到本地指定的資料夾
     *
     * @param wb         EXCEL物件SXSSFWorkbook
     * @param exportPath 匯出路徑
     */
    public static void downLoadExcelToLocalPath(SXSSFWorkbook wb, String exportPath) {
        FileOutputStream fops = null;
        try {
            fops = new FileOutputStream(exportPath);
            wb.write(fops);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != wb) {
                try {
                    wb.dispose();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (null != fops) {
                try {
                    fops.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 下載EXCEL到瀏覽器
     *
     * @param wb       EXCEL物件XSSFWorkbook
     * @param response
     * @param fileName 檔名稱
     * @throws IOException
     */
    public static void downLoadExcelToWebsite(SXSSFWorkbook wb, HttpServletResponse response, String fileName) throws IOException {

        response.setHeader("Content-disposition", "attachment; filename="
                + new String((fileName + ".xlsx").getBytes("utf-8"), "ISO8859-1"));//設定下載的檔名

        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            wb.write(outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != wb) {
                try {
                    wb.dispose();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 匯出Excel到本地指定路徑
     *
     * @param totalRowCount           總記錄數
     * @param titles                  標題
     * @param exportPath              匯出路徑
     * @param writeExcelDataDelegated 向EXCEL寫資料/處理格式的委託類 自行實現
     * @throws Exception
     */
    public static final void exportExcelToLocalPath(Integer totalRowCount, String[] titles, String exportPath, WriteExcelDataDelegated writeExcelDataDelegated) throws Exception {

        logger.info("開始匯出:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));

        // 初始化EXCEL
        SXSSFWorkbook wb = PoiUtil.initExcel(totalRowCount, titles);

        // 呼叫委託類分批寫資料
        int sheetCount = wb.getNumberOfSheets();
        for (int i = 0; i < sheetCount; i++) {
            SXSSFSheet eachSheet = wb.getSheetAt(i);

            for (int j = 1; j <= ExcelConstant.PER_SHEET_WRITE_COUNT; j++) {

                int currentPage = i * ExcelConstant.PER_SHEET_WRITE_COUNT + j;
                int pageSize = ExcelConstant.PER_WRITE_ROW_COUNT;
                int startRowCount = (j - 1) * ExcelConstant.PER_WRITE_ROW_COUNT + 1;
                int endRowCount = startRowCount + pageSize - 1;


                writeExcelDataDelegated.writeExcelData(eachSheet, startRowCount, endRowCount, currentPage, pageSize);

            }
        }


        // 下載EXCEL
        PoiUtil.downLoadExcelToLocalPath(wb, exportPath);

        logger.info("匯出完成:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));
    }


    /**
     * 匯出Excel到瀏覽器
     *
     * @param response
     * @param totalRowCount           總記錄數
     * @param fileName                檔名稱
     * @param titles                  標題
     * @param writeExcelDataDelegated 向EXCEL寫資料/處理格式的委託類 自行實現
     * @throws Exception
     */
    public static final void exportExcelToWebsite(HttpServletResponse response, Integer totalRowCount, String fileName, String[] titles, WriteExcelDataDelegated writeExcelDataDelegated) throws Exception {

        logger.info("開始匯出:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));

        // 初始化EXCEL
        SXSSFWorkbook wb = PoiUtil.initExcel(totalRowCount, titles);


        // 呼叫委託類分批寫資料
        int sheetCount = wb.getNumberOfSheets();
        for (int i = 0; i < sheetCount; i++) {
            SXSSFSheet eachSheet = wb.getSheetAt(i);

            for (int j = 1; j <= ExcelConstant.PER_SHEET_WRITE_COUNT; j++) {

                int currentPage = i * ExcelConstant.PER_SHEET_WRITE_COUNT + j;
                int pageSize = ExcelConstant.PER_WRITE_ROW_COUNT;
                int startRowCount = (j - 1) * ExcelConstant.PER_WRITE_ROW_COUNT + 1;
                int endRowCount = startRowCount + pageSize - 1;

                writeExcelDataDelegated.writeExcelData(eachSheet, startRowCount, endRowCount, currentPage, pageSize);

            }
        }


        // 下載EXCEL
        PoiUtil.downLoadExcelToWebsite(wb, response, fileName);

        logger.info("匯出完成:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));
    }


}

三. 使用DEMO

3.1 Controller

3.2 Service

3.3 ServiceImpl

3.4 mapper

3.5 mapper.xml