今天给大家安利一款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/javaXiaoCaiJi/zhixie-code-example/tree/master/easyexcel

如果文章对您有帮助,请记得点赞关注哟~

欢迎大家关注我的公众号<情系IT>,每日技术推送文章供大家学习参考。

最新文章

  1. Ubuntu如何选择更新源
  2. MemCached add命令的用法详解
  3. Yocto开发笔记之《U-boot启动内核流程》(QQ交流群:519230208)
  4. Culcurse
  5. stsadm.exe
  6. ZOJ 1024 Calendar Game
  7. 深入理解JavaScript的变量作用域(转载Rain Man之作)
  8. MIME 部分扩展名与类型对应
  9. Kafka系列(二)特性和常用命令
  10. 自定义UICollectionViewLayout 实现瀑布流
  11. 5、Web应用程序中的安全向量 -- Open Redirect Attack(开放重定向)
  12. LeetCode OJ 84. Largest Rectangle in Histogram
  13. JAVA提高十:ArrayList 深入分析
  14. 第五周 IP通信基础回顾
  15. gerapy 实现自动化部署
  16. redis缓存雪崩,缓存穿透,缓存击穿的解决方法
  17. 《JQuery技术内幕》读书笔记——自调用匿名函数剖析
  18. 13个.Net开源的网络爬虫
  19. 跟随我在oracle学习php(6)
  20. Linux-Shell基础(变量,字符串,数组)

热门文章

  1. Qt之键盘事件监听-实时响应大小写Capslock按键
  2. 关于纯xmlhttprequest请求服务器数据
  3. &lt;%@ include %&gt;导入的文件乱码
  4. 松软科技课堂:SQLUNION和UNIONALL操作符
  5. 无法解析的外部符号,该符号在xxx函数中被引用
  6. NestedInteger Java
  7. [LeetCode]sum合集
  8. Python学习-while循环&amp;逻辑运算符
  9. 看完您如果还不明白 Kerberos 原理,算我输!
  10. Dubbo学习系列之八(分布式事务之MQ方案)