利用EasyExcel匯入匯出多個sheet和多個table
最近專案需要匯入匯出多個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.class
和ItemExcelData.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
為需要讀取的檔案File
,file
為上傳的MultiFile
類檔案,這裡需要擴充套件AnalysisEventListener
並對一些方法進行重寫,特別是需要自己實現儲存資料的方法,需要自己傳入一些mapper
或者converter
之類。
以下是通過讀取dest
檔案,從而獲得我們上傳檔案的sheet
的List
;
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)
表示我們第一個Table
從Excel
的第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讀取流程圖