excel 百萬數據如何導入導出?

前景

在項目開發中往往需要使用到數據的導入和導出, 導入就是從 Excel 中導入到 DB 中, 而導出就是從 DB 中查詢數據然後使用 POI 寫到 Excel 上。

寫本文的背景是因爲在工作中遇到了大數據的導入和導出,問題既然來了逃跑不如干掉它!!!

只要這一次解決了,後期遇到同樣的問題就好解決了。

廢話不多說,開始擼起來!!!

傳統 POI 的的版本優缺點比較

其實想到數據的導入導出, 理所當然的會想到 apache 的 poi 技術, 以及 Excel 的版本問題。

既然要做導入導出, 那麼我們就先來大致看一下傳統 poi 技術的版本以及優缺點對比吧!

首先我們知道 POI 中我們最熟悉的莫過於 WorkBook 這樣一個接口,我們的 POI 版本也在更新的同時對這個幾口的實現類做了更新;

這個實現類是我們早期使用最多的對象,它可以操作 Excel2003 以前(包含 2003)的所有 Excel 版本。在 2003 以前 Excel 的版本後綴還是. xls

這個實現類現在在很多公司都可以發現還在使用,它是操作的 Excel2003--Excel2007 之間的版本,Excel 的擴展名是. xlsx

這個實現類是 POI3.8 之後的版本纔有的, 它可以操作 Excel2007 以後的所有版本 Excel, 擴展名是. xlsx

大致知道了我們在導入導出操作的時候會用到這樣三個實現類以及他們可以操作的 Excel 版本和後綴之後, 我們就要從優缺點分析他們了

它是 POI 版本中最常用的方式, 不過:

優點:這種形式的出現是爲了突破 HSSFWorkbook 的 65535 行侷限,是爲了針對 Excel2007 版本的 1048576 行,16384 列,最多可以導出 104w 條數據;

缺點:伴隨的問題來了,雖然導出數據行數增加了好多倍,但是隨之而來的內存溢出問題也成了噩夢。因爲你所創建的 book,Sheet,row,cell 等在寫入到 Excel 之前,都是存放在內存中的(這還沒有算 Excel 的一些樣式格式等等),可想而知,內存不溢出就有點不科學了!!!

從 POI 3.8 版本開始,提供了一種基於 XSSF 的低內存佔用的 SXSSF 方式;

優點:

缺點:

使用方式哪種看情況

經過了解也知道了這三種 Workbook 的優點和缺點,那麼具體使用哪種方式還是需要看情況的:

我一般會根據這樣幾種情況做分析選擇:

  1. 當我們經常導入導出的數據不超過 7w 的情況下,可以使用 HSSFWorkbook 或者 XSSFWorkbook 都行;

  2. 當數據量查過 7w 並且導出的 Excel 中不牽扯對 Excel 的樣式,公式,格式等操作的情況下,推薦使用 SXSSFWorkbook;

  3. 當數據量查過 7w,並且我們需要操做 Excel 中的表頭,樣式,公式等,

這時候我們可以使用 XSSFWorkbook 配合進行分批查詢,分批寫入 Excel 的方式來做;

百萬數據導入導出(正菜)

鋪墊也做了不少,那麼現在開始講講我在工作中遇到的超百萬數據的導入導出解決方案:

想要解決問題我們首先要明白自己遇到的問題是什麼?

1、 我遇到的數據量超級大,使用傳統的 POI 方式來完成導入導出很明顯會內存溢出,並且效率會非常低;

2、 數據量大直接使用 select * from tableName 肯定不行,一下子查出來 300w 條數據肯定會很慢;

3、 300w 數據導出到 Excel 時肯定不能都寫在一個 Sheet 中,這樣效率會非常低;估計打開都得幾分鐘;

4、 300w 數據導出到 Excel 中肯定不能一行一行的導出到 Excel 中。頻繁 IO 操作絕對不行;

5、 導入時 300 萬數據存儲到 DB 如果循環一條條插入也肯定不行;

6、導入時 300w 數據如果使用 Mybatis 的批量插入肯定不行,因爲 Mybatis 的批量插入其實就是 SQL 的循環;一樣很慢。

解決思路:

