今天给大家安利一款 excel 文件导入神器,easyexcel,官方地址:(https://github.com/alibaba/easyexcel)。
在官网文档中有介绍了其性能。
从上面的性能测试可以看出 easyexcel 在解析耗时上比 poiuserModel 模式弱了一些。主要原因是我内部采用了反射做模型字段映射,中间我也加了 cache,但感觉这点差距可以接受的。但在内存消耗上差别就比较明显了,easyexcel 在后面文件再增大,内存消耗几乎不会增加了。但 poi userModel 就不一样了,简直就要爆掉了。想想一个 excel 解析 200M,同时有 20 个人再用估计一台机器就挂了。
如何使用呢
1、引入 maven 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.0.5</version>
</dependency>
由于改 jar 包对 poi 进行了一些封装,因此需要注释掉项目引用的 poi 依赖,不然会有版本冲突。
2、创建接收 excel 数据的实体
@Data
public class Person {@ExcelProperty(value = "姓名",index = 1)
private String name;
@ExcelProperty("性别")
private String sex;
@ExcelProperty("年龄")
private int age;
}
@ExcelProperty 这个注解用于指定该属性对应 excel 文件中的哪一列数据。里面有两个属性,一个是 value, 另一个是 index(从 0 开始),这里不建议 index 和 name 同时用,要么一个对象只用 index,要么一个对象只用 name 去匹配。
3、增加 person 监听器
@Slf4j
public class PersonListener extends AnalysisEventListener<Person> {
/**
* 每隔 5 条存储数据库,实际使用中可以 3000 条,然后清理 list,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<Person> list = new ArrayList();
@Override
public void invoke(Person person, AnalysisContext analysisContext) {log.info("解析到一条数据:{}",person);
if (list.size() >= BATCH_COUNT) {saveData();
list.clear();}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {saveData();
log.info("所有数据解析完成!");
}
@Override
public void onException(Exception exception, AnalysisContext context) {log.error("解析失败,但是继续解析下一行", exception);
}
/**
* 加上存储数据库
*/
private void saveData(){log.info("{} 条数据,开始存储数据库!", list.size());
log.info("存储数据库成功!");
}
}
4、文件上传方法
@RestController
@Slf4j
public class PersonController {@PostMapping("importFile")
public String readPerson(MultipartFile file){PersonListener personListener = new PersonListener();
try {// headRowNumber(2) 这里可以设置 1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,他没有指定头,也就是默认 1 行
EasyExcel.read(file.getInputStream(), Person.class,personListener).sheet().headRowNumber(1).doRead();}catch (IOException ioe){log.info("读取 excel 异常 ={}",ioe);
}
return "";
}
}
这样代码基本就算完成了。接下来我们看看如何在
spring 框架中使用
我们知道,在 spring 中文件入库的时候可能需要调用 service,service 调用 dao 入库。那我们如何在这个 PersonListener 中调用 service 呢。
首先,在 PersonController 中注入 service.
@Autowired
private PersonService personService;
然后,在 PersonListener 中增加一个有参构造器。
private PersonService personService;
public PersonListener(PersonService personService){this.personService = personService;}
最后在 PersonController 中 new PersonListener 的时候将 service 传进来即可。
@Autowired
private PersonService personService;
@PostMapping("importFile")
public String readPerson(MultipartFile file){PersonListener personListener = new PersonListener(personService);
try {// headRowNumber(2) 这里可以设置 1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,他没有指定头,也就是默认 1 行
EasyExcel.read(file.getInputStream(), Person.class,personListener).sheet().headRowNumber(2).doRead();}catch (IOException ioe){log.info("读取 excel 异常 ={}",ioe);
}
return "";
}
接下来我们在看一下,有一个需求是这样的,我们需要统计一下导入成功数和失败数并且要不失败数写入到一个 excel 中返回给页面,如何实现?
我们调整一下 PersonListener
@Slf4j
public class PersonListener extends AnalysisEventListener<Person> {
private int successCount = 0; // 成功量
private int exceptionCount = 0; // 异常量
private List<Person> exceptionList = new ArrayList<>(); // 异常数据
/**
* 每隔 5 条存储数据库,实际使用中可以 3000 条,然后清理 list,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<Person> list = new ArrayList();
private PersonService personService;
public PersonListener(PersonService personService){this.personService = personService;}
@Override
public void invoke(Person person, AnalysisContext analysisContext) {log.info("解析到一条数据:{}",person);
successCount++;
list.add(person);
if (list.size() >= BATCH_COUNT) {saveData();
list.clear();}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {saveData();
log.info("所有数据解析完成!");
}
@Override
public void onException(Exception exception, AnalysisContext context) {log.error("解析失败,但是继续解析下一行", exception);
Person person = (Person)context.readRowHolder().getCurrentRowAnalysisResult();
exceptionList.add(person);
exceptionCount++;
}
/**
* 加上存储数据库
*/
private void saveData(){log.info("{} 条数据,开始存储数据库!", list.size());
log.info("存储数据库成功!");
}
/**
* 插入结果返回
* @return
*/
public Map<String,Object> getData(){Map<String,Object> map = new HashMap<>();
map.put("success",successCount);
map.put("exception",exceptionCount);
return map;
}
/**
* 失败数据返回
* @return
*/
public List<Person> getExceptionList(){return exceptionList;}
}
在 PersonController 中将异常数据写入文件。
@RestController
@Slf4j
public class PersonController {
@Autowired
private PersonService personService;
@PostMapping("importFile")
public String readPerson(MultipartFile file){PersonListener personListener = new PersonListener(personService);
try {// headRowNumber(1) 这里可以设置 1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,他没有指定头,也就是默认 1 行
EasyExcel.read(file.getInputStream(), Person.class,personListener).sheet().headRowNumber(1).doRead();}catch (IOException ioe){log.info("读取 excel 异常 ={}",ioe);
}
Map<String, Object> data = personListener.getData();
String exception = data.get("exception") + "";
int exceptionCount = Integer.parseInt(exception);
if(exceptionCount>0){String fileName = System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个 class 去读,然后写到第一个 sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write("E://upload/file/"+fileName, Person.class).sheet("模板").doWrite(personListener.getExceptionList());
data.put("fileName",fileName);
}
return data.toString();}
}
我们写个页面测试一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> 文件上传 </title>
</head>
<body>
<form action="/importFile" method="post" enctype="multipart/form-data">
<input type="file" name="file"/>
<button type="submit"> 提交 </button>
</form>
</body>
</html>
下面是测试的日志文件
2019-10-20 11:21:59.809 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 解析到一条数据:Person(name= 执偕, sex= 男, age=18)
2019-10-20 11:21:59.811 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 解析到一条数据:Person(name= 执偕 2, sex= 男, age=19)
2019-10-20 11:21:59.811 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 解析到一条数据:Person(name= 执偕 3, sex= 男, age=20)
2019-10-20 11:21:59.811 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 解析到一条数据:Person(name= 执偕 4, sex= 男, age=21)
2019-10-20 11:21:59.812 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 解析到一条数据:Person(name= 执偕 5, sex= 男, age=22)
2019-10-20 11:21:59.812 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 5 条数据,开始存储数据库!2019-10-20 11:21:59.812 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 存储数据库成功!2019-10-20 11:21:59.812 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 解析到一条数据:Person(name= 执偕 6, sex= 男, age=23)
2019-10-20 11:21:59.812 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 解析到一条数据:Person(name= 执偕 7, sex= 男, age=24)
2019-10-20 11:21:59.813 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 解析到一条数据:Person(name= 执偕 8, sex= 男, age=25)
2019-10-20 11:21:59.813 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 解析到一条数据:Person(name= 执偕 9, sex= 男, age=26)
2019-10-20 11:21:59.813 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 4 条数据,开始存储数据库!2019-10-20 11:21:59.813 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 存储数据库成功!2019-10-20 11:21:59.813 INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener : 所有数据解析完成!
今天就介绍到这了,文中的代码我已上传到码云上,
地址:https://gitee.com/javaXiaoCai…
如果文章对您有帮助,请记得点赞关注哟~
欢迎大家关注我的公众号 < 情系 IT>,每日推送技术文章供大家学习参考。