1. 程式人生 > 其它 >利用EasyExcel匯入匯出多個sheet和多個table

利用EasyExcel匯入匯出多個sheet和多個table

技術標籤:springJavaexcelpoi

最近專案需要匯入匯出多個sheet,並且同一個sheet裡面匯入匯出兩個或者多個Table,實現過程如下:

Excel匯出

匯出某個sheet,指定sheet名:

WriteSheet writeSheet = EasyExcel.writerSheet(tableName).build();

指定sheet中的每個表(Table)的表頭以及匯出對應的實體類,序號0,1分別表示第幾張表,head為指定表頭以及表匯出對應的實體類:

WriteTable writeTable = EasyExcel.writerTable(0).head(TableExcelData.
class).needHead(true).build(); WriteTable writeTable2 = EasyExcel.writerTable(1).head(ItemExcelData.class).needHead(true).build();

這裡我的TableExcelData.classItemExcelData.class分別是匯出Excel的實體,其中一個定義如下。這裡注意用EasyExcel匯出時,@NoArgsConstructor是必須標註的,不然會報錯;@ContentStyle(dataFormat = 49)主要指定匯出時間時Excel單元格格式為文字格式,不然為常規格式的話,“yyyy-MM-dd HH:mm:ss"

excel中點選後會變成"yyyy/MM/dd HH:mm:ss”,不利於格式的統一。

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
@Builder(toBuilder = true)
@ApiModel(value = "Excel實體", description = "資料資訊")
public class InspectionTableExcelData {
        @ExcelProperty(value = "表名稱")
        @NotNull
private String tableName; @ExcelProperty(value = "型別名稱") @NotNull private String typeName; @ExcelProperty(value = "建立人姓名") @NotNull private String creatorName; @ExcelProperty(value = "表建立時間", converter = InstantConverter.class) @NotNull @ContentStyle(dataFormat = 49) private Instant tableCreationTime; }

下面是對每個sheet的每個Table進行寫入:

excelWriter.write(ImmutableList.of(inspectionTableExcelData), writeSheet, writeTable);
excelWriter.write(inspectionItemExcelDataList, writeSheet, writeTable2);
        ExcelWriter excelWriter = null;
        try {
            excelWriter = EasyExcel.write(ExcelUtil.getOutputStream(fileName, response)).build();
            for(Long id:idList) {

                WriteSheet writeSheet = EasyExcel.writerSheet(tableExcelData.getTableName()).build();

                WriteTable writeTable = EasyExcel.writerTable(0).head(TableExcelData.class).needHead(true).build();
                WriteTable writeTable2 = EasyExcel.writerTable(1).head(ItemExcelData.class).needHead(true).build();

                excelWriter.write(ImmutableList.of(tableExcelData), writeSheet, writeTable);
                excelWriter.write(itemExcelDataList, writeSheet, writeTable2);
            }
        }catch(Exception e){
   
            throw new MMSException(FAILED_TO_EXPORT.getCode(),"匯出表失敗",FAILED_TO_EXPORT.getMsg());
        }finally {
            if (excelWriter!=null){
                excelWriter.finish();
            }
        }

Excel匯入

匯入相對複雜些,需要實現如下同一個sheet匯入兩個Table,第一個Table只有一項,第一個Table有若干項,經過多番查閱和實驗最終得到結果:
在這裡插入圖片描述
主要有以下幾點:
dest為需要讀取的檔案Filefile為上傳的MultiFile類檔案,這裡需要擴充套件AnalysisEventListener並對一些方法進行重寫,特別是需要自己實現儲存資料的方法,需要自己傳入一些mapper或者converter之類。
以下是通過讀取dest檔案,從而獲得我們上傳檔案的sheetList

List<ReadSheet> readSheetList = EasyExcel.read(dest).build().excelExecutor().sheetList();

對於List裡面的每個ReadSheet也就是對於每個sheet,我們進行一一匯入,如下,通過sheet(readSheet.getSheetName())讀取某個sheet,通過指定java spring需要匹配的實體類(TableExcelData.class)以及我們的Listener

EasyExcel.read(dest, TableExcelData.class, new TableExcelListener<TableExcelData>(tableMapper,tableConverter, tableExcelConverter))
          .sheet(readSheet.getSheetName())
          .doRead();
EasyExcel.read(dest, ItemExcelData.class, new TableExcelListener<ItemExcelData>(ItemMapper, itemConverter,itemExcelConverter))
          .sheet(readSheet.getSheetName())
          .headRowNumber(3)
          .doRead();