針對 1 :

其實問題所在就是內存溢出,我們只要使用對上面介紹的 POI 方式即可,主要問題就是原生的 POI 解決起來相當麻煩。

經過查閱資料翻看到阿里的一款 POI 封裝工具 EasyExcel,上面問題等到解決;

針對 2:

不能一次性查詢出全部數據,我們可以分批進行查詢,只不過時多查詢幾次的問題,況且市面上分頁插件很多。此問題好解決。

針對 3:

可以將 300w 條數據寫到不同的 Sheet 中,每一個 Sheet 寫一百萬即可。

針對 4:

不能一行一行的寫入到 Excel 上,我們可以將分批查詢的數據分批寫入到 Excel 中。

針對 5:

導入到 DB 時我們可以將 Excel 中讀取的數據存儲到集合中,到了一定數量,直接批量插入到 DB 中。

針對 6:

不能使用 Mybatis 的批量插入,我們可以使用 JDBC 的批量插入,配合事務來完成批量插入到 DB。即 Excel 讀取分批 + JDBC 分批插入 + 事務。

1.EasyExcel 簡介

附上 GitHub 地址:https://github.com/alibaba/easyexcel

GitHub 地址上教程和說明很詳細,並且附帶有讀和寫的 demo 代碼,這裏對它的介紹我就不再詳細說了。

至於 EasyExcel 底層怎麼實現的這個還有待研究。

  1. 300w 數據導出

EasyExcel 完成 300w 數據的導出。技術難點已經知道了,接下來就是針對這一難點提供自己的解決思路即可。

300w 數據的導出解決思路:

1、 首先在查詢數據庫層面,需要分批進行查詢(我使用的是每次查詢 20w)

2、 每查詢一次結束,就使用 EasyExcel 工具將這些數據寫入一次;

3、 當一個 Sheet 寫滿了 100w 條數據,開始將查詢的數據寫入到另一個 Sheet 中;

4、 如此循環直到數據全部導出到 Excel 完畢。

注意:

  1. 我們需要計算 Sheet 個數,以及循環寫入次數。特別是最後一個 Sheet 的寫入次數

因爲你不知道最後一個 Sheet 選喲寫入多少數據,可能是 100w,也可能是 25w 因爲我們這裏的 300w 只是模擬數據,有可能導出的數據比 300w 多也可能少

2 我們需要計算寫入次數,因爲我們使用的分頁查詢,所以需要注意寫入的次數。

(其實查詢數據庫多少次就是寫入多少次)

