如何让excel文件读取变得更简单
今天给大家安利一款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>,每日技术推送文章供大家学习参考。
如何让excel文件读取变得更简单的更多相关文章
- 深入解析 Kubebuilder:让编写 CRD 变得更简单
作者 | 刘洋(炎寻) 阿里云高级开发工程师 导读:自定义资源 CRD(Custom Resource Definition)可以扩展 Kubernetes API,掌握 CRD 是成为 Kubern ...
- spring 第一篇(1-1):让java开发变得更简单(下)
切面(aspects)应用 DI能够让你的软件组件间保持松耦合,而面向切面编程(AOP)能够让你捕获到在整个应用中可重用的组件功能.在软件系统中,AOP通常被定义为提升关注点分离的一个技术.系统由很多 ...
- [翻译]Kafka Streams简介: 让流处理变得更简单
Introducing Kafka Streams: Stream Processing Made Simple 这是Jay Kreps在三月写的一篇文章,用来介绍Kafka Streams.当时Ka ...
- Kafka Streams简介: 让流处理变得更简单
Introducing Kafka Streams: Stream Processing Made Simple 这是Jay Kreps在三月写的一篇文章,用来介绍Kafka Streams.当时Ka ...
- spring 第一篇(1-1):让java开发变得更简单(下)转
spring 第一篇(1-1):让java开发变得更简单(下) 这个波主虽然只发了几篇,但是写的很好 上面一篇文章写的很好,其中提及到了Spring的jdbcTemplate,templet方式我之前 ...
- Moq让单元测试变得更简单
[ASP.Net MVC3 ]使用Moq让单元测试变得更简单 前几天调查完了unity.现在给我的任务是让我调查Moq. 以下是自己找了资料,总结并实践的内容.如果有表述和理解错误的地方.恳请指正. ...
- Winform 让跨线程访问变得更简单
Winform 让跨线程访问变得更简单 前言 由于多线程可能导致对控件访问的不一致,导致出现问题.C#中默认是要线程安全的,即在访问控件时需要首先判断是否跨线程,如果是跨线程的直接访问,在运行时会抛出 ...
- EpiiAdmin 开源的php交互性管理后台框架, 让复杂的交互变得更简单!Phper快速搭建交互性平台的开发框架,基于Thinkphp5.1+Adminlte3.0+Require.js。
EpiiAdmin EpiiAdmin php开源交互性管理后台框架,基于Thinkphp5.1+Adminlte3.0+Require.js, 让复杂的交互变得更简单!Phper快速搭建交互性平台的 ...
- 快开宝PDA开单器出入库扫码:让批发零售变得更简单
快开宝PDA开单器出现前 批发商户是这样开单和管理的 ★员工痛苦:需要记客户.价格.库存等等,应对报错价.错漏单.盘错货等各种状况. ★老板麻烦:每天要守店.对单.核账,经常因错漏单.库存乱.积压货. ...
随机推荐
- Python(Head First)学习笔记:五
5 推导数据:处理数据.格式.编码.解码.排序 处理数据:从Head First Python 上下载资源文件,即:james.txt,julie.txt,mikey.txt,sarah.txt. 实 ...
- FreeSql (三)实体特性
主键(Primary Key) class Topic { [Column(IsPrimary = true)] public int Id { get; set; } } 约定: 当没有指明主键时, ...
- 干货 干货 2019阿里巴巴Android40道基本面试题
找工作还是需要大家不要经常,有我们干这一行的接触人本来就不多 难免看到面试官会紧张,主要是因为怕面试官问的问题到不上来,那时候不要着急 ,答不上了的千万不然胡扯一些,直接就给面试官说这块我还没接触到, ...
- C#中将表示颜色的string转换成Color
场景 在Winform中需要存储某控件的Color属性,存储的是string字符串, 然后再对控件进行赋值时需要将string转换成Color. 实现 myPane.YAxis.Color = Sys ...
- Django--路由层、视图层、模版层
路由层: 路由匹配 url(正则表达式,视图函数内存地址) 只要正则匹配到了内容,就不再往下匹配,而是直接运行后面的视图函数 匹配首页) url(r'^&', home) 匹配尾页 url(r ...
- Https与Http的区别以及Https的解说
http:信息不加密,具有信息被盗的危险 https:信息加密,第三获取原信息 1:https多了一层SSL,而这一层的设计是为了达到如下的 (1) 所有信息都是加密传播,第三方无法窃听. (2) 具 ...
- [3]尝试用Unity3d制作一个王者荣耀(持续更新)->选择英雄-(中)
如果已经看过本章节:目录传送门:这是目录鸭~ 上节内容写了Actor管理器,那么这一节让我们先创建一个角色.(此章节开始加速...) 1.制作角色展示AssetBundle: 提取农药某个展示模型(S ...
- 松软科技课堂:SQL--FULLJOIN关键字
SQL FULL JOIN 关键字(from:www.sysoft.net.cn) 只要其中某个表存在匹配,FULL JOIN 关键字就会返回行. FULL JOIN 关键字语法 SELECT col ...
- Android服务之混合方式开启服务
引言 前面介绍过了Android服务的两种开启方式:Start方式可以让服务在后台运行:bind方式能够调用到服务中的方法. 在实际的开发工作中,有很多需求是:既要在后台能够长期运行,又要在服务中操作 ...
- java数据结构——单链表、双端链表、双向链表(Linked List)
1.继续学习单链表,终于摆脱数组的魔爪了,单链表分为数据域(前突)和引用域(指针域)(后继),还有一个头结点(就好比一辆火车,我们只关心火车头,不关心其它车厢,只需知晓车头顺藤摸瓜即可),头结点没有前 ...