<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官:百萬數(shù)據(jù)的導入導出解決方案,怎么設(shè)計?

          共 32603字,需瀏覽 66分鐘

           ·

          2022-11-29 12:21

          關(guān)注我們,設(shè)為星標,每天7:40不見不散,架構(gòu)路上與您共享

          回復架構(gòu)師獲取資源


          大家好,我是你們的朋友架構(gòu)君,一個會寫代碼吟詩的架構(gòu)師。

          'javajgs.com';


          原文:
          blog.csdn.net/weixin_44848900/article/details/117701981


          前景

          在項目開發(fā)中往往需要使用到數(shù)據(jù)的導入和導出,導入就是從Excel中導入到DB中,而導出就是從DB中查詢數(shù)據(jù)然后使用POI寫到Excel上。


          寫本文的背景是因為在工作中遇到了大數(shù)據(jù)的導入和導出,問題既然來了逃跑不如干掉它?。。?/span>


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


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


          1 傳統(tǒng)POI的的版本優(yōu)缺點比較

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

          既然要做導入導出,那么我們就先來大致看一下傳統(tǒng)poi技術(shù)的版本以及優(yōu)缺點對比吧!

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

          • HSSFWorkbook :

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

          • XSSFWorkbook :

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

          • SXSSFWorkbook :

          這個實現(xiàn)類是POI3.8之后的版本才有的,它可以操作Excel2007以后的所有版本Excel,擴展名是.xlsx

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

          HSSFWorkbook

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

          • 它的缺點是 最多只能導出 65535行,也就是導出的數(shù)據(jù)函數(shù)超過這個數(shù)據(jù)就會報錯;
          • 它的優(yōu)點是 不會報內(nèi)存溢出。(因為數(shù)據(jù)量還不到7w所以內(nèi)存一般都夠用,首先你得明確知道這種方式是將數(shù)據(jù)先讀取到內(nèi)存中,然后再操作)

          XSSFWorkbook

          • 優(yōu)點:這種形式的出現(xiàn)是為了突破HSSFWorkbook的65535行局限,是為了針對Excel2007版本的1048576行,16384列,最多可以導出104w條數(shù)據(jù);
          • 缺點:伴隨的問題來了,雖然導出數(shù)據(jù)行數(shù)增加了好多倍,但是隨之而來的內(nèi)存溢出問題也成了噩夢。因為你所創(chuàng)建的book,Sheet,row,cell等在寫入到Excel之前,都是存放在內(nèi)存中的(這還沒有算Excel的一些樣式格式等等),可想而知,內(nèi)存不溢出就有點不科學了!??!

          SXSSFWorkbook

          從POI 3.8版本開始,提供了一種基于XSSF的低內(nèi)存占用的SXSSF方式:

          優(yōu)點:

          • 這種方式不會一般不會出現(xiàn)內(nèi)存溢出(它使用了硬盤來換取內(nèi)存空間,
          • 也就是當內(nèi)存中數(shù)據(jù)達到一定程度這些數(shù)據(jù)會被持久化到硬盤中存儲起來,而內(nèi)存中存的都是最新的數(shù)據(jù)),
          • 并且支持大型Excel文件的創(chuàng)建(存儲百萬條數(shù)據(jù)綽綽有余)。

          缺點:

          • 既然一部分數(shù)據(jù)持久化到了硬盤中,且不能被查看和訪問那么就會導致,
          • 在同一時間點我們只能訪問一定數(shù)量的數(shù)據(jù),也就是內(nèi)存中存儲的數(shù)據(jù);
          • sheet.clone()方法將不再支持,還是因為持久化的原因;
          • 不再支持對公式的求值,還是因為持久化的原因,在硬盤中的數(shù)據(jù)沒法讀取到內(nèi)存中進行計算;
          • 在使用模板方式下載數(shù)據(jù)的時候,不能改動表頭,還是因為持久化的問題,寫到了硬盤里就不能改變了;


          2 使用方式哪種看情況

          經(jīng)過了解也知道了這三種Workbook的優(yōu)點和缺點,那么具體使用哪種方式還是需要看情況的:

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

          1、當我們經(jīng)常導入導出的數(shù)據(jù)不超過7w的情況下,可以使用 HSSFWorkbook 或者 XSSFWorkbook都行;

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

          3、當數(shù)據(jù)量查過7w,并且我們需要操做Excel中的表頭,樣式,公式等,這時候我們可以使用 XSSFWorkbook 配合進行分批查詢,分批寫入Excel的方式來做;


          3 百萬數(shù)據(jù)導入導出(正菜)

          鋪墊也做了不少,那么現(xiàn)在開始講講我在工作中遇到的超百萬數(shù)據(jù)的導入導出解決方案:

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

          1、 我遇到的數(shù)據(jù)量超級大,使用傳統(tǒng)的POI方式來完成導入導出很明顯會內(nèi)存溢出,并且效率會非常低;

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

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

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

          5、 導入時300萬數(shù)據(jù)存儲到DB如果循環(huán)一條條插入也肯定不行;

          6、導入時300w數(shù)據(jù)如果使用Mybatis的批量插入肯定不行,因為Mybatis的批量插入其實就是SQL的循環(huán);一樣很慢。

          解決思路:

          針對1 :

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

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

          針對2:

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

          針對3:

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

          針對4:

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

          針對5:

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

          針對6:

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

          3.1 EasyExcel 簡介

          附上GitHub地址:

          https://github.com/alibaba/easyexcel

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

          至于EasyExcel底層怎么實現(xiàn)的這個還有待研究。

          3.2 300w數(shù)據(jù)導出

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

          300w數(shù)據(jù)的導出解決思路:

          • 首先在查詢數(shù)據(jù)庫層面,需要分批進行查詢(我使用的是每次查詢20w)
          • 每查詢一次結(jié)束,就使用EasyExcel工具將這些數(shù)據(jù)寫入一次;
          • 當一個Sheet寫滿了100w條數(shù)據(jù),開始將查詢的數(shù)據(jù)寫入到另一個Sheet中;
          • 如此循環(huán)直到數(shù)據(jù)全部導出到Excel完畢。

          注意:

          1、我們需要計算Sheet個數(shù),以及循環(huán)寫入次數(shù)。特別是最后一個Sheet的寫入次數(shù)

          因為你不知道最后一個Sheet選喲寫入多少數(shù)據(jù),可能是100w,也可能是25w因為我們這里的300w只是模擬數(shù)據(jù),有可能導出的數(shù)據(jù)比300w多也可能少

          2、我們需要計算寫入次數(shù),因為我們使用的分頁查詢,所以需要注意寫入的次數(shù)。

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

          //導出邏輯代碼
          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);

                      //模擬統(tǒng)計查詢的數(shù)據(jù)數(shù)量這里模擬100w
                      int count = 3000001;
                      //記錄總數(shù):實際中需要根據(jù)查詢條件進行統(tǒng)計即可
                      Integer totalCount = actResultLogMapper.findActResultLogByCondations(count);
                      //每一個Sheet存放100w條數(shù)據(jù)
                      Integer sheetDataRows = ExcelConstants.PER_SHEET_ROW_COUNT;
                      //每次寫入的數(shù)據(jù)量20w
                      Integer writeDataRows = ExcelConstants.PER_WRITE_ROW_COUNT;
                      //計算需要的Sheet數(shù)量
                      Integer sheetNum = totalCount % sheetDataRows == 0 ? (totalCount / sheetDataRows) : (totalCount / sheetDataRows + 1);
                      //計算一般情況下每一個Sheet需要寫入的次數(shù)(一般情況不包含最后一個sheet,因為最后一個sheet不確定會寫入多少條數(shù)據(jù))
                      Integer oneSheetWriteCount = sheetDataRows / writeDataRows;
                      //計算最后一個sheet需要寫入的次數(shù)
                      Integer lastSheetWriteCount = totalCount % sheetDataRows == 0 ? oneSheetWriteCount : (totalCount % sheetDataRows % writeDataRows == 0 ? (totalCount / sheetDataRows / writeDataRows) : (totalCount / sheetDataRows / writeDataRows + 1));

                      //開始分批查詢分次寫入
                      //注意這次的循環(huán)就需要進行嵌套循環(huán)了,外層循環(huán)是Sheet數(shù)目,內(nèi)層循環(huán)是寫入次數(shù)
                      List<List<String>> dataList = new ArrayList<>();
                      for (int i = 0; i < sheetNum; i++) {
                          //創(chuàng)建Sheet
                          Sheet sheet = new Sheet(i, 0);
                          sheet.setSheetName("測試Sheet1" + i);
                          //循環(huán)寫入次數(shù): j的自增條件是當不是最后一個Sheet的時候?qū)懭氪螖?shù)為正常的每個Sheet寫入的次數(shù),如果是最后一個就需要使用計算的次數(shù)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()));
                                  });
                              }
                              //寫數(shù)據(jù)
                              writer.write0(dataList, sheet, table);
                          }
                      }

                      // 下載EXCEL
                      response.setHeader("Content-Disposition""attachment;filename=" + new String((fileName).getBytes("gb2312"), "ISO-8859-1") + ".xlsx");
                      response.setContentType("multipart/form-data");
                      response.setCharacterEncoding("utf-8");
                      writer.finish();
                      outputStream.flush();
                      //導出時間結(jié)束
                      long endTime = System.currentTimeMillis();
                      System.out.println("導出結(jié)束時間:" + 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();
                          }
                      }
                  }
              }
          }
          3.2.1 測試機狀態(tài)

          下面是測試機配置

          圖片




          3.2.2 使用數(shù)據(jù)庫版本

          數(shù)據(jù)庫我使用的是Oracle19C在網(wǎng)上查閱其實在數(shù)據(jù)量不超過1億的情況下,Mysql和Oracle的性能其實相差不大,超過1億,Oracle的各方面優(yōu)勢才會明顯。

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

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

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

          <select id="findByPage300w" 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
            );
          3.2.3 測試結(jié)果

          下面是300w數(shù)據(jù)從DB導出到Excel所用時間

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

          綜合來說速度還算可以。

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

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

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



          3.2.4 導出小結(jié)

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

          導出測試就到這里,對于數(shù)據(jù)量小于300W的數(shù)據(jù)可以使用在一個Sheet中進行導出。這里就不再演示。

          3.3 300w數(shù)據(jù)導入

          代碼不重要首先還是思路

          300W數(shù)據(jù)的導入解決思路

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

          2、其次就是往DB里插入,怎么去插入這20w條數(shù)據(jù),當然不能一條一條的循環(huán),應該批量插入這20w條數(shù)據(jù),同樣也不能使用Mybatis的批量插入語,因為效率也低??梢詤⒖枷旅骀溄印綧yabtis批量插入和JDBC批量插入性能對比】

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

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

          3.3.1 數(shù)據(jù)庫數(shù)據(jù)(導入前)

          如圖


          3.3.2 核心業(yè)務代碼
          // EasyExcel的讀取Excel數(shù)據(jù)的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時間(包括導入數(shù)據(jù)過程):" + startReadTime + "ms------");
              //讀取所有Sheet的數(shù)據(jù).每次讀完一個Sheet就會調(diào)用這個方法
              EasyExcel.read(fileName, new EasyExceGeneralDatalListener(actResultLogService2)).doReadAll();
              long endReadTime = System.currentTimeMillis();
              System.out.println("------結(jié)束讀取Excel的Sheet時間(包括導入數(shù)據(jù)過程):" + endReadTime + "ms------");
          }
          // 事件監(jiān)聽
          public class EasyExceGeneralDatalListener extends AnalysisEventListener<Map<Integer, String>> {
              /**
               * 處理業(yè)務邏輯的Service,也可以是Mapper
               */
              private ActResultLogService2 actResultLogService2;

              /**
               * 用于存儲讀取的數(shù)據(jù)
               */
              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) {
                  //數(shù)據(jù)add進入集合
                  dataList.add(data);
                  //size是否為100000條:這里其實就是分批.當數(shù)據(jù)等于10w的時候執(zhí)行一次插入
                  if (dataList.size() >= ExcelConstants.GENERAL_ONCE_SAVE_TO_DB_ROWS) {
                      //存入數(shù)據(jù)庫:數(shù)據(jù)小于1w條使用Mybatis的批量插入即可;
                      saveData();
                      //清理集合便于GC回收
                      dataList.clear();
                  }
              }

              /**
               * 保存數(shù)據(jù)到DB
               *
               * @param
               * @MethodName: saveData
               * @return: void
               */
              private void saveData() {
                  actResultLogService2.import2DBFromExcel10w(dataList);
                  dataList.clear();
              }
              
              /**
               * Excel中所有數(shù)據(jù)解析完畢會調(diào)用此方法
               *
               * @param: context
               * @MethodName: doAfterAllAnalysed
               * @return: void
               */
              @Override
              public void doAfterAllAnalysed(AnalysisContext context) {
                  saveData();
                  dataList.clear();
              }
          }
          //JDBC工具類
          public class JDBCDruidUtils {
              private static DataSource dataSource;

              /*
             創(chuàng)建數(shù)據(jù)Properties集合對象加載加載配置文件
              */
              static {
                  Properties pro = new Properties();
                  //加載數(shù)據(jù)庫連接池對象
                  try {
                      //獲取數(shù)據(jù)庫連接池對象
                      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();
              }


              /**
               * 關(guān)閉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();
                      }
                  }
              }

              /**
               * 關(guān)閉 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中具體業(yè)務邏輯

          /**
           * 測試用Excel導入超過10w條數(shù)據(jù),經(jīng)過測試發(fā)現(xiàn),使用Mybatis的批量插入速度非常慢,所以這里可以使用 數(shù)據(jù)分批+JDBC分批插入+事務來繼續(xù)插入速度會非???br> *
           * @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<>();
              //結(jié)果集中數(shù)據(jù)為0時,結(jié)束方法.進行下一次調(diào)用
              if (dataList.size() == 0) {
                  result.put("empty""0000");
                  return result;
              }
              //JDBC分批插入+事務操作完成對10w數(shù)據(jù)的插入
              Connection conn = null;
              PreparedStatement ps = null;
              try {
                  long startTime = System.currentTimeMillis();
                  System.out.println(dataList.size() + "條,開始導入到數(shù)據(jù)庫時間:" + 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);
                  //循環(huán)結(jié)果集:這里循環(huán)不支持"爛布袋"表達式
                  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));
                      //將一組參數(shù)添加到此 PreparedStatement 對象的批處理命令中。
                      ps.addBatch();
                  }
                  //執(zhí)行批處理
                  ps.executeBatch();
                  //手動提交事務
                  conn.commit();
                  long endTime = System.currentTimeMillis();
                  System.out.println(dataList.size() + "條,結(jié)束導入到數(shù)據(jù)庫時間:" + endTime + "ms");
                  System.out.println(dataList.size() + "條,導入用時:" + (endTime - startTime) + "ms");
                  result.put("success""1111");
              } catch (Exception e) {
                  result.put("exception""0000");
                  e.printStackTrace();
              } finally {
                  //關(guān)連接
                  JDBCDruidUtils.close(conn, ps);
              }
              return result;
          }
          3.3.3 測試結(jié)果

          下面是300w數(shù)據(jù)邊讀邊寫用時間:

          大致計算一下:

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

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

          計算可得300w數(shù)據(jù)讀取時間為:91.095-8.209=82.886

          結(jié)果顯而易見:

          EasyExcel分批讀取300W數(shù)據(jù)只用了 82.886秒

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

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

          看一下數(shù)據(jù)庫的數(shù)據(jù)是不是真的存進去了300w

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


          3.3.4 導入小結(jié)

          具體我沒有看網(wǎng)上其他人的測試情況,這東西一般也很少有人愿意測試,不過這個速度對于我當時解決公司大數(shù)據(jù)的導入和導出已經(jīng)足夠,當然公司的業(yè)務邏輯很復雜,數(shù)據(jù)量也比較多,表的字段也比較多,導入和導出的速度會比現(xiàn)在測試的要慢一點,但是也在人類能接受的范圍之內(nèi)。

          4 總結(jié)

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

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

          最后說一下公司之前怎么做的,公司之前做法是

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

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

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

          好了到此結(jié)束,相信大神有比我做的更好的,對于EasyExcel內(nèi)部到底是怎么實現(xiàn)的還有待考究(有空我再研究研究)。

          到此文章就結(jié)束了。Java架構(gòu)師必看一個集公眾號、小程序、網(wǎng)站(3合1的文章平臺,給您架構(gòu)路上一臂之力,javajgs.com)。如果今天的文章對你在進階架構(gòu)師的路上有新的啟發(fā)和進步,歡迎轉(zhuǎn)發(fā)給更多人。歡迎加入架構(gòu)師社區(qū)技術(shù)交流群,眾多大咖帶你進階架構(gòu)師,在后臺回復“加群”即可入群。

          第23期已結(jié)束!第24期已開始,11月1號截止



          這些年小編給你分享過的干貨


          1.idea永久激活碼(親測可用)

          2.優(yōu)質(zhì)ERP系統(tǒng)帶進銷存財務生產(chǎn)功能(附源碼)

          3.優(yōu)質(zhì)SpringBoot帶工作流管理項目(附源碼)

          4.最好用的OA系統(tǒng),拿來即用(附源碼)

          5.SBoot+Vue外賣系統(tǒng)前后端都有(附源碼

          6.SBoot+Vue可視化大屏拖拽項目(附源碼)


          轉(zhuǎn)發(fā)在看就是最大的支持??

          瀏覽 75
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产成人AV导航 | 男女啪啪AV | 欧洲性爱视频在线 | 国产操视频 | jjzz亚洲 |