//導出邏輯代碼
public void dataExport300w(HttpServletResponse response) {
        {
            OutputStream outputStream = null;
            try {
                long startTime = System.currentTimeMillis();
                System.out.println("導出開始時間:" + startTime);
                outputStream = response.getOutputStream();
                ExcelWriter writer = new ExcelWriter(outputStream, ExcelTypeEnum.XLSX);
                String fileName = new String(("excel100w").getBytes(), "UTF-8");
                //title
                Table table = new Table(1);
                List<List<String>> titles = new ArrayList<List<String>>();
                titles.add(Arrays.asList("onlineseqid"));
                titles.add(Arrays.asList("businessid"));
                titles.add(Arrays.asList("becifno"));
                titles.add(Arrays.asList("ivisresult"));
                titles.add(Arrays.asList("createdby"));
                titles.add(Arrays.asList("createddate"));
                titles.add(Arrays.asList("updateby"));
                titles.add(Arrays.asList("updateddate"));
                titles.add(Arrays.asList("risklevel"));
                table.setHead(titles);
                //模擬統計查詢的數據數量這裏模擬100w
                int count = 3000001;
                //記錄總數:實際中需要根據查詢條件進行統計即可
                Integer totalCount = actResultLogMapper.findActResultLogByCondations(count);
                //每一個Sheet存放100w條數據
                Integer sheetDataRows = ExcelConstants.PER_SHEET_ROW_COUNT;
                //每次寫入的數據量20w
                Integer writeDataRows = ExcelConstants.PER_WRITE_ROW_COUNT;
                //計算需要的Sheet數量
                Integer sheetNum = totalCount % sheetDataRows == 0 ? (totalCount / sheetDataRows) : (totalCount / sheetDataRows + 1);
                //計算一般情況下每一個Sheet需要寫入的次數(一般情況不包含最後一個sheet,因爲最後一個sheet不確定會寫入多少條數據)
                Integer oneSheetWriteCount = sheetDataRows / writeDataRows;
                //計算最後一個sheet需要寫入的次數
                Integer lastSheetWriteCount = totalCount % sheetDataRows == 0 ? oneSheetWriteCount : (totalCount % sheetDataRows % writeDataRows == 0 ? (totalCount / sheetDataRows / writeDataRows) : (totalCount / sheetDataRows / writeDataRows + 1));
                //開始分批查詢分次寫入
                //注意這次的循環就需要進行嵌套循環了,外層循環是Sheet數目,內層循環是寫入次數
                List<List<String>> dataList = new ArrayList<>();
                for (int i = 0; i < sheetNum; i++) {
                    //創建Sheet
                    Sheet sheet = new Sheet(i, 0);
                    sheet.setSheetName("測試Sheet1" + i);
                    //循環寫入次數: j的自增條件是當不是最後一個Sheet的時候寫入次數爲正常的每個Sheet寫入的次數,如果是最後一個就需要使用計算的次數lastSheetWriteCount
                    for (int j = 0; j < (i != sheetNum - 1 ? oneSheetWriteCount : lastSheetWriteCount); j++) {
                        //集合複用,便於GC清理
                        dataList.clear();
                        //分頁查詢一次20w
                        PageHelper.startPage(j + 1 + oneSheetWriteCount * i, writeDataRows);
                        List<ActResultLog> reslultList = actResultLogMapper.findByPage100w();
                        if (!CollectionUtils.isEmpty(reslultList)) {
                            reslultList.forEach(item -> {
                                dataList.add(Arrays.asList(item.getOnlineseqid(), item.getBusinessid(), item.getBecifno(), item.getIvisresult(), item.getCreatedby(), Calendar.getInstance().getTime().toString(), item.getUpdateby(), Calendar.getInstance().getTime().toString(), item.getRisklevel()));
                            });
                        }
                        //寫數據
                        writer.write0(dataList, sheet, table);
                    }
                }
                // 下載EXCEL
                response.setHeader("Content-Disposition", "attachment;file);
                response.setContentType("multipart/form-data");
                response.setCharacterEncoding("utf-8");
                writer.finish();
                outputStream.flush();
                //導出時間結束
                long endTime = System.currentTimeMillis();
                System.out.println("導出結束時間:" + endTime + "ms");
                System.out.println("導出所用時間:" + (endTime - startTime) / 1000 + "");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (outputStream != null) {
                    try {
                        outputStream.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

2.1 測試機狀態

下面是測試機配置

2.2 使用數據庫版本

數據庫我使用的是 Oracle19C 在網上查閱其實在數據量不超過 1 億的情況下, Mysql 和 Oracle 的性能其實相差不大, 超過 1 億, Oracle 的各方面優勢纔會明顯。

所以這裏可以忽略使用數據庫對時間造成的影響,使用 mysql 一樣可以完成測試,不需要單獨安裝 Oracle。

這次測試在查詢方面我使用的是 rownum 進行的模擬查詢 300w 條數據,這種查詢效率其實並不高,實際還有很多優化空間來加快查詢速度,

如:明確查詢具體字段,不要用星號,經常查詢字段增加索引等儘可能提高查詢效率,用時可能會更短。

<select resultType="show.mrkay.pojo.ActResultLog">
    select *
    from ACT_RESULT_LOG
    where rownum <![CDATA[<]]> 3000001
</select>
-- 建表語句:可以參考一下
-- Create table
create table ACT_RESULT_LOG
(
  onlineseqid VARCHAR2(32),
  businessid  VARCHAR2(32),
  becifno     VARCHAR2(32),
  ivisresult  VARCHAR2(32),
  createdby   VARCHAR2(32),
  createddate DATE,
  updateby    VARCHAR2(32),
  updateddate DATE,
  risklevel   VARCHAR2(32)
)
tablespace STUDY_KAY
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );

2.3 測試結果

下面是 300w 數據從 DB 導出到 Excel 所用時間

從上面結果可以看出, 300w 的數據導出時間用時 2 分 15 秒, 並且這是在不適用實體作爲映射的情況下, 如果使用實體映射不適用循環封裝的話速度會更快 (當然這也是在沒有設置表頭等其他表格樣式的情況下)

綜合來說速度還算可以.

在網上查了很多資料有一個博主測試使用 EasyExcel 導出 102w 數據用時 105 秒, 具體可以看一下鏈接:

https://blog.csdn.net/u014299266/article/details/107790561

看一下導出效果: 文件還是挺大的 163M

2.4 導出小結

經過測試 EasyExcel 還是挺快的, 並且使用起來相當方便, 作者還專門提供了關流方法, 不需要我們手動去關流了, 也避免了我們經常忘記關流導致的一系列問題。

導出測試就到這裏,對於數據量小於 300W 的數據可以使用在一個 Sheet 中進行導出。這裏就不再演示。

3. 300w 數據導入

代碼不重要首先還是思路

300W 數據的導入解決思路

1、首先是分批讀取讀取 Excel 中的 300w 數據,這一點 EasyExcel 有自己的解決方案,我們可以參考 Demo 即可,只需要把它分批的參數 3000 調大即可。我是用的 20w;(一會兒代碼一看就能明白)

2、其次就是往 DB 裏插入,怎麼去插入這 20w 條數據,當然不能一條一條的循環,應該批量插入這 20w 條數據,同樣也不能使用 Mybatis 的批量插入語,因爲效率也低。

3、使用 JDBC + 事務的批量操作將數據插入到數據庫。(分批讀取 + JDBC 分批插入 + 手動事務控制)

https://www.cnblogs.com/wxw7blog/p/8706797.html

3.1 數據庫數據 (導入前)

如圖

3.2 核心業務代碼

// EasyExcel的讀取Excel數據的API
@Test
public void import2DBFromExcel10wTest() {
    String fileName = "D:\\StudyWorkspace\\JavaWorkspace\\java_project_workspace\\idea_projects\\SpringBootProjects\\easyexcel\\exportFile\\excel300w.xlsx";
    //記錄開始讀取Excel時間,也是導入程序開始時間
    long startReadTime = System.currentTimeMillis();
    System.out.println("------開始讀取Excel的Sheet時間(包括導入數據過程):" + startReadTime + "ms------");
    //讀取所有Sheet的數據.每次讀完一個Sheet就會調用這個方法
    EasyExcel.read(fileName, new EasyExceGeneralDatalListener(actResultLogService2)).doReadAll();
    long endReadTime = System.currentTimeMillis();
    System.out.println("------結束讀取Excel的Sheet時間(包括導入數據過程):" + endReadTime + "ms------");
}
// 事件監聽
public class EasyExceGeneralDatalListener extends AnalysisEventListener<Map<Integer, String>> {
    /**
     * 處理業務邏輯的Service,也可以是Mapper
     */
    private ActResultLogService2 actResultLogService2;
    /**
     * 用於存儲讀取的數據
     */
    private List<Map<Integer, String>> dataList = new ArrayList<Map<Integer, String>>();
    public EasyExceGeneralDatalListener() {
    }
    public EasyExceGeneralDatalListener(ActResultLogService2 actResultLogService2) {
        this.actResultLogService2 = actResultLogService2;
    }
    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        //數據add進入集合
        dataList.add(data);
        //size是否爲100000條:這裏其實就是分批.當數據等於10w的時候執行一次插入
        if (dataList.size() >= ExcelConstants.GENERAL_ONCE_SAVE_TO_DB_ROWS) {
            //存入數據庫:數據小於1w條使用Mybatis的批量插入即可;
            saveData();
            //清理集合便於GC回收
            dataList.clear();
        }
    }
    /**
     * 保存數據到DB
     *
     * @param
     * @MethodName: saveData
     * @return: void
     */
    private void saveData() {
        actResultLogService2.import2DBFromExcel10w(dataList);
        dataList.clear();
    }
    /**
     * Excel中所有數據解析完畢會調用此方法
     *
     * @param: context
     * @MethodName: doAfterAllAnalysed
     * @return: void
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
        dataList.clear();
    }
}
//JDBC工具類
public class JDBCDruidUtils {
    private static DataSource dataSource;
    /*
   創建數據Properties集合對象加載加載配置文件
    */
    static {
        Properties pro = new Properties();
        //加載數據庫連接池對象
        try {
            //獲取數據庫連接池對象
            pro.load(JDBCDruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /*
    獲取連接
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    /**
     * 關閉conn,和 statement獨對象資源
     *
     * @param connection
     * @param statement
     * @MethodName: close
     * @return: void
     */
    public static void close(Connection connection, Statement statement) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 關閉 conn , statement 和resultset三個對象資源
     *
     * @param connection
     * @param statement
     * @param resultSet
     * @MethodName: close
     * @return: void
     */
    public static void close(Connection connection, Statement statement, ResultSet resultSet) {
        close(connection, statement);
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    /*
    獲取連接池對象
     */
    public static DataSource getDataSource() {
        return dataSource;
    }
}
# druid.properties配置
driverClassName=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:ORCL
username=mrkay
password=******
initialSize=10
maxActive=50
maxWait=60000
// Service中具體業務邏輯
/**
 * 測試用Excel導入超過10w條數據,經過測試發現,使用Mybatis的批量插入速度非常慢,所以這裏可以使用 數據分批+JDBC分批插入+事務來繼續插入速度會非常快
 *
 * @param
 * @MethodName: import2DBFromExcel10w
 * @return: java.util.Map<java.lang.String, java.lang.Object>
 */
@Override
public Map<String, Object> import2DBFromExcel10w(List<Map<Integer, String>> dataList) {
    HashMap<String, Object> result = new HashMap<>();
    //結果集中數據爲0時,結束方法.進行下一次調用
    if (dataList.size() == 0) {
        result.put("empty", "0000");
        return result;
    }
    //JDBC分批插入+事務操作完成對10w數據的插入
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        long startTime = System.currentTimeMillis();
        System.out.println(dataList.size() + "條,開始導入到數據庫時間:" + startTime + "ms");
        conn = JDBCDruidUtils.getConnection();
        //控制事務:默認不提交
        conn.setAutoCommit(false);
        String sql = "insert into ACT_RESULT_LOG (onlineseqid,businessid,becifno,ivisresult,createdby,createddate,updateby,updateddate,risklevel) values";
        sql += "(?,?,?,?,?,?,?,?,?)";
        ps = conn.prepareStatement(sql);
        //循環結果集:這裏循環不支持"爛布袋"表達式
        for (int i = 0; i < dataList.size(); i++) {
            Map<Integer, String> item = dataList.get(i);
            ps.setString(1, item.get(0));
            ps.setString(2, item.get(1));
            ps.setString(3, item.get(2));
            ps.setString(4, item.get(3));
            ps.setString(5, item.get(4));
            ps.setTimestamp(6, new Timestamp(System.currentTimeMillis()));
            ps.setString(7, item.get(6));
            ps.setTimestamp(8, new Timestamp(System.currentTimeMillis()));
            ps.setString(9, item.get(8));
            //將一組參數添加到此 PreparedStatement 對象的批處理命令中。
            ps.addBatch();
        }
        //執行批處理
        ps.executeBatch();
        //手動提交事務
        conn.commit();
        long endTime = System.currentTimeMillis();
        System.out.println(dataList.size() + "條,結束導入到數據庫時間:" + endTime + "ms");
        System.out.println(dataList.size() + "條,導入用時:" + (endTime - startTime) + "ms");
        result.put("success", "1111");
    } catch (Exception e) {
        result.put("exception", "0000");
        e.printStackTrace();
    } finally {
        //關連接
        JDBCDruidUtils.close(conn, ps);
    }
    return result;
}

3.3 測試結果

下面是 300w 數據邊讀邊寫用時間:

大致計算一下:

從開始讀取到中間分批導入再到程序結束總共用時: (1623127964725-1623127873630)/1000=91.095 秒

300w 數據正好是分 15 次插入綜合用時: 8209 毫秒 也就是 8.209 秒

計算可得 300w 數據讀取時間爲: 91.095-8.209=82.886 秒

結果顯而易見:

EasyExcel 分批讀取 300W 數據只用了 82.886 秒

使用 JDBC 分批 + 事務操作插入 300w 條數據綜合只用時 8.209 秒

------開始讀取Excel的Sheet時間(包括導入數據過程):1623127873630ms------
200000條,開始導入到數據庫時間:1623127880632ms
200000條,結束導入到數據庫時間:1623127881513ms
200000條,導入用時:881ms
200000條,開始導入到數據庫時間:1623127886945ms
200000條,結束導入到數據庫時間:1623127887429ms
200000條,導入用時:484ms
200000條,開始導入到數據庫時間:1623127892894ms
200000條,結束導入到數據庫時間:1623127893397ms
200000條,導入用時:503ms
200000條,開始導入到數據庫時間:1623127898607ms
200000條,結束導入到數據庫時間:1623127899066ms
200000條,導入用時:459ms
200000條,開始導入到數據庫時間:1623127904379ms
200000條,結束導入到數據庫時間:1623127904855ms
200000條,導入用時:476ms
200000條,開始導入到數據庫時間:1623127910495ms
200000條,結束導入到數據庫時間:1623127910939ms
200000條,導入用時:444ms
200000條,開始導入到數據庫時間:1623127916271ms
200000條,結束導入到數據庫時間:1623127916744ms
200000條,導入用時:473ms
200000條,開始導入到數據庫時間:1623127922465ms
200000條,結束導入到數據庫時間:1623127922947ms
200000條,導入用時:482ms
200000條,開始導入到數據庫時間:1623127928260ms
200000條,結束導入到數據庫時間:1623127928727ms
200000條,導入用時:467ms
200000條,開始導入到數據庫時間:1623127934374ms
200000條,結束導入到數據庫時間:1623127934891ms
200000條,導入用時:517ms
200000條,開始導入到數據庫時間:1623127940189ms
200000條,結束導入到數據庫時間:1623127940677ms
200000條,導入用時:488ms
200000條,開始導入到數據庫時間:1623127946402ms
200000條,結束導入到數據庫時間:1623127946925ms
200000條,導入用時:523ms
200000條,開始導入到數據庫時間:1623127952158ms
200000條,結束導入到數據庫時間:1623127952639ms
200000條,導入用時:481ms
200000條,開始導入到數據庫時間:1623127957880ms
200000條,結束導入到數據庫時間:1623127958925ms
200000條,導入用時:1045ms
200000條,開始導入到數據庫時間:1623127964239ms
200000條,結束導入到數據庫時間:1623127964725ms
200000條,導入用時:486ms
------結束讀取Excel的Sheet時間(包括導入數據過程):1623127964725ms------

看一下數據庫的數據是不是真的存進去了 300w

可以看到數據比導入前多了 300W, 測試很成功

3.4 導入小結

具體我沒有看網上其他人的測試情況, 這東西一般也很少有人願意測試, 不過這個速度對於我當時解決公司大數據的導入和導出已經足夠,

當然公司的業務邏輯很複雜, 數據量也比較多, 表的字段也比較多, 導入和導出的速度會比現在測試的要慢一點, 但是也在人類能接受的範圍之內。

總結

這次工作中遇到的問題也給我留下了深刻印象, 同時也是我職業生涯添彩的一筆。

最起碼簡歷上可以寫上你處理過上百萬條數據的導入導出。

最後說一下公司之前怎麼做的,公司之前做法是

限制了用戶的下載數量每次最多隻能有四個人同時下載,並且控制每個用戶最大的導出數據最多隻能是 20w,與此同時他們也是使用的 JDBC 分批導入,但是並沒有手動控制事務。

控制同時下載人數我可以理解,但是控制下載數據最多爲 20w 就顯得有點雞肋了。

這也是我後期要解決的問題。

好了到此結束,相信大神有比我做的更好的,對於 EasyExcel 內部到底是怎麼實現的還有待考究(有空我再研究研究)。

作者:liu.kai

來源:blog.csdn.net/weixin_44848900/article/details/117701981

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/oD7HBJ5LKkcp9Lfp5dkDUw