文章是根据官方文档,加上自己的测试运行总结出来的,目前只总结的EasyExcel读的部分,写的部分还未完结,后续会更新
https://easyexcel.opensource.alibaba.com/
<!--Easy Excel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.7</version>
</dependency>
创建一个读取对象,存储读取的内容
/**
* 读对象
* @author banana
* @create 2023-12-26 11:39
*/
@Data //生成getter、setter、toString、equals、hashCode等方法
public class ReadDemoData {
private String string;
private Date date;
private Double doubleData;
}
创建一个读的监听器
package com.example.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson2.JSON;
import com.example.model.pojo.ReadDemoData;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
/**
*
* @author banana
* @create 2023-12-26 11:50
*/
@Slf4j //Lombok 注解之一,它可以帮助我们在 Java 类中自动添加日志记录功能
public class ReadDemoDataListener implements ReadListener<ReadDemoData> {
//每隔100条存储数据库,然后清理list,方便内存的回收
private static final int BATCH_COUNT = 100;
//缓存数据
private List<ReadDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
@Override
public void onException(Exception e, AnalysisContext analysisContext) throws Exception {
}
//在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
}
//在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
@Override
public void invoke(ReadDemoData readDemoData, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JSON.toJSONString(readDemoData));
cachedDataList.add(readDemoData);
//达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
//目的:防止几万条数据在内存中,容易OOM
if(cachedDataList.size() >= BATCH_COUNT)
{
//一些业务操作(如存储数据库)
//……
saveData();
//清除缓存
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
//在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
@Override
public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
}
//在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//一些业务操作(如存储数据库,简单打印剩余数据)
cachedDataList.stream().forEach(System.out::println);
log.info("所有数据解析完成!");
}
//判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
// 如果返回 false,则结束读取数据的过程。
@Override
public boolean hasNext(AnalysisContext analysisContext) {
return false;
}
//模拟数据存储(这里就是简单的打印一下)
private void saveData(){
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
cachedDataList.stream().forEach(System.out::println);
log.info("存储数据库成功!");
}
}
由于我们之后的测试都要在单元测试中进行,因此我们在单元测试目录下创建一个resources
目录,用于后面对excel
文件的读取
创建一个excel
文件,名称为readExcel
该excel
文件中共有四百十条数据
在单元测试中创建一个方法,专门根据名称去读取单元测试目录中resources
目录下保存的excel
文件
//根据文件名称,获取Excel目录地址
private String getExcelUrl(String docName){
return this.getClass().getClassLoader().getResource("").getPath() + docName;
}
之后创建一个单元测试类,去进行简单的读取操作
① 方法一:JDK8+ ,不用额外写一个DemoDataListener
实现代码:
//写法一
String fileName = getExcelUrl("readExcel.xlsx");
// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
// 具体需要返回多少行可以在`PageReadListener`的构造函数设置
EasyExcel.read(fileName, ReadDemoData.class, new PageReadListener<ReadDemoData>(dataList ->{
for(ReadDemoData demoData : dataList){
log.info("读取到一条数据{}", JSON.toJSONString(demoData));
}
})).sheet().doRead();
具体运行结果:
我们可以看下PageReadListener
源码,其就是继承ReadListener
接口的一个实现类,相当于是EasyExcel
帮我们写好的读的监听器,我们直接拿来用就好了
public class PageReadListener<T> implements ReadListener<T> {
public static int BATCH_COUNT = 100;
private List<T> cachedDataList;
private final Consumer<List<T>> consumer;
private final int batchCount;
public PageReadListener(Consumer<List<T>> consumer) {
this(consumer, BATCH_COUNT);
}
public PageReadListener(Consumer<List<T>> consumer, int batchCount) {
this.cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
this.consumer = consumer;
this.batchCount = batchCount;
}
public void invoke(T data, AnalysisContext context) {
this.cachedDataList.add(data);
if (this.cachedDataList.size() >= this.batchCount) {
this.consumer.accept(this.cachedDataList);
this.cachedDataList = ListUtils.newArrayListWithExpectedSize(this.batchCount);
}
}
public void doAfterAllAnalysed(AnalysisContext context) {
if (CollectionUtils.isNotEmpty(this.cachedDataList)) {
this.consumer.accept(this.cachedDataList);
}
}
}
并且其每次读取100条数据,然后返回过来,执行监听器中的方法。如果我们需要具体指定需要返回多少行,可以调用PageReadListener
的构造器进行设置,如下所示:
调用函数PageReadListener
构造器,指定batchCount
的值:
public PageReadListener(Consumer<List<T>> consumer, int batchCount) {
this.cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
this.consumer = consumer;
this.batchCount = batchCount;
}
完整调用构造器方式如下
//写法一
String fileName = getExcelUrl("readExcel.xlsx");
// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
// 具体需要返回多少行可以在`PageReadListener`的构造函数设置
EasyExcel.read(fileName, ReadDemoData.class, new PageReadListener<ReadDemoData>(dataList ->{
for(ReadDemoData demoData : dataList){
log.info("读取到一条数据{}", JSON.toJSONString(demoData));
}
}, 20)).sheet().doRead();
每次到达监听器时的数量(指定batchCount前):
每次到达监听器时的数量(指定batchCount后):
② 方法二:匿名内部类 不用额外写一个DemoDataListener
其实质上是和方法一一样,只不过将监听器写成了内部类的方式去实现
代码实现:
String fileName = getExcelUrl("readExcel.xlsx");
EasyExcel.read(fileName, ReadDemoData.class, new ReadListener<ReadDemoData>() {
//单次缓存数据量
public static final int BATCH_COUNT = 100;
//临时存储
private List<ReadDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
@Override
public void invoke(ReadDemoData readDemoData, AnalysisContext analysisContext) {
cachedDataList.add(readDemoData);
if(cachedDataList.size() >= BATCH_COUNT){
saveData();
//存储完成,清理list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
saveData();
}
//模拟数据存储(这里就是简单的打印一下)
private void saveData(){
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
cachedDataList.stream().forEach(System.out::println);
log.info("存储数据库成功!");
}
}).sheet().doRead();
运行结果:
③ 方式三:使用自定义的ReadListener
的实现类
代码实现:
//写法三
String fileName = getExcelUrl("readExcel.xlsx");
//有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, ReadDemoData.class, new ReadDemoDataListener()).sheet().doRead();
ReadDemoDataListener
:
@Slf4j //Lombok 注解之一,它可以帮助我们在 Java 类中自动添加日志记录功能
public class ReadDemoDataListener implements ReadListener<ReadDemoData> {
//每隔100条存储数据库,然后清理list,方便内存的回收
private static final int BATCH_COUNT = 100;
//缓存数据
private List<ReadDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
@Override
public void onException(Exception e, AnalysisContext analysisContext) throws Exception {
}
//在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
}
//在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
@Override
public void invoke(ReadDemoData readDemoData, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JSON.toJSONString(readDemoData));
cachedDataList.add(readDemoData);
//达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
//目的:防止几万条数据在内存中,容易OOM
if(cachedDataList.size() >= BATCH_COUNT)
{
//一些业务操作(如存储数据库)
//……
saveData();
//清除缓存
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
//在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
@Override
public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
}
//在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//一些业务操作(如存储数据库,简单打印剩余数据)
cachedDataList.stream().forEach(System.out::println);
log.info("所有数据解析完成!");
}
//判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
// 如果返回 false,则结束读取数据的过程。
@Override
public boolean hasNext(AnalysisContext analysisContext) {
return true;
}
//模拟数据存储(这里就是简单的打印一下)
private void saveData(){
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
cachedDataList.stream().forEach(System.out::println);
log.info("存储数据库成功!");
}
}
运行结果:
④ 方法四:使用 build
方法构建 ExcelReader 对象
使用了try-with-resources
结构,通过使用 try-with-resources 结构,可以确保在代码块执行完毕或者出现异常时,会自动关闭在 try 括号中声明的资源,下面的ExcelReader
对象属于一个需要手动关闭的资源,try-with-resources 可以确保在读取操作结束后会自动关闭 ExcelReader,释放相关资源,无需手动编写关闭资源的代码。
try-with-resources
结构如下:
// ResourceType 是要操作的资源类型
// initialization 是初始化资源的表达式。
//在 try 块中使用资源的代码块执行完毕后,会自动调用资源的 close() 方法进行关闭
try (ResourceType resource = initialization) {
// 使用资源的代码块
} catch (ExceptionType exception) {
// 异常处理代码块
}
代码实现:
//写法四
String fileName = getExcelUrl("readExcel.xlsx");
// 一个文件一个reader
try (ExcelReader excelReader = EasyExcel.read(fileName, ReadDemoData.class, new ReadDemoDataListener()).build()) {
// 构建一个sheet 这里可以指定名字或者no
ReadSheet readSheet = EasyExcel.readSheet(0).build();
// 读取一个sheet
excelReader.read(readSheet);
}
运行结果:
① 创建读对象:
/** 指定列的下标或者列名的读对象
* @author banana
* @create 2023-12-26 21:00
*/
@Data
public class IndexOrNameData {
/**
* 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
*/
@ExcelProperty(index = 2)
private Double doubleData;
/**
* 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
*/
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
}
@ExcelProperty
注解说明:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelProperty {
//字符串数组类型,默认值为 ""。用于指定 Excel 中的列名,可以指定多个列名,用于匹配 Excel 文件中的列,用于读取或写入 Excel 数据时的映射关系
String[] value() default {""};
//整型变量,默认值为 -1。用于指定 Excel 中的列索引,用于读取或写入 Excel 数据时的映射关系
int index() default -1;
//整型变量,默认值为 2147483647。用于指定 Excel 中的列顺序,表示在 Excel 文件中的列的位置
int order() default 2147483647;
//泛型类型为 Converter 的 Class,默认值为 AutoConverter.class。用于指定转换器,用于在读取或写入 Excel 数据时进行数据类型的转换
Class<? extends Converter<?>> converter() default AutoConverter.class;
//原来用于指定格式化字符串,用于读取或写入 Excel 数据时的格式化(过时)
/** @deprecated */
@Deprecated
String format() default "";
}
②创建一个监听器(使用1.1中的监听器即可)
/**
* @author banana
* @create 2023-12-26 21:14
*/
@Slf4j
public class IndexOrNameDataListener implements ReadListener<IndexOrNameData> {
//每隔100条存储数据库,然后清理list,方便内存的回收
private static final int BATCH_COUNT = 100;
//缓存数据
private List<IndexOrNameData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
@Override
public void onException(Exception e, AnalysisContext analysisContext) throws Exception {
}
//在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
}
//在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
@Override
public void invoke(IndexOrNameData indexOrNameData, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JSON.toJSONString(indexOrNameData));
cachedDataList.add(indexOrNameData);
//达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
//目的:防止几万条数据在内存中,容易OOM
if(cachedDataList.size() >= BATCH_COUNT)
{
//一些业务操作(如存储数据库)
//……
saveData();
//清除缓存
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
//在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
@Override
public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
}
//在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//一些业务操作(如存储数据库,简单打印剩余数据)
cachedDataList.stream().forEach(System.out::println);
log.info("所有数据解析完成!");
}
//判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
// 如果返回 false,则结束读取数据的过程。
@Override
public boolean hasNext(AnalysisContext analysisContext) {
return true;
}
//模拟数据存储(这里就是简单的打印一下)
private void saveData(){
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
cachedDataList.stream().forEach(System.out::println);
log.info("存储数据库成功!");
}
}
③单元测试代码
/**
* 指定列的下标或者列名
*
* <p>1. 创建excel对应的实体对象,并使用 ExcelProperty注解. 参照IndexOrNameData
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照 IndexOrNameDataListener
* <p>3. 直接读即可
*/
@Test
public void indexOrNameRead() {
String fileName = getExcelUrl("readExcel.xlsx");
// 这里默认读取第一个sheet
EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
}
//根据文件名称,获取Excel目录地址
private String getExcelUrl(String docName){
return this.getClass().getClassLoader().getResource("").getPath() + docName;
}
④运行结果
①创建Excel表
② 创建对象
这里使用1.1的对象ReadDemoData
@Data //生成getter、setter、toString、equals、hashCode等方法
public class ReadDemoData {
private String string;
private Date date;
private Double doubleData;
}
③创建监听器
使用1.1的监听器ReadDemoDataListener
/**
*
* @author banana
* @create 2023-12-26 11:50
*/
@Slf4j //Lombok 注解之一,它可以帮助我们在 Java 类中自动添加日志记录功能
public class ReadDemoDataListener implements ReadListener<ReadDemoData> {
//每隔100条存储数据库,然后清理list,方便内存的回收
private static final int BATCH_COUNT = 100;
//缓存数据
private List<ReadDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
@Override
public void onException(Exception e, AnalysisContext analysisContext) throws Exception {
}
//在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
}
//在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
@Override
public void invoke(ReadDemoData readDemoData, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JSON.toJSONString(readDemoData));
cachedDataList.add(readDemoData);
//达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
//目的:防止几万条数据在内存中,容易OOM
if(cachedDataList.size() >= BATCH_COUNT)
{
//一些业务操作(如存储数据库)
//……
saveData();
//清除缓存
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
//在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
@Override
public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
}
//在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//一些业务操作(如存储数据库,简单打印剩余数据)
cachedDataList.stream().forEach(System.out::println);
log.info("所有数据解析完成!");
}
//判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
// 如果返回 false,则结束读取数据的过程。
@Override
public boolean hasNext(AnalysisContext analysisContext) {
return true;
}
//模拟数据存储(这里就是简单的打印一下)
private void saveData(){
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
cachedDataList.stream().forEach(System.out::println);
log.info("存储数据库成功!");
}
}
④单元测试
方法一:通过doReadAll
读取全部sheet
/**
* 3 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
* <p>
* 1. 创建excel对应的实体对象 参照ReadDemoData
* <p>
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照ReadDemoDataListener
* <p>
* 3. 直接读即可
*/
@Test
public void repeateRead(){
//写法一:读取全部sheet
//获取Excel表的路径
String fileName = getExcelUrl("MultipleSheetExcel.xlsx");
// 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。
// 然后所有sheet都会往同一个DemoDataListener里面写
EasyExcel.read(fileName, ReadDemoData.class, new ReadDemoDataListener()).doReadAll();
}
运行结果:
前面是通过监听器的invoke
方法中的log.info("解析到一条数据:{}", JSON.toJSONString(readDemoData));
后面是通过模拟存储数据库中的saveData
方法打印出来的
关于后面为什么把Sheet1和Sheet2的内容全部打印出来,是因为设置的BATCH_COUNT
是100,没有清楚过缓存,所以执行saveData
方法的时候都打印出来了
方法二:通过doReadAll
读取全部sheet
/**
* 3 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
* <p>
* 1. 创建excel对应的实体对象 参照ReadDemoData
* <p>
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照ReadDemoDataListener
* <p>
* 3. 直接读即可
*/
@Test
public void repeateRead(){
//写法二:分开读取各个Sheet的信息
String fileName = getExcelUrl("MultipleSheetExcel.xlsx");
try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {
// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
ReadSheet readSheet1 =
EasyExcel.readSheet(0).head(ReadDemoData.class).registerReadListener(new ReadDemoDataListener()).build();
ReadSheet readSheet2 =
EasyExcel.readSheet(1).head(ReadDemoData.class).registerReadListener(new ReadDemoDataListener()).build();
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
excelReader.read(readSheet1, readSheet2);
}
}
运行结果:
① 创建自定义类型转换器
package com.example.converter;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.WriteCellData;
/** 自定义转换器
* @author banana
* @create 2023-12-26 23:15
*/
//将自定义转换器基础EasyExcel提供的转换接口Converter
public class CustomStringStringConverter implements Converter<String> {
//指定该转换器支持的 Java 类型,这里指定为 String.class
@Override
public Class<?> supportJavaTypeKey() {
return String.class;
}
//指定该转换器支持的 Excel 数据类型,这里指定为 CellDataTypeEnum.STRING,表示支持读取 Excel 中的字符串类型数据
/*
以下是 CellDataTypeEnum 中定义的所有单元格类型:
- BOOL:布尔类型
- ERROR:错误类型
- FORMULA:公式类型
- INLINE_STR:内联字符串类型
- NUMBER:数字类型
- STRING:字符串类型
- DATE:日期类型
- EMPTY:空类型
*/
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 将读取到的 Excel 数据转换为 Java 对象中的数据
* 这里读的时候会调用
*
* @param context
* @return
*/
@Override
public String convertToJavaData(ReadConverterContext<?> context) {
return "自定义:" + context.getReadCellData().getStringValue();
}
/**
* 用于将 Java 对象中的数据转换为写入 Excel 文件中的数据
* 这里是写的时候会调用 不用管
*
* @return
*/
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
return new WriteCellData<>(context.getValue());
}
}
② 创建对象
/** 自定义格式转换对象
* @author banana
* @create 2023-12-26 23:13
*/
@Data
public class ConverterData {
//我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:”
@ExcelProperty(converter = CustomStringStringConverter.class)
private String string;
//这里用string 去接日期才能格式化。我想接收年月日格式
@DateTimeFormat(pattern = "yyyy年MM月dd日HH时mm分ss秒")
private String date;
//我想接收百分比的数字
@NumberFormat("#.##%")
private String doubleData;
}
③ 自定义格式转换监听器
/**
* 自定义格式转换监听器
* @author banana
* @create 2023-12-26 23:29
*/
@Slf4j
public class ConverterDataListener implements ReadListener<ConverterData> {
//每隔100条存储数据库,然后清理list,方便内存的回收
private static final int BATCH_COUNT = 100;
//缓存数据
private List<ConverterData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
@Override
public void onException(Exception e, AnalysisContext analysisContext) throws Exception {
}
//在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
}
//在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
@Override
public void invoke(ConverterData converterData, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JSON.toJSONString(converterData));
cachedDataList.add(converterData);
//达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
//目的:防止几万条数据在内存中,容易OOM
if(cachedDataList.size() >= BATCH_COUNT)
{
//一些业务操作(如存储数据库)
//……
saveData();
//清除缓存
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
//在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
@Override
public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
}
//在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//一些业务操作(如存储数据库,简单打印剩余数据)
cachedDataList.stream().forEach(System.out::println);
log.info("所有数据解析完成!");
}
//判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
// 如果返回 false,则结束读取数据的过程。
@Override
public boolean hasNext(AnalysisContext analysisContext) {
return true;
}
//模拟数据存储(这里就是简单的打印一下)
private void saveData(){
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
cachedDataList.stream().forEach(System.out::println);
log.info("存储数据库成功!");
}
}
④设置excel单元格格式
右击单元格,选择设置单元格格式
将日期标题的第一个单元格内容格式改成日期
将数字标题中第一个单元格内容改成数值
⑤单元测试
/**
* 4、日期、数字或者自定义格式转换
* <p>
* 默认读的转换器DefaultConverterLoader、loadDefaultReadConverter()
* <p>1. 创建excel对应的实体对象 参照ConverterData.里面可以使用注解DateTimeFormat、NumberFormat或者自定义注解
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照ConverterDataListener
* <p>3. 直接读即可
*/
@Test
public void converterRead() {
String fileName = getExcelUrl("readExcel.xlsx");
// 这里 需要指定读用哪个class去读,然后读取第一个sheet
EasyExcel.read(fileName, ConverterData.class, new ConverterDataListener())
// 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。
// 如果就想单个字段使用请使用@ExcelProperty 指定converter
// .registerConverter(new CustomStringStringConverter())
// 读取sheet
.sheet().doRead();
}
运行结果:
①监听器中重写invokeHeadMap
方法方法
/**
* 读取表头数据监听器
* @author banana
* @create 2023-12-26 23:49
*/
@Slf4j
public class DemoHeadDataListener implements ReadListener<ReadDemoData> {
//每隔100条存储数据库,然后清理list,方便内存的回收
private static final int BATCH_COUNT = 100;
//缓存数据
private List<ReadDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
@Override
public void onException(Exception e, AnalysisContext analysisContext) throws Exception {
}
//在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
// 如果想转成成 Map<Integer,String>
// 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener
// 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换
}
//在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
@Override
public void invoke(ReadDemoData readDemoData, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JSON.toJSONString(readDemoData));
cachedDataList.add(readDemoData);
//达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
//目的:防止几万条数据在内存中,容易OOM
if(cachedDataList.size() >= BATCH_COUNT)
{
//一些业务操作(如存储数据库)
//……
saveData();
//清除缓存
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
//在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
@Override
public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
}
//在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//一些业务操作(如存储数据库,简单打印剩余数据)
cachedDataList.stream().forEach(System.out::println);
log.info("所有数据解析完成!");
}
//判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
// 如果返回 false,则结束读取数据的过程。
@Override
public boolean hasNext(AnalysisContext analysisContext) {
return true;
}
//模拟数据存储(这里就是简单的打印一下)
private void saveData(){
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
cachedDataList.stream().forEach(System.out::println);
log.info("存储数据库成功!");
}
}
②单元测试
/**
* 7、读取表头数据
*
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}
* <p>
* 3. 直接读即可
*/
@Test
public void headerRead() {
String fileName = getExcelUrl("readExcel.xlsx");
// 这里 需要指定读用哪个class去读,然后读取第一个sheet
EasyExcel.read(fileName, ReadDemoData.class, new DemoHeadDataListener()).sheet().doRead();
}
运行结果: