這個案例實際上涉及到多個方面,需要我們系統(tǒng)地分析。讓我們首先看看,從Excel中讀取百萬級數(shù)據(jù)并將其插入數(shù)據(jù)庫時可能遇到的問題:
加載如此龐大的Excel數(shù)據(jù)可能導(dǎo)致內(nèi)存溢出,需要注意內(nèi)存管理。
處理百萬級數(shù)據(jù)的讀取和插入操作可能很耗時,性能優(yōu)化至關(guān)重要。
讀取和導(dǎo)入過程中會有各種潛在問題,我們需妥善處理各類異常情況。
處理百萬級數(shù)據(jù),直接加載到內(nèi)存中顯然不現(xiàn)實。解決之道在于采用流式讀取,分批處理數(shù)據(jù)。
在技術(shù)選型上,選擇EasyExcel是明智之舉。它專為處理大數(shù)據(jù)量和復(fù)雜Excel文件進(jìn)行了優(yōu)化。EasyExcel在解析Excel時,不會將整個文件一次性加載到內(nèi)存中,而是按行從磁盤逐個讀取數(shù)據(jù)并解析。
針對百萬級數(shù)據(jù)的處理,單線程顯然效率低下。提升性能的關(guān)鍵在于多線程處理。
多線程應(yīng)用涉及兩個場景:一是多線程讀取文件,另一個是多線程實現(xiàn)數(shù)據(jù)插入。這涉及到生產(chǎn)者-消費者模式,多線程讀取并多線程插入,以最大程度提升整體性能。
在數(shù)據(jù)插入方面,除了利用多線程,還應(yīng)當(dāng)結(jié)合數(shù)據(jù)庫的批量插入功能以進(jìn)一步提升速度。
在文件讀取和數(shù)據(jù)庫寫入過程中,可能遇到諸多問題,如數(shù)據(jù)格式錯誤、不一致性和重復(fù)數(shù)據(jù)等。
因此,應(yīng)分兩步處理。首先進(jìn)行數(shù)據(jù)檢查,在插入操作前檢查數(shù)據(jù)格式等問題,然后在插入過程中處理異常情況。
處理方式多種多樣,可通過事務(wù)回滾或記錄日志。一般不推薦直接回滾操作,而是自動重試,若嘗試多次仍無效,則記錄日志,隨后重新插入數(shù)據(jù)。
此外,在這一過程中,需考慮數(shù)據(jù)重復(fù)問題,可在Excel中設(shè)定若干字段為數(shù)據(jù)庫唯一約束。遇到數(shù)據(jù)沖突時,可覆蓋、跳過或報錯處理。根據(jù)實際業(yè)務(wù)情況選擇合適的處理方式,一般情況下,跳過并記錄日志是相對合理的選擇。
所以,總體方案如下:
利用EasyExcel進(jìn)行Excel數(shù)據(jù)讀取,因其逐行讀取數(shù)據(jù)而非一次性加載整個文件至內(nèi)存。為提高并發(fā)效率,將百萬級數(shù)據(jù)分布在不同的工作表中,利用線程池和多線程同時讀取各個工作表。在讀取過程中,借助EasyExcel的ReadListener進(jìn)行數(shù)據(jù)處理。
在處理過程中,并非每條數(shù)據(jù)都直接操作數(shù)據(jù)庫,以免對數(shù)據(jù)庫造成過大壓力。設(shè)定一個批次大小,例如每1000條數(shù)據(jù),將從Excel中讀取的數(shù)據(jù)臨時存儲在內(nèi)存中(可使用List實現(xiàn))。每讀取1000條數(shù)據(jù)后,執(zhí)行數(shù)據(jù)的批量插入操作,可簡單地借助mybatis實現(xiàn)批量插入。
此外,在處理過程中,需要考慮并發(fā)問題,因此我們將使用線程安全的隊列來存儲內(nèi)存中的臨時數(shù)據(jù),如ConcurrentLinkedQueue。
經(jīng)驗證,通過上述方案,讀取并插入100萬條數(shù)據(jù)的Excel所需時間約為100秒,不超過2分鐘。
為了提升并發(fā)處理能力,我們將百萬級數(shù)據(jù)存儲在同一個Excel文件的不同工作表中,然后通過EasyExcel并發(fā)地讀取這些工作表數(shù)據(jù)。
EasyExcel提供了ReadListener接口,允許在每批數(shù)據(jù)讀取后進(jìn)行自定義處理。我們可以基于這一功能實現(xiàn)文件的分批讀取。
首先,需要添加以下依賴:
<dependencies> <!-- EasyExcel --> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>latest_version</version> </dependency> <!-- 數(shù)據(jù)庫連接和線程池 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency></dependencies>
然后實現(xiàn)并發(fā)讀取多個sheet的代碼:
@Servicepublic class ExcelImporterService { @Autowired private MyDataService myDataService; public void doImport() { // Excel文件的路徑 String filePath = "users/paidaxing/workspace/excel/test.xlsx"; // 需要讀取的sheet數(shù)量 int numberOfSheets = 20; // 創(chuàng)建一個固定大小的線程池,大小與sheet數(shù)量相同 ExecutorService executor = Executors.newFixedThreadPool(numberOfSheets); // 遍歷所有sheets for (int sheetNo = 0; sheetNo < numberOfSheets; sheetNo++) { // 在Java lambda表達(dá)式中使用的變量需要是final int finalSheetNo = sheetNo; // 向線程池提交一個任務(wù) executor.submit(() -> { // 使用EasyExcel讀取指定的sheet EasyExcel.read(filePath, MyDataModel.class, new MyDataModelListener(myDataService)) .sheet(finalSheetNo) // 指定sheet號 .doRead(); // 開始讀取操作 }); } // 啟動線程池的關(guān)閉序列 executor.shutdown(); // 等待所有任務(wù)完成,或者在等待超時前被中斷 try { executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (InterruptedException e) { // 如果等待過程中線程被中斷,打印異常信息 e.printStackTrace(); } }}
這段代碼通過創(chuàng)建一個固定大小的線程池來并發(fā)讀取一個包含多個sheets的Excel文件。每個sheet的讀取作為一個單獨的任務(wù)提交給線程池。
我們在代碼中用了一個MyDataModelListener,這個類是ReadListener的一個實現(xiàn)類。當(dāng)EasyExcel讀取每一行數(shù)據(jù)時,它會自動調(diào)用我們傳入的這個ReadListener實例的invoke方法。在這個方法中,我們就可以定義如何處理這些數(shù)據(jù)。
MyDataModelListener還包含doAfterAllAnalysed方法,這個方法在所有數(shù)據(jù)都讀取完畢后被調(diào)用。這里可以執(zhí)行一些清理工作,或處理剩余的數(shù)據(jù)。
接下來,我們來實現(xiàn)這個我們的ReadListener:
import com.alibaba.excel.context.AnalysisContext;import com.alibaba.excel.read.listener.ReadListener;import org.springframework.transaction.annotation.Transactional;import java.util.ArrayList;import java.util.List;// 自定義的ReadListener,用于處理從Excel讀取的數(shù)據(jù)public class MyDataModelListener implements ReadListener<MyDataModel> { // 設(shè)置批量處理的數(shù)據(jù)大小 private static final int BATCH_SIZE = 1000; // 用于暫存讀取的數(shù)據(jù),直到達(dá)到批量大小 private List<MyDataModel> batch = new ArrayList<>(); private MyDataService myDataService; // 構(gòu)造函數(shù),注入MyBatis的Mapper public MyDataModelListener(MyDataService myDataService) { this.myDataService = myDataService; } // 每讀取一行數(shù)據(jù)都會調(diào)用此方法 @Override public void invoke(MyDataModel data, AnalysisContext context) { //檢查數(shù)據(jù)的合法性及有效性 if (validateData(data)) { //有效數(shù)據(jù)添加到list中 batch.add(data); } else { // 處理無效數(shù)據(jù),例如記錄日志或跳過 } // 當(dāng)達(dá)到批量大小時,處理這批數(shù)據(jù) if (batch.size() >= BATCH_SIZE) { processBatch(); } } private boolean validateData(MyDataModel data) { // 調(diào)用mapper方法來檢查數(shù)據(jù)庫中是否已存在該數(shù)據(jù) int count = myDataService.countByColumn1(data.getColumn1()); // 如果count為0,表示數(shù)據(jù)不存在,返回true;否則返回false if(count == 0){ return true; } // 在這里實現(xiàn)數(shù)據(jù)驗證邏輯 return false; } // 所有數(shù)據(jù)讀取完成后調(diào)用此方法 @Override public void doAfterAllAnalysed(AnalysisContext context) { // 如果還有未處理的數(shù)據(jù),進(jìn)行處理 if (!batch.isEmpty()) { processBatch(); } } // 處理一批數(shù)據(jù)的方法 private void processBatch() { int retryCount = 0; // 重試邏輯 while (retryCount < 3) { try { // 嘗試批量插入 myDataService.batchInsert(batch); // 清空批量數(shù)據(jù),以便下一次批量處理 batch.clear(); break; } catch (Exception e) { // 重試計數(shù)增加 retryCount++; // 如果重試3次都失敗,記錄錯誤日志 if (retryCount >= 3) { logError(e, batch); }}
通過自定義MyDataModelListener,在讀取Excel文件過程中可實現(xiàn)數(shù)據(jù)處理。每讀取一條數(shù)據(jù)后,將其加入列表,在列表累積達(dá)到1000條時,執(zhí)行一次數(shù)據(jù)庫批量插入操作。若插入失敗,則進(jìn)行重試;若多次嘗試仍失敗,則記錄錯誤日志。
這里批量插入,用到了MyBatis的批量插入,代碼實現(xiàn)如下:
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapperpublic interface MyDataMapper { void batchInsert(List<MyDataModel> dataList); int countByColumn1(String column1);}
mapper.xml文件:
<insert id="batchInsert" parameterType="list"> INSERT INTO paidaxing_test_table_name (column1, column2, ...) VALUES <foreach collection="list" item="item" index="index" separator=","> (#{item.column1}, #{item.column2}, ...) </foreach></insert><select id="countByColumn1" resultType="int"> SELECT COUNT(*) FROM your_table WHERE column1 = #{column1}</select>
本文鏈接:http://www.tebozhan.com/showinfo-26-82361-0.html實現(xiàn)百萬級數(shù)據(jù)從Excel導(dǎo)入到數(shù)據(jù)庫的方式
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 您必須了解的 21 個 HTML 技巧