前言
前段时间刚刚在一个项目中集成EasyExcel实现了数据导出到Excel表中,“产品经理”突然又加了要求:“那个谁,这个项目好像还缺一个批量数据导入的功能,你去写一下”。我:“……😥😤”。生活所迫,只能“自愿”的去完成了这个功能。
EasyExcel官网 – > https://www.yuque.com/easyexcel/doc/easyexcel
1、实体类准备
这里用一个简单的学生Student类进行演示
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "学号")
private String studentId;
@ExcelProperty(value = "姓名")
private String studentName;
@ExcelProperty(value = "年级")
private String studentGrade;
@ExcelProperty(value = "专业")
private String studentMajor;
@ExcelProperty(value = "班级")
private String studentClass;
}
2、自定义监听器
根据白嫖来的官网教学,我们还需要自定义一个Excel读的监听器。这里设定了每隔100条存储数据库,然后清理list,这是为了防止数据几万条数据在内存,从而造成OOM(OutOfMemory,内存溢出)。
这里使用到了泛型提高了这个监听器的复用性,也可以将下面的T 换成自己指定的一个实体类
@Slf4j
@Setter
@EqualsAndHashCode(callSuper = true)
public class DataListener<T> extends AnalysisEventListener<T> {
private static final int BATCH_COUNT = 100;
private IService<T> service;
public DataListener(IService<T> service){
this.service = service;
}
List<T> list = new ArrayList<T>();
@Override
public void invoke(T instance, AnalysisContext analysisContext) {
log.info("读取到了数据{}", instance);
list.add(instance);
if (list.size() >= BATCH_COUNT){
this.saveData();
list.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
this.saveData();
log.info("所有数据导入成功");
}
private void saveData() {
for (T data : list) {
log.info("{}开始存放数据库……", data);
try {
this.service.save(data);
} catch (Exception e) {
log.info(e.getMessage());
}
}
}
}
3、编写控制器
在上面的两个准备工作弄好之后,就可以编写控制器对其进行使用实现我们的需求了
@PostMapping("/upload")
public Result<String> upload(@RequestParam("files") MultipartFile file) throws IOException {
String filename = file.getOriginalFilename();
int index = filename != null ? filename.lastIndexOf(".") : -1;
if (index < 0) {
return new Result.error("请选择正确的文件");
}
String suffix = filename.substring(index + 1);
log.info("获取后缀名为:{}", suffix);
if (!"xlsx".equals(suffix) && !"xls".equals(suffix)) {
return new Result.error("只支持Excel文档导入");
}
try {
EasyExcel.read(file.getInputStream(), Student.class, new DataListener<Student>(studentService)).sheet().doRead();
} catch (Exception e) {
return new Result.error("导入失败,请重试");
}
return new Result.success("批量导入成功");
}
4、结果演示
-
根据实体类创建导入数据。 这里可能有些伙伴有疑问,这里为什么会有一个序号呢?实体类里面没有需要这个属性啊,这是因为在编写实体类的时候是按照value进行匹配,而不是根据index进行匹配的,一次在导入时只会匹配实体类中存在的value。
- 使用Postman或Apifox进行测试(这里使用的时Apifox)
5、问题总结
狗子我在写这一个功能时遇到了一个bug,就是在读取Excel文件时,读取到的数据都是null,在排查了好一段时间的问题之后可算是给我找到了问题所在,原来是EasyExcel本身的问题,它与我使用的Lombok产生了冲突。
EasyExcel和Lombok会有一个冲突:当你尝试在用于接收Excel解析数据的Bean上面加上@Accessors(chain = true) 注解时,你会发现该Bean接收不到数据,体现在String类型的字段总是为null,int类型的字段总是为0。
因此目前的解决办法就是,把@Accessors(chain = true) 这个注解先给删掉,鱼与熊掌不可兼得,只能放弃其一了。
|