1. 程式人生 > 實用技巧 >easyExcel用策略模式實現匯入

easyExcel用策略模式實現匯入

在之前的一次專案中用的過Easyexcel進行一些匯入匯出操作,之前是將解析到的資料提取出來然後返回給呼叫方,讓呼叫方進行資料庫操作。
這方法雖然可行,但是面對大量資料時可能會造成oom。
本來官方文件給的參考方法就是當解析量達到一定數量時就進行儲存,然後清空列表繼續解析。根據官方文件以及這個工具類的可用性、擴充套件性。
我根據策略模式想出了一種解決方法。
在監聽器裡面宣告一個ExcelService介面,然後根據實際業務創造相應實現類。在實現類裡面注入所需的各種service進行資料庫操作
下面貼上程式碼

監聽器類

package com.konka.dsp.api.elecsign.service.util;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.fastjson.JSON;

import lombok.extern.slf4j.Slf4j;


import java.util.ArrayList;
import java.util.List;

/**
 * @author ydmy
 * @date :2020/11/6 10:23
 */
@Slf4j
public class BaseHandlerListener<T> extends AnalysisEventListener<T> {
    /**
     * 每隔5條儲存資料庫,實際使用中可以3000條,然後清理list ,方便記憶體回收
     */
    private static final int BATCH_COUNT = 3000;
    private final ExcelService<T> excelService;
    private final List<T> list = new ArrayList<T>();
    /**
     * 假設這個是一個DAO,當然有業務邏輯這個也可以是一個service。當然如果不用儲存這個物件沒用。
     * @param customerService customerService
     */

//    public CustomerListener(Y customerService) {
//        // 這裡是demo,所以隨便new一個。實際使用如果到了spring,請使用下面的有參建構函式
//        this.customerService = customerService;
//    }


    public BaseHandlerListener(ExcelService<T> excelService) {
        this.excelService = excelService;
    }

    /**
     * 這個每一條資料解析都會來呼叫
     *
     * @param data
     *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context context
     */
    @Override
    public void invoke(T data, AnalysisContext context) {
        log.info("解析到一條資料:{}", JSON.toJSONString(data));
        list.add(data);
        // 達到BATCH_COUNT了,需要去儲存一次資料庫,防止資料幾萬條資料在記憶體,容易OOM
        if (list.size() >= BATCH_COUNT) {
            handleSaveData();
            // 儲存完成清理 list
            list.clear();
        }
    }

    /**
     * 所有資料解析完成了 都會來呼叫
     *
     * @param context context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 這裡也要儲存資料,確保最後遺留的資料也儲存到資料庫
        handleSaveData();
        log.info("所有資料解析完成!");
    }

    /**
     * 加上儲存資料庫
     */
    private void handleSaveData() {
        log.info("{}條資料,開始儲存資料庫!", list.size());
        excelService.handleSaveImportData(list);
        log.info("儲存資料庫成功!");
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        log.error("解析失敗:{}", exception.getMessage());
        // 如果是某一個單元格的轉換異常 能獲取到具體行號
        // 如果要獲取頭的資訊 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("第{}行,第{}列解析異常", excelDataConvertException.getRowIndex(),
                excelDataConvertException.getColumnIndex());
            throw new RuntimeException("第"+excelDataConvertException.getRowIndex()+"行,第"+excelDataConvertException.getColumnIndex()+"列解析異常");
        }
    }
}

Service介面

public interface ExcelService<T> {
    void handleSaveImportData(List<T> dataList);
}

通過構造方法進行自動注入
ServiceImpl類

public class ElecsignCerteExcelServiceImpl implements ExcelService<ElecsignCertExcelDTO> {
    private ElecsignCertService elecsignCertService;

    public ElecsignCerteExcelServiceImpl(ElecsignCertService elecsignCertService) {
        this.elecsignCertService = elecsignCertService;
    }

    @Override
    public void handleSaveImportData(List<ElecsignCertExcelDTO> dataList) {
            elecsignCertService.saveAll(dataList);
    }
}