EasyExcel保姆级教程(1)

EasyExcel保姆级教程(1)

由于博猪所在行业使用到关于Excel导入、导出的功能比较多,本文主要详细介绍一下博猪使用的阿里巴巴的easyExcel,让Excel相关操作没那么多烦恼。

前言

Excel的缺点

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到KB级别,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便

  • Excel读写时候内存溢出

虽然POI是目前使用最多的用来做excel解析的框架,但这个框架并不那么完美。大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc

  • 其他开源框架使用复杂

对POI有过深入了解的估计才知道原来POI还有SAX模式。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。

  • 其他开源框架存在一些BUG修复不及时

EasyExcel简介

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

阿里巴巴GitHub地址:Github

EasyExcelDemo

项目准备

  • 创建空的Maven项目
  • 添加Maven依赖
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
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
<!--lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<optional>true</optional>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
  • 新建util包,创建FileUtil工具类
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
public class FileUtil {
/**
* 读取文件成输入流
* @param fileName 文件名称
* @return
*/
public static InputStream getResourcesFileInputStream(String fileName) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream("" + fileName);
}

/**
* 获取文件资源根路径,resource
* @return
*/
public static String getPath() {
return FileUtil.class.getResource("/").getPath();
}

/**
* 创建resource下文件
* @param pathName 路径名称
* @return
*/
public static File createNewFile(String pathName) {
File file = new File(getPath() + pathName);
if (file.exists()) {
file.delete();
} else {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
}
return file;
}

/**
* 读取文件
* @param pathName 全路径文件名
* @return
*/
public static File readFile(String pathName) {
return new File(getPath() + pathName);
}

/**
* 读取用户目录下面的文件
* @param pathName 全路径文件名
* @return
*/
public static File readUserHomeFile(String pathName) {
return new File(System.getProperty("user.home") + File.separator + pathName);
}
}

Excel操作-读

测试类相关常量如下:

1
2
private final String SUFFIX_EXCEL_FILE_TYPE = ".xlsx";
private final String READ_ROOT_RESOURCE = "read";

最简单的读

excel示例

image

对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Getter
@Setter
@ToString
public class AnnotationReadEntity {
/**
* 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
*/
@ExcelProperty("字符串标题")
private String title;
@ExcelProperty("日期标题")
private Date dateTitle;
/**
* 数字标题
* 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
*/
@ExcelProperty(index = 2)
private Double numberTitle;
}

监听器

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
public class SimpleReadListener extends AnalysisEventListener<SimpleReadEntity> {

private static final Logger LOGGER = LoggerFactory.getLogger(SimpleReadListener.class);

/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<SimpleReadEntity> saveList = new ArrayList<SimpleReadEntity>();

/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private EntityDao entityDao;
public SimpleReadListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
this.entityDao = new EntityDao();
}

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

/**
* 这个每一条数据解析都会来调用
* @param simpleReadEntity
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param analysisContext
*/
public void invoke(SimpleReadEntity simpleReadEntity, AnalysisContext analysisContext) {
LOGGER.info("解析到一条数据:{}", JSONObject.toJSONString(simpleReadEntity));
saveList.add(simpleReadEntity);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (saveList.size() >= BATCH_COUNT) {
batchSave();
// 存储完成清理 list
saveList.clear();
}
}

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

/**
* 批量保存方法
*/
private void batchSave() {
if (!saveList.isEmpty()) {
LOGGER.info("{}条数据,开始存储数据库!", saveList.size());
entityDao.batchSave(saveList);
LOGGER.info("存储数据库成功!");
}
}
}

持久层

