当前位置: 首页 > news >正文

SpringBoot整合EasyExcel实现读操作

😊 @ 作者: 一恍过去
💖 @ 主页: https://blog.csdn.net/zhuocailing3390
🎊 @ 社区: Java技术栈交流
🎉 @ 主题: SpringBoot整合EasyExcel实现读操作
⏱️ @ 创作时间: 2023年07月18日

在这里插入图片描述

目录

      • 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@Resourceprivate ReadExcelService excelService;//存放解析后的数据private List<PersonData> list = new ArrayList<>();/*** 读取每一条头部信息** @param headMap* @param context*/@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));}/*** 这个每一条数据解析都会来调用** @param context*/@Overridepublic void invoke(PersonData data, AnalysisContext context) {//调用业务类,对数据进行校验excelService.checkPersonExcel(data, rowIndex);list.add(data);log.info("解析到一条数据:{}", JSON.toJSONString(data));}/*** 所有数据解析完成了 都会来调用** @param context*/@Overridepublic 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 ,方便内存回收*/@Resourceprivate 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();//指定读取的SheetReadSheet 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> {@Overridepublic Class supportJavaTypeKey() {return Double.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.NUMBER;}/*** 这里读的时候会调用** @param cellData            NotNull* @param contentProperty     Nullable* @param globalConfiguration NotNull* @return*/@Overridepublic Double convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,GlobalConfiguration globalConfiguration) {CellDataTypeEnum type = cellData.getType();double newValue = 0.00d;if (type == CellDataTypeEnum.STRING) {//如果为文本则转换为为DoublenewValue = Double.valueOf(cellData.getStringValue());} else {newValue = Double.valueOf(cellData.getNumberValue().toString());}return newValue;}/*** 这里是写的时候会调用** @param value               NotNull* @param contentProperty     Nullable* @param globalConfiguration NotNull* @return*/@Overridepublic 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> {@Resourceprivate ReadExcelService excelService;//存放解析后的数据private List<T> list = new ArrayList<>();/*** 读取每一条头部信息** @param headMap* @param context*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();log.info("所有数据解析完成!");}/*** 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。** @param exception* @param context* @throws Exception*/@Overridepublic 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

在这里插入图片描述

http://www.lryc.cn/news/94949.html

相关文章:

  • go mod 设置国内源 windows 环境 win10
  • 智能决策支持系统实现的关键技术分析
  • OSS对象存储后端实现+Vue实现图片上传【基于若依管理系统开发】
  • 人工智能学习目录
  • Vue单页面实现el-tree el-breadcrumb功能、el-tree右键点击树节点展示菜单功能、树节点编辑节点字段名称功能
  • C++核心编程之函数高级使用
  • 如何创建智能合约游戏系统
  • 如何用rust实现一个异步channel
  • gitee上传项目到仓库
  • day27 贪心算法
  • Java实现字符串反转
  • vue - 常见的性能优化
  • 微服务系列文章 之 Nginx服务状态监控的方法
  • 【网络系统集成】路由器实验
  • 【mac 安装Miniconda】
  • 螺栓疲劳计算-风电行业,参考GL2010, ST0361,1993-1-9
  • QT学习之旅 - QThread多线程
  • PROFINET转TCP/IP网关TCP/IP协议的含义是
  • 计算机网络基础第六章
  • MobPush:Android客户端SDK厂商通道回执配置指南
  • Karmada: Open, Multi-Cloud, Multi-Cluster Kubernetes Orchestration
  • arcgis拓扑检查
  • icp许可证 办理流程(icp资质申请条件)
  • 三菱PLC 控制灯一秒钟交替闪烁
  • 金融数据库的战场,太平洋保险和OceanBase打了场胜仗
  • IP协议【图解TCP/IP(笔记九)】
  • C#仿热血江湖
  • Nginx静态资源部署
  • javaee jstl表达式
  • ChatGPT是否具有记忆能力?