基本上就是官方的快速开始,自己走了一遍,并笔记。没有完全照抄官方示例,正好可以做对比。
另外为了方便还引入了fastjson 2.0.3, lombok 1.18.24, junit 4.11
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
测试的 excel 放在此处:
private String filePath = "E:\\temp\\测试数据.xlsx";
| 用法 | 说明 |
|---|---|
@ExcelProperty(index = 0) | 指定列的索引 |
@ExcelProperty("日期标题") | 指定列名 |
@ExcelProperty(converter = CustomStringStringConverter.class) | 自定义转换器 |
@DateTimeFormat("yyyy-MM-dd HH:mm:ss") | string 接格式化的日期 |
@NumberFormat("#.##%") | string 接格式的化百分比数字 |
@Data
@EqualsAndHashCode
public class DemoData {
// @ExcelProperty(index = 0)
private String string;
// @ExcelProperty("日期标题")
private Date date;
// @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
// private String dateStr;
private Double doubleData;
}
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class<?> supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/** 这里读的时候会调用 */
@Override
public String convertToJavaData(ReadConverterContext<?> context) {
return "自定义:" + context.getReadCellData().getStringValue();
}
/** 这里是写的时候会调用 */
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
return new WriteCellData<>(context.getValue());
}
}
BATCH_COUNT = 100条数据。然后进行消费。invoke:每一行读取完毕会调用。doAfterAllAnalysed:每个sheet读取完毕会调用。Consumer<List<T>> consumer。(invoke、doAfterAllAnalysed会调用消费者)DemoData.class ,读取sheet(默认索引0)后,文件流会自动关闭。excel。EasyExcel.read()返回的 ExcelReaderBuilder 可以配置。sheet。.sheet() 返回的 ExcelReaderSheetBuilder 可以配置。@Test
public void simpleRead() {
// 这个消费者在 PageReadListener 的 invoke、doAfterAllAnalysed 中会被调用。
Consumer<List<DemoData>> consumer = dataList -> {
dataList.forEach(demoData -> log.info("读取到一条数据{}", JSON.toJSONString(demoData)));
};
// 用上面的消费者创建监听器
PageReadListener<DemoData> myListener = new PageReadListener<>(consumer);
// 开始读取
EasyExcel.read(fileName, DemoData.class, myListener).sheet().doRead();
}
匿名类实现 ReadListener接口。重写了invoke、doAfterAllAnalysed这两个方法就行了。具名类实现 ReadListener接口得到一个监听器。和上面只是具名类与匿名类的区别。
一个文件一个 ExcelReader
// 这里直接用了前文中的监听器 myListener
try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, myListener ).build()) {
// 构建一个sheet 这里可以指定名字或者索引(注意我给的索引是第二个sheet)
ReadSheet readSheet = EasyExcel.readSheet(1).build();
// 读取这个sheet
excelReader.read(readSheet);
}
如果头有多行,那么可以指定跳过多少行,开始读取。
只要加了一个 headRowNumber 用于说明头有多少行。
@Test
public void headRead() {
EasyExcel.read(filePath, new NoModelDataListener()).sheet().headRowNumber(7).doRead();
}
节约篇幅我这里直接用了 PageReadListener。
这里需要注意:每个sheet读取完毕后调用一次doAfterAllAnalysed。
因为这里只给了一个监听器,最后所有sheet都会写进同一个监听器里。(直观的效果就是如下代码每次输出都包含上一次的内容。人个理解是要处理数据的话,在读完最后一个sheet时再一起处理就好了。)
EasyExcel.read(fileName, DemoData.class, new PageReadListener<>(dataList -> {
dataList.forEach(demoData -> log.info("读取到一条数据{}", JSON.toJSONString(demoData)));
})).doReadAll();
这里读取第1与第3个表,每个sheet对应各自的监听器,所以是分开输出的。
如果用同一个监听器,则效果与上面一样。
// 监听器
Consumer<List<DemoData>> consumer = dataList -> {
dataList.forEach(demoData -> log.info("读取到一条数据{}", JSON.toJSONString(demoData)));
};
// 用上面的监听器,生成 ReadSheet 列表。(第1、第3 个Sheet)
List<ReadSheet> sheets = Stream.of(0, 2)
.map(i -> EasyExcel.readSheet(i)
.head(DemoData.class)
.registerReadListener(new PageReadListener<>(consumer))
.build())
.collect(Collectors.toList());
// 读取 sheets 中的表
try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {
excelReader.read(sheets);
}
与最简单的读的区别只是把doRead 换成 doReadSync。
下面是不带head(DemoData.class)的写法。直接用List<Map<Integer, String>>接。
List<Map<Integer, String>> objects = EasyExcel.read(fileName).sheet().doReadSync();
objects.forEach(demoData -> log.info("读取到一条数据{}", JSON.toJSONString(demoData)));
懒得从头实现一个监听器,直接用 SyncReadListener 复写 nvokeHeadMap 。
这里设置了headRowNumber(3) 头有3行。
SyncReadListener syncReadListener = new SyncReadListener() {
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
}
};
EasyExcel.read(fileName, DemoData.class, syncReadListener).sheet().headRowNumber(3).doRead();
官网示例代码 中按类型分别判断并处理有点长,我这直接输出就好了。
同样偷个懒重写 SyncReadListener 直接用。
SyncReadListener syncReadListener = new SyncReadListener() {
@Override
public void extra(CellExtra extra, AnalysisContext context) {
log.info("\n额外信息的类型(批注、超链接、合并单元格)是:{};" +
"\n行:{},列{};\n开始行:{},开始列{};\n结束行:{},结束例:{};\n内容为:{}",
extra.getType(),
extra.getRowIndex(), extra.getColumnIndex(),
extra.getFirstRowIndex(), extra.getFirstColumnIndex(),
extra.getLastRowIndex(), extra.getLastColumnIndex(),
extra.getText());
}
};
// 三种额外信息默认都是不读取,需要手动添加。
EasyExcel.read(fileName, null, syncReadListener)
.extraRead(CellExtraTypeEnum.COMMENT)// 读取批注
.extraRead(CellExtraTypeEnum.HYPERLINK)// 读取超链接
.extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();// 读取合并单元格信息
关键在于实体中的字体用 CellData 进行包裹,就能拿到单元格信息了。
注意:字段顺序要与表格列对应
@Data
@EqualsAndHashCode
@JSONType(orders = {"string", "date", "doubleData", "formulaValue"}) // 设置下顺序好看些
public class CellDataReadDemoData {
private CellData<String> string;
private CellData<Date> date; // excel 中的日期值其实是数字
private CellData<Double> doubleData;
private CellData<String> formulaValue; // 不一定能完美的获取,等官方后续
}
List<CellDataReadDemoData> objects = EasyExcel.read(fileName).head(CellDataReadDemoData.class).sheet().doReadSync();
objects.forEach(demoData -> log.info("解析到一条数据{}", JSON.toJSONString(demoData)));
这里表的内容