1
2
3
4
5
6
7
8
public class EntityDao<T> {

private static final Logger LOGGER = LoggerFactory.getLogger(EntityDao.class);

public void batchSave(List<T> list) {
LOGGER.info(">>>>>>>>>>>>>>EntityDao.batchSave:{}" + JSONObject.toJSONString(list));
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 最简单的读
* <p>1. 创建excel对应的实体对象 参照{@link SimpleReadEntity}
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link SimpleReadListener}
* <p>3. 直接读即可
*/
@Test
public void testSimpleRead1() {
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 写法1:
String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE;
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, SimpleReadEntity.class, new SimpleReadListener()).sheet().doRead();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 简单度方式2
*/
@Test
public void testSimpleRead2() {
String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE;
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName, SimpleReadEntity.class, new SimpleReadListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}

根据注解读取

excel示例

参照:excel示例

对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Getter
@Setter
@ToString
public class AnnotationReadEntity {
/**
* 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
*/
@ExcelProperty("字符串标题")
private String title;
@ExcelProperty("日期标题")
private Date dateTitle;
/**
* 数字标题
* 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
*/
@ExcelProperty(index = 2)
private Double numberTitle;
}

监听器

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
public class AnnotationReadListener extends AnalysisEventListener<AnnotationReadEntity> {

private static final Logger LOGGER = LoggerFactory.getLogger(AnnotationReadListener.class);

/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<AnnotationReadEntity> saveList = new ArrayList<AnnotationReadEntity>();

/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private EntityDao entityDao;
public AnnotationReadListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
this.entityDao = new EntityDao();
}

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

/**
* 这个每一条数据解析都会来调用
* @param entity
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param analysisContext
*/
public void invoke(AnnotationReadEntity entity, AnalysisContext analysisContext) {
LOGGER.info("解析到一条数据:", entity);
saveList.add(entity);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (saveList.size() >= BATCH_COUNT) {
batchSave();
// 存储完成清理 list
saveList.clear();
}
}

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

/**
* 批量保存方法
*/
private void batchSave() {
if (!saveList.isEmpty()) {
LOGGER.info("{}条数据,开始存储数据库!", saveList.size());
entityDao.batchSave(saveList);
LOGGER.info("存储数据库成功!");
}
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 注解读取,与标题不匹配的名称可忽略
*/
@Test
public void testAnnotationRead() {
String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "annotationRead" + SUFFIX_EXCEL_FILE_TYPE;
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName, AnnotationReadEntity.class, new AnnotationReadListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}

读多个sheet

测试代码

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
/**
* 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
* <p>
* 1. 创建excel对应的实体对象 参照{@link SimpleReadEntity}
* <p>
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link SimpleReadListener}
* <p>
* 3. 直接读即可
*/
@Test
public void testRepeatedRead() {
String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE;
// 读取全部sheet
// 这里需要注意 SimpleReadListener 的 doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个 SimpleReadListener 里面写
EasyExcel.read(fileName, SimpleReadEntity.class, new SimpleReadListener()).doReadAll();

// 读取部分sheet
fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE;
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName).build();

// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
ReadSheet readSheet1 =
EasyExcel.readSheet(0).head(SimpleReadEntity.class).registerReadListener(new SimpleReadListener()).build();
ReadSheet readSheet2 =
EasyExcel.readSheet(1).head(SimpleReadEntity.class).registerReadListener(new SimpleReadListener()).build();
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
excelReader.read(readSheet1, readSheet2);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}

读取多行

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 多行头
* <p>1. 创建excel对应的实体对象 参照{@link SimpleReadEntity}
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link SimpleReadListener}
* <p>3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数,
* 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。
*/
@Test
public void testManyHeaderRead() {
String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE;
// 这里 需要指定读用哪个class去读,然后读取第一个sheet
EasyExcel.read(fileName, SimpleReadEntity.class, new SimpleReadListener()).sheet()
// 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行
.headRowNumber(1).doRead();
}

同步返回数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
*/
@Test
public void testSyncResRead() {
String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE;
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
List<SimpleReadEntity> list = EasyExcel.read(fileName).head(SimpleReadEntity.class).sheet().doReadSync();
for (SimpleReadEntity data : list) {
System.out.println("读取到数据: " + JSON.toJSONString(data));
}
// 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish
List<Map<Integer, String>> listMap = EasyExcel.read(fileName).sheet().doReadSync();
for (Map<Integer, String> data : listMap) {
// 返回每条数据的键值对 表示所在的列 和所在列的值
System.out.println(JSON.toJSONString(data));
System.out.println("读取到数据: " + JSON.toJSONString(data));
}
}

读取表头数据

SimpleReadListener监听器上增加方法

1
2
3
4
5
6
7
8
9
10
/**
* 这里会一行行的返回头
*
* @param headMap
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 读取表头数据
*
* <p>
* 1. 创建excel对应的实体对象 参照{@link SimpleReadEntity}
* <p>
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link SimpleReadListener}
* <p>
* 3. 直接读即可
*/
@Test
public void testHeaderRead() {
String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE;
// 这里 需要指定读用哪个class去读,然后读取第一个sheet
EasyExcel.read(fileName, SimpleReadEntity.class, new SimpleReadListener()).sheet().doRead();
}

