最近项目需要导入导出多个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读取流程图
官方文档
Easyexcel github