- 前言
- 1、实体类准备
- 2、自定义监听器
- 3、编写控制器
- 4、结果演示
- 5、问题总结
前段时间刚刚在一个项目中集成EasyExcel实现了数据导出到Excel表中,“产品经理”突然又加了要求:“那个谁,这个项目好像还缺一个批量数据导入的功能,你去写一下”。我:“……”。生活所迫,只能“自愿”的去完成了这个功能。
1、实体类准备EasyExcel官网 – > https://www.yuque.com/easyexcel/doc/easyexcel
这里用一个简单的学生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 DataListener3、编写控制器extends AnalysisEventListener { private static final int BATCH_COUNT = 100; private IService service; public DataListener(IService service){ this.service = service; } List list = new ArrayList (); @Override public void invoke(T instance, AnalysisContext analysisContext) { log.info("读取到了数据{}", instance); list.add(instance); // 达到BATCH_COUNT存储一次数据库,防止数据几万条数据在内存造成OOM 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()); } } } }
在上面的两个准备工作弄好之后,就可以编写控制器对其进行使用实现我们的需求了
@PostMapping("/upload")
public Result 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);
//判断是否为excel文档
if (!"xlsx".equals(suffix) && !"xls".equals(suffix)) {
return new Result.error("只支持Excel文档导入");
}
try {
EasyExcel.read(file.getInputStream(), Student.class, new DataListener(studentService)).sheet().doRead();
} catch (Exception e) {
return new Result.error("导入失败,请重试");
}
return new Result.success("批量导入成功");
}
4、结果演示
-
根据实体类创建导入数据。
这里可能有些伙伴有疑问,这里为什么会有一个序号呢?实体类里面没有需要这个属性啊,这是因为在编写实体类的时候是按照value进行匹配,而不是根据index进行匹配的,一次在导入时只会匹配实体类中存在的value。
- 使用Postman或Apifox进行测试(这里使用的时Apifox)
狗子我在写这一个功能时遇到了一个bug,就是在读取Excel文件时,读取到的数据都是null,在排查了好一段时间的问题之后可算是给我找到了问题所在,原来是EasyExcel本身的问题,它与我使用的Lombok产生了冲突。
EasyExcel和Lombok会有一个冲突:当你尝试在用于接收Excel解析数据的Bean上面加上@Accessors(chain = true)注解时,你会发现该Bean接收不到数据,体现在String类型的字段总是为null,int类型的字段总是为0。
因此目前的解决办法就是,把@Accessors(chain = true)这个注解先给删掉,鱼与熊掌不可兼得,只能放弃其一了。