这是一行的效果
{
"string":{
"data":"字符串0","dataFormatData":{"format":"General","index":0},"stringValue":"字符串0","type":0
},
"date":{
"data":"2022-06-27 00:00:00","dataFormatData":{"format":"yyyy/m/d","index":14},"numberValue":44739.0,"type":2
},
"doubleData":{
"data":1.0,"dataFormatData":{"format":"General","index":0},"numberValue":1.0,"type":2
},
"formulaValue":{
"data":"字符串0447391","dataFormatData":{"format":"General","index":0},
"formulaData":{"formulaValue":"A2&B2&C2"},"stringValue":"字符串0447391","type":0
}
}
@Data
@EqualsAndHashCode
public class ExceptionDemoData {
private Date date;
}
ReadListener<ExceptionDemoData> readListener = new ReadListener<ExceptionDemoData>() {
@Override
public void invoke(ExceptionDemoData data, AnalysisContext context) { }
@Override
public void doAfterAllAnalysed(AnalysisContext context) { }
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
log.info("解析失败,但是继续解析下一行:{}", exception.getMessage());
}
};
List<ExceptionDemoData> objects = EasyExcel.read(fileName, ExceptionDemoData.class, readListener) .sheet().doReadSync();
objects.forEach(demoData -> log.info("解析到一条数据{}", JSON.toJSONString(demoData)));
System.out.println("正常结束");
官方说的是在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。但我还是被终断了执行,不知道是不是我理解的不对。

正常创建按表字段,创建实体,然后读数据,没什么好说的。这里看一下:不创建对象的读
就是官方代码,只是补充了一下注释。
package com.jerry.excel.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
@Slf4j
public class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
private List<Map<Integer, String>> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 每一行读取完毕会调用
* @param data 当前行数据
* @param context
*/
@Override
public void invoke(Map<Integer, String> data, AnalysisContext context) {
log.info("解析到一条数据:{}", JSON.toJSONString(data));
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 每个sheet读取完毕会调用
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
log.info("所有数据解析完成!");
}
/**
* 写入数据库操作
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
log.info("存储数据库成功!");
}
}
这注释也是官方的,好像少字了。
@Test
public void noModelRead() {
// 这里 只要,然后读取第一个sheet 同步读取会自动finish
EasyExcel.read(filePath, new NoModelDataListener()).sheet().doRead();
}
从上传的 Excel 中读取数据到 List<DemoData> 并返回。用到了同步读取doReadSync()
@Slf4j
@RestController
public class ExcelControllor {
@PostMapping("upload")
public List<DemoData> importExcel(MultipartFile file) throws IOException {
return EasyExcel.read(file.getInputStream(), DemoData.class, new PageReadListener<DemoData>(dataList -> {
dataList.forEach(demoData -> log.info("读取到一条数据{}", JSON.toJSONString(demoData)));
})).sheet().doReadSync();
}
}
无实体版本
@PostMapping("upload")
public HashMap<String, Object> importExcel(MultipartFile file) throws IOException {
return new HashMap<String, Object>(){{
put("code", 200);
put("data", EasyExcel.read(file.getInputStream()).sheet().doReadSync());
}};
}
Easy Excel 官网
Easy Excel 官方文档:快速开始 (常规操作这里都有,照做就行了)
Easy Excel 官方文档:读excel-通用参数
🏆【Alibaba中间件技术系列】「EasyExcel实战案例」实战研究一下EasyExcel如何从指定文件位置进行读取数据
🏆【Alibaba 工具型技术系列】「EasyExcel 技术专题」实战技术针对于项目中常用的 Excel 操作指南
🏆【Alibaba 工具型技术系列】「EasyExcel 技术专题」摒除 OOM!让你的 Excel 操作变得更加优雅和安全