异常处理

对象

1
2
3
4
5
6
7
8
9
10
11
12
13
@Getter
@Setter
@ToString
public class ExceptionReadEntity {
/** 标题 */
private String title;
/**
* 用日期去接字符串 肯定报错
*/
private String dateTitle;
/** 数字标题 */
private Double numberTitle;
}

SimpleReadListener监听器上增加方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
* @param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context){
LOGGER.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
// 如果是某一个单元格的转换异常 能获取到具体行号
// 如果要获取头的信息 配合invokeHeadMap使用
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
LOGGER.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex(),
excelDataConvertException.getColumnIndex());
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 数据转换等异常处理
*
* <p>
* 1. 创建excel对应的实体对象 参照{@link ExceptionReadEntity}
* <p>
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link SimpleReadListener}
* <p>
* 3. 直接读即可
*/
@Test
public void testSimpleReadOnException() {
String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE;
// 这里 需要指定读用哪个class去读,然后读取第一个sheet
EasyExcel.read(fileName, ExceptionReadEntity.class,new SimpleReadListener()).sheet().doRead();
}

测试不创建对象的读取

监听器

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
public class NotCreateObjectReadListener  extends AnalysisEventListener<Map<Integer, String>> {

private static final Logger LOGGER = LoggerFactory.getLogger(NotCreateObjectReadListener.class);

/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<Map<Integer, String>> list = new ArrayList<Map<Integer, String>>();

@Override
public void invoke(Map<Integer, String> data, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
list.add(data);
if (list.size() >= BATCH_COUNT) {
saveData();
list.clear();
}
}

@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
saveData();
LOGGER.info("所有数据解析完成!");
}

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

测试代码

1
2
3
4
5
6
@Test
public void testNotCreateObjectRead() {
String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE;
// 这里 只要,然后读取第一个sheet 同步读取会自动finish
EasyExcel.read(fileName, new NotCreateObjectReadListener()).sheet().doRead();
}

格式转换

对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Getter
@Setter
@ToString
public class ConverterReadEntity {
/**
* 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:”
*/
@ExcelProperty(converter = CustomStringConverter.class)
private String title;
/**
* 这里用string 去接日期才能格式化。我想接收年月日格式
*/
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private String dateTitle;
/**
* 我想接收百分比的数字
*/
@NumberFormat("#.##%")
private Double numberTitle;
}

监听器

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
public class ConverterReadListener extends AnalysisEventListener<ConverterReadEntity> {

private static final Logger LOGGER = LoggerFactory.getLogger(ConverterReadListener.class);

/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<ConverterReadEntity> saveList = new ArrayList<ConverterReadEntity>();

/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private EntityDao entityDao;
public ConverterReadListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
this.entityDao = new EntityDao();
}

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

/**
* 这个每一条数据解析都会来调用
* @param entity
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param analysisContext
*/
public void invoke(ConverterReadEntity entity, AnalysisContext analysisContext) {
LOGGER.info("解析到一条数据:", entity);
saveList.add(entity);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (saveList.size() >= BATCH_COUNT) {
batchSave();
// 存储完成清理 list
saveList.clear();
}
}

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

/**
* 批量保存方法
*/
private void batchSave() {
if (!saveList.isEmpty()) {
LOGGER.info("{}条数据,开始存储数据库!", saveList.size());
entityDao.batchSave(saveList);
LOGGER.info("存储数据库成功!");
}
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 日期、数字或者自定义格式转换
* <p>
* 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()}
* <p>1. 创建excel对应的实体对象 参照{@link ConverterReadEntity}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterReadListener}
* <p>3. 直接读即可
*/
@Test
public void testConvertRead() {
String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE;
// 这里 需要指定读用哪个class去读,然后读取第一个sheet
EasyExcel.read(fileName, ConverterReadEntity.class, new ConverterReadListener())
// 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。
// 如果就想单个字段使用请使用@ExcelProperty 指定converter
// .registerConverter(new CustomStringStringConverter())
// 读取sheet
.sheet().doRead();
}

EasyExcel保姆级教程(1)
https://github.com/yangxiangnanwill/yangxiangnanwill.github.io/2024/01/03/好好码代码吖/JAVA/POI/EasyExcel保姆级教程(1)/
作者
will
发布于
2024年1月3日
许可协议