EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。

个人Demo地址

EasyExcel解决了什么

我们以使用最多的 Apache poi 来做为对比

  • 这是我项目中使用到的 poi 做Excel的解析,就这一堆看着难受不难受

  • 可以看出直接使用poi的结果就是繁琐,代码看着也不舒服,最为致命的是很容易造成OOM,内存占用是poi永远不可避免的话题,虽然EasyExcel底层还是使用poi,但是做了很多的优化,尤其解决了并发处理中的bug(说这么多干嘛!其实我只需要代码看着简洁易懂)

SpringBoot

引入jar包

1
2
3
4
5
6
<!--Alibaba-Excel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.2</version>
</dependency>
  • 我使用的最新2.2.2版本,对比csdn等博客中记录的1.0之后的版本来说,这个版本已经进行了很多次的迭代,修复了许多bug,而2.2.1是最近的一个正式版。

    能干什么

    三大功能(读、写、填充)

读(Read)

  • 示例Excel

  • 以下Demo都以这个Excel为例子
  • 创建实体类 (代码)

图中的converter属性,后面再介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component
@AllArgsConstructor //全参构造器
@NoArgsConstructor //无参构造
@Builder //作用于类上,将类转变为建造者模式
public @Data class Student {

@ExcelProperty(value = "姓名")
@NonNull private String name;

@ExcelProperty(value = "年龄")
private Integer age;

@ExcelProperty(value = "性别")
private String gender;

@ExcelProperty(value = "身高")
private String height;

@ExcelProperty(value = "体重")
private String weight;
/**
* 自定义转换器(String to String)
*/
@ExcelProperty(value = "专业",converter = CustomStringStringConverter.class)
private String major;

@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "日期")
private String update;

}
  • 监听器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* @Author: 赵博雅
* @Date: 2020/4/26 18:57
*/
public class ListenerExcel extends AnalysisEventListener<Student> {
private static final Logger LOGGER = LoggerFactory.getLogger(ListenerExcel.class);

//批次保存最大值
private static final int BATCH_COUNT = 2;

List<Student> list = new ArrayList<>();

private StudentDao studentDao;

/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param studentDao
*/
public ListenerExcel(StudentDao studentDao) {
this.studentDao = studentDao;
}

/**
* 这个每一条数据解析都会来调用
*
* @param data
* one row value. Is is same as invoke
* @param context
*/
@Override
public void invoke(Student data, AnalysisContext context) {
Integer rowNum = context.getCurrentRowNum();
if (rowNum == 0) {
return;
}
LOGGER.info("解析到一条数据:{},位于第{}行", JSON.toJSONString(data), rowNum);

list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}

/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
LOGGER.info("所有数据解析完成!");
}

/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
studentDao.saveAll(list);
LOGGER.info("存储数据库成功!");
}
}

重点

针对我们每个要解析的Excel文件都应实现一个监听器,实际就是简化版的解析器,其中我们只需要实现invoke()doAfterAllAnalysed()两个方法,invoke是Excel文件中每解析一行都需要执行的操作,如果用在我们实际的业务中,一般会放入集合中等待入表或做算法操作,doAfterAllAnalysed将在整个sheet解析完成后执行。

注意:如果是Spring的项目中使用,Excel的监听器不能被Spring所管理, 要每次读取excel都要new,里面用到spring的话可以构造方法传进去 (比如我上面的StudentDao,持久层)

  • 结束

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootTest
class LombookApplicationTests {

@Test
void contextLoads() throws FileNotFoundException {

//数据库 -> 数据持久化
StudentDao studentdao = new StudentDaoImpl();

ExcelReaderBuilder read =
EasyExcel.read(
new FileInputStream("E:\\studentTest.xlsx"),
Student.class,
new ListenerExcel(studentdao));
read.doReadAll();
}
}
  • Result

  • QA

1、 @ExcelProperty注解是用来指定每个字段的列名称,以及Excel中的下标位置

这个地方我们只需要统一使用value或者index来指定excel中所对用的列即可,官方文档中不推荐同时使用value和index

2、@DateTimeFormat("yyyy-MM-dd HH:mm:ss")注解可以将时间格式化,但是需要时间类型为String

3、@ExcelIgnore 注解可以忽略掉当前属性,也就是excel中当前列

4、 @ExcelProperty 中的converter属性表示类型转换器(Excel转对象的数据处理,和对象集合转Excel的数据处理),根据Converter接口找到一些已经提供的的转换器

当然我们可以重写这个接口实现,使用 converter 绑定到属性上

例如:

重写convertToJavaDataconvertToExcelData方法

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class supportJavaTypeKey() {
return String.class;
}

@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}

@Override
public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
return "【学科专业】" + cellData.getStringValue();
}

@Override
public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
return new CellData("【学科专业】" + value);
}
}

在解析Excel文件的时候自动对对应的属性值进行转换

同样,在生成Excel时也可以通过 convertToExcelData 方法自动转换数据

写(write)

  • 先写一个生成测试的模板数据类

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @Author: 赵博雅
* @Date: 2020/4/27 11:05
*/
public class DataTemplateImpl implements DataTemplate {
@Override
public List<Student> create() {

List<Student> students = new ArrayList<>();

Student build = Student.builder()
.name("张三")
.age(22)
.gender("man")
.height("170")
.weight("120")
.major("计算机科学与技术")
.update("2020-04-27 11:17:50")
.build();

students.add(build);
return students;
}
}
  • 生成Excel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest
public class WriteTest {

@Test
void contextLoads() throws FileNotFoundException {

String fileName = "E:\\" + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
//生成测试数据的模板
DataTemplate dataTemplate = new DataTemplateImpl();
EasyExcel.write(fileName, Student.class)
.sheet("模板")
.doWrite(dataTemplate.create());
}
}

1、doWrite中传入数据

2、excelType可指定类型,使用ExcelTypeEnum的枚举类

  • 生成Excel(自定义列)

1、使用excludeColumnFiledNames或者excludeColumnIndexes会根据列名或者列编码来屏蔽该列

2、使用注解@ExcelIgnore同样可以忽略列

体重 属性使用 @ExcelIgnore 忽略
年龄 属性使用方法 excludeColumnFiledNames 忽略

  • 复杂头写入

web中的读和写(Swagger)

下载模板