headRowNumber(3)表示我們第一個TableExcel的第3行為表頭開始讀,這裡有個問題,就是我們在讀取sheet的時候,EasyExcel預設是讀取全部的,因此在讀第一個Table的同時也會將第二個Table包括表頭和內容都讀入,並且用第一個表的class進行匹配,顯而易見,這樣會出錯。因此我們需要指定第一個Table的停止讀取的邏輯,由於我第一個Table只需要讀一項,因此當讀取到第一項就需要停止讀取。網上查閱資料並沒有發現EasyExcel怎麼能夠實現這個功能,通過去讀EasyExcel原始碼ReadListener發現其有個hasNext方法,其方法解釋為,我們可以通過返回false從而讓EasyExcel停止讀取。這裡因為我們第一個Table只讀取一項就停止了。因此我們可以在繼承的Listener上實現hasNext()方法,這裡通過判斷我們用於臨時儲存我們實體的一個list,如果list的size等於1了,就令hasNext返回false

    /**
     * Verify that there is another piece of data.You can stop the read by returning false
     *
     * @param context
     * @return
     */
    boolean hasNext(AnalysisContext context);
}

Listener完整程式碼如下:

@Component
public class TableExcelListener extends AnalysisEventListener<TableExcelData> {

    private static final Logger LOGGER = LoggerFactory.getLogger(TableExcelListener.class);

    private static final int BATCH_COUNT = 5;

    List<TableExcelData> list = new ArrayList<>();

    private TableMapper tableMapper;
    private TableConverter tableConverter;
    private TableExcelConverter tableExcelConverter;

    public TableExcelListener(TableMapper tableMapper, TableConverter tableConverter, TableExcelConverter tableExcelConverter){
        this.tableMapper = tableMapper;
        this.tableConverter = tableConverter;
        this.tableExcelConverter = tableExcelConverter;
    }
    // 一條一條資料解析  invoke()方法
    @Override
    public void invoke(TableExcelData tableExcelData, AnalysisContext analysisContext){
        list.add(tableExcelData);
        if(list.size()>=BATCH_COUNT){
            saveData();
            list.clear();
        }
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) {
        LOGGER.error("解析失敗,但是繼續解析下一行:{}", exception.getMessage());
        // 如果是某一個單元格的轉換異常 能獲取到具體行號
        // 如果要獲取頭的資訊 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            LOGGER.error("第{}行,第{}列解析異常", excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex());
            // ASException為自定義異常
            exception.printStackTrace;
        }
    }
    // 所有資料解析完, doAfterAllAnalysed()方法,裡面寫的有儲存資料方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext){
        saveData();
        LOGGER.info("所有資料解析完成!");
    }
    
    // 業務邏輯,實現儲存資料的方法
    private void saveData(){
        for(TableExcelData tableExcelData:list){

        try{
            TableDTO tableDTO = tableExcelConverter.toDTO(tableExcelData);
            TableDO tableDO = tableConverter.toDO(tableDTO);
            this.tableMapper.addTable(tableDO);
        }catch(Exception e){
            e.printStackTrace;
        }
        LOGGER.info("{}條資料,開始儲存資料庫!",list.size());
        LOGGER.info("儲存資料庫成功!");
        }
    }
    // 停止條件,如果list元素為1個時,停止讀取資料
    @Override
    public boolean hasNext(AnalysisContext analysisContext){
        if(list.size()>=1){
            return false;
        }
        return true;
    }
}

最後貼一下實現上傳的程式碼:

  public int uploadTableExcel(MultipartFile file) {
        String fileName = file.getOriginalFilename();

        // TODO 怎麼處理路徑 不生成臨時檔案
        String tmpPath = System.getProperty("user.dir") + "/tmpPath/";
        File dest = new File(tmpPath+fileName);
        if(!dest.getParentFile().exists()){
            dest.getParentFile().mkdirs();
        }

        try{
            // 用來把 MultipartFile   轉換成  File
            file.transferTo(dest);
        } catch (Exception e) {
            e.printStackTrace;
        }
        
        try {
            List<ReadSheet> readSheetList = EasyExcel.read(dest).build().excelExecutor().sheetList();
            for(ReadSheet readSheet:readSheetList) {
                EasyExcel.read(dest, TableExcelData.class, new TableExcelListener<TableExcelData>(tableMapper,tableConverter, tableExcelConverter))
                        .sheet(readSheet.getSheetName())
                        .doRead();
                EasyExcel.read(dest, ItemExcelData.class, new TableExcelListener<ItemExcelData>(ItemMapper, itemConverter,itemExcelConverter))
                        .sheet(readSheet.getSheetName())
                        .headRowNumber(3)
                        .doRead();
            }
        } catch (Exception e) {
            e.printStackTrace;
        }
}

參考文章:
easyexcel 使用table寫入
easyexcel原始碼
急!請問EasyExcel如何獲取sheetname?
EasyExcel從指定位置開始讀資料
EasyExceld讀取流程圖