目录
- 1、简单关系
- 2、准备数据
- 1、Excel表格:
- 2、实体类:
- 3、监听类
- 4、业务处理类
- 3、Demo说明
- 1、启动项目
- 2、查看控制台信息
- 4、数据转换、格式处理
- 5、Web操作,上传文件处理
- 6、泛型使用
- 1、修改Controller加入读取设备的Excel接口
- 2、修改业务类,加入设备的处理逻辑
- 3、建立设备Excel及Entity
- 4、修改监听类
- 5、发起请求进行测试
注意:项目没有搭建任何前端页面进行操作,所有的操作使用Swagger2进行模拟前端
1、简单关系
在读取Excel表格时,需要依赖一个Entity类或者直接是Map类型,并且对应一个Listener
监听类,在监听类中通过继承AnalysisEventListener
实现方法,对数据进行处理操作。
以下demo主要参考官方的操作,在其中加入一些实际开发中的需求和想法。
建议参考EasyExcel官方文档再结合看Demo,EasyExcel
2、准备数据
1、Excel表格:
2、实体类:
/**
* @Date: 2020/5/1 15:13
* @Description: 人员实体类
**/
@Data
//注意属性的顺序对应excel中各个列解析的顺序,为了处理单元格为空的情况莫须有写一个Converter类,对空进行判断,并且返回默认值
public class PersonData {
//名称
private String name;
//入职时间
private String date;
//部门
private String dept;
//年薪
private Double year = 0.00;
//月薪
private Double month = 0.00;
}
3、监听类
官方的监听类没有交给Spring管理,下列demo中将监听类交给Spring进行管理;并且结合一点业务处理进行构建Demo。
@Slf4j
@Component
public class PersonDataListener extends AnalysisEventListener<PersonData> {
//引入业务处理的Server
@Resource
private ReadExcelService excelService;
//存放解析后的数据
private List<PersonData> list = new ArrayList<>();
/**
* 读取每一条头部信息
*
* @param headMap
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
}
/**
* 这个每一条数据解析都会来调用
*
* @param context
*/
@Override
public void invoke(PersonData data, AnalysisContext context) {
//调用业务类,对数据进行校验
excelService.checkPersonExcel(data, rowIndex);
list.add(data);
log.info("解析到一条数据:{}", JSON.toJSONString(data));
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", list.size());
List<PersonData> dataList = new ArrayList<>();
//调用service储存数据
excelService.savePersonExcel(dataList);
list.clear();
}
}
4、业务处理类
@Service
@Slf4j
public class ReadExcelService {
/**
* 每隔3000条存储数据库,然后清理list ,方便内存回收
*/
@Resource
private PersonDataListener personDataListener;
//读取用户选择的excel文件
public void readPeronExcel(String filePath) {
//设置headRowNumber表示从第几行开始读取,下标从0开始
// 写法1:
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭,因为excel第三行开始 才是数据
EasyExcel.read(filePath, PersonData.class, personDataListener).sheet().headRowNumber(2).doRead();
/*
// 写法2:
ExcelReader excelReader = EasyExcel.read(filePath, PersonData.class, new PersonDataListener()).build();
//指定读取的Sheet
ReadSheet readSheet = EasyExcel.readSheet(0).headRowNumber(2).build();
excelReader.read(readSheet);
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
*/
}
public void readPeronExcelByUpload(MultipartFile file) throws Exception {
//设置headRowNumber表示从第几行开始读取,下标从0开始
EasyExcel.read(file.getInputStream(), PersonData.class, personDataListener).sheet().headRowNumber(2).doRead();
}
//对导入数据进行业务判断
public void checkPersonExcel(PersonData personData, Integer rowIndex) {
if (StringUtils.isEmpty(personData.getName()) || StringUtils.isEmpty(personData.getDept()) || StringUtils.isEmpty(personData.getDate())) {
throw new ServiceException(500, "第" + rowIndex + "行,数据不能为空");
}
}
//保存有效的导入数据
public void savePersonExcel(List<PersonData> list) {
//对数据进行一些业务处理,比如将月薪大于5000的数据,分出来
List<PersonData> collect = list.stream().filter(li -> li.getMonth() >= 5000).collect(Collectors.toList());
log.info("月薪达标按数量:" + collect.size());
//插入数据库
log.info("插入人员数据库:" + list.size());
}
}
3、Demo说明
1、启动项目
访问:http://127.0.0.1:8080/demo/doc.html,然后选择录入用户文件路径
功能,在地址栏录入excel路径,点击发送进行请求
2、查看控制台信息
控制台打印信息如下:
可以看到 数据解析已经成功。
4、数据转换、格式处理
上面的Demo中,年薪和月薪是Doubl的类型;但是,你不能保证用户录入的Excel中数据格式一定是数字类型,可能会出现字符串类型的情况,直接进行导入会出现转换异常
我们需要自定义一个转换类,实现无论什么格式,都进行Double类型的输出
public class CustomDoubleConverter implements Converter<Double> {
@Override
public Class supportJavaTypeKey() {
return Double.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.NUMBER;
}
/**
* 这里读的时候会调用
*
* @param cellData NotNull
* @param contentProperty Nullable
* @param globalConfiguration NotNull
* @return
*/
@Override
public Double convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
CellDataTypeEnum type = cellData.getType();
double newValue = 0.00d;
if (type == CellDataTypeEnum.STRING) {
//如果为文本则转换为为Double
newValue = Double.valueOf(cellData.getStringValue());
} else {
newValue = Double.valueOf(cellData.getNumberValue().toString());
}
return newValue;
}
/**
* 这里是写的时候会调用
*
* @param value NotNull
* @param contentProperty Nullable
* @param globalConfiguration NotNull
* @return
*/
@Override
public CellData convertToExcelData(Double value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return new CellData(value);
}
}
并且在Entity中字段上加入注解:@ExcelProperty(converter = CustomDoubleConverter.class)
;经常也会出现日期格式不统一的情况,可以使用 @DateTimeFormat("yyyy-MM-dd")
进行统一的日期格式化操作。
修改Entity如下:
@Data
//注意属性的顺序对应excel中各个列解析的顺序,为了处理单元格为空的情况莫须有写一个Converter类,对空进行判断,并且返回默认值
public class PersonData {
//名称
private String name;
//入职时间,日期格式化操作(不用关系excel中的日期为文本还是自定义日期格式)
@DateTimeFormat("yyyy-MM-dd")//只会转换excel中的时间格式,如果excel本来就是String类型,需要写一个转换器进行统一的转换
private String date;
//部门
private String dept;
//年薪,对excel里面的数据进行统一的转换,当解析到年薪单元格时,会CustomDoubleConverter中进行格式转换操作
@ExcelProperty(converter = CustomDoubleConverter.class)
private Double year = 0.00;
//月薪,对excel里面的数据进行统一的转换
@ExcelProperty(converter = CustomDoubleConverter.class)
private Double month = 0.00;
}
5、Web操作,上传文件处理
直接通过输入流进行读取excel操作
public void readPeronExcelByUpload(MultipartFile file) throws Exception {
//设置headRowNumber表示从第几行开始读取,下标从0开始
EasyExcel.read(file.getInputStream(), PersonData.class, personDataListener).sheet().headRowNumber(2).doRead();
}
6、泛型使用
如果我们存在导入多个excel每个excel对应不同的Entity类和不同的业务操作,那么需要开发者创建多个Entity类(这是应该的),但是也需要创建多个监听类,并且监听类的代码几乎一样,只有少量的业务处理不一致,那么每次创建多个监听类,显得很没必要。为了解决这个问题 我们可以使用泛型
进行处理。
1、修改Controller加入读取设备的Excel接口
@GetMapping("/device")
@ApiOperation(value = "录入设备文件路径", notes = "录入设备文件路径")
@ApiImplicitParam(name = "filePath", value = "Excel路径", paramType = "query", required = true, dataType = "String")
public Object readDevice(@RequestParam String filePath) {
readExcelService.readDeviceExcel(filePath);
return ResponseObject.success();
}
2、修改业务类,加入设备的处理逻辑
//读取设备的excel文件
public void readDeviceExcel(String filePath) {
EasyExcel.read(filePath, PersonData.class, personDataListener).sheet().headRowNumber(2).doRead();
}
//保存有效的导入数据
public void saveDeviceExcel(List<DeviceData> list) {
//插入数据库
log.info("插入设备数据库:" + list.size());
}
3、建立设备Excel及Entity
@Data
public class DeviceData {
//名称
private String name;
//设备编码
private String code;
//设备价格
private String price;
}
4、修改监听类
在原来的监听类中,需要指定到一个Entity上,现在我们直接使用泛型 T
,来标识任何一个实体类,通过instanceof
判断传入的泛型属于那个Entity。在service中使用EasyExcel.read()
方法时,一定要指定实体的类型,所以不同的excel要对应不同的EasyExcel.read()
方法。
@Slf4j
@Component
public class PersonDataListener<T> extends AnalysisEventListener<T> {
@Resource
private ReadExcelService excelService;
//存放解析后的数据
private List<T> list = new ArrayList<>();
/**
* 读取每一条头部信息
*
* @param headMap
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
}
/**
* 这个每一条数据解析都会来调用
*
* @param t one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(T t, AnalysisContext context) {
log.info("解析到一条数据:{}", JSON.toJSONString(t));
//调用service类进行一些业务判断操作
Integer rowIndex = context.readRowHolder().getRowIndex() + 1;//当时行数,从0开始,实际加1
//通过判断泛型类,调用不同的Service类
if (t instanceof PersonData) {
PersonData data = (PersonData) t;
excelService.checkPersonExcel(data, rowIndex);
list.add(t);
}
if (t instanceof DeviceData) {
list.add(t);
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
*
* @param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context) {
log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
// 如果是某一个单元格的转换异常 能获取到具体行号
// 如果要获取头的信息 配合invokeHeadMap使用
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
log.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex(),
excelDataConvertException.getColumnIndex());
}
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", list.size());
//人员数据
if (list instanceof PersonData) {
List<PersonData> dataList = new ArrayList<>();
excelService.savePersonExcel(dataList);
}
//设备数据
if (list instanceof PersonData) {
List<DeviceData> dataList = new ArrayList<>();
excelService.saveDeviceExcel(dataList);
}
list.clear();
}
}
5、发起请求进行测试
依次进行请求发送,可以看到控制台输出不一样,但是监听类却只有一个
具体代码文档描述不清楚,可以下载Demo进行运行看效果。
读写操作Github:https://github.com/lxlhz/easy-demo