EasyExcel详解(结合官方文档)

EasyExcel

零、前言

复制代码
文章是根据官方文档,加上自己的测试运行总结出来的,目前只总结的EasyExcel读的部分,写的部分还未完结,后续会更新

1、官方文档

复制代码
https://easyexcel.opensource.alibaba.com/

2、EasyExcel的maven依赖

xml 复制代码
<!--Easy Excel-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.7</version>
</dependency>

一、具体使用演示&说明

1、读Excel

1.1 简单的读

创建一个读取对象,存储读取的内容

java 复制代码
/**
 * 读对象
 * @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;
}

创建一个读的监听器

java 复制代码
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文件

java 复制代码
//根据文件名称,获取Excel目录地址
private String getExcelUrl(String docName){
    return this.getClass().getClassLoader().getResource("").getPath()  + docName;
}

之后创建一个单元测试类,去进行简单的读取操作

① 方法一:JDK8+ ,不用额外写一个DemoDataListener

实现代码:

java 复制代码
//写法一
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帮我们写好的读的监听器,我们直接拿来用就好了

java 复制代码
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的值:

java 复制代码
public PageReadListener(Consumer<List<T>> consumer, int batchCount) {
        this.cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        this.consumer = consumer;
        this.batchCount = batchCount;
    }

完整调用构造器方式如下

java 复制代码
//写法一
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

其实质上是和方法一一样,只不过将监听器写成了内部类的方式去实现

代码实现:

java 复制代码
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的实现类

代码实现:

java 复制代码
//写法三
String fileName = getExcelUrl("readExcel.xlsx");
//有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, ReadDemoData.class, new ReadDemoDataListener()).sheet().doRead();

ReadDemoDataListener

java 复制代码
@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结构如下:

java 复制代码
// ResourceType 是要操作的资源类型
// initialization 是初始化资源的表达式。
//在 try 块中使用资源的代码块执行完毕后,会自动调用资源的 close() 方法进行关闭
try (ResourceType resource = initialization) {
    // 使用资源的代码块
} catch (ExceptionType exception) {
    // 异常处理代码块
}

代码实现:

java 复制代码
//写法四
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);
}

运行结果:

1.2 指定列的下标或者列名

① 创建读对象:

java 复制代码
/** 指定列的下标或者列名的读对象
 * @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注解说明:

java 复制代码
@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中的监听器即可)

java 复制代码
/**
 * @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("存储数据库成功!");
    }
}

③单元测试代码

java 复制代码
/**
     * 指定列的下标或者列名
     *
     * <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;
}

④运行结果

1.3 读多个sheet

①创建Excel表

② 创建对象

这里使用1.1的对象ReadDemoData

java 复制代码
@Data  //生成getter、setter、toString、equals、hashCode等方法
public class ReadDemoData {

    private String string;

    private Date date;

    private Double doubleData;
}

③创建监听器

使用1.1的监听器ReadDemoDataListener

java 复制代码
/**
 *
 * @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

java 复制代码
/**
     * 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

java 复制代码
/**
     * 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);
    }


}

运行结果:

1.4 日期、数字或者自定义格式转换

① 创建自定义类型转换器

java 复制代码
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());
    }

}

② 创建对象

java 复制代码
/** 自定义格式转换对象
 * @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;
}

③ 自定义格式转换监听器

java 复制代码
/**
 * 自定义格式转换监听器
 * @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单元格格式

右击单元格,选择设置单元格格式

将日期标题的第一个单元格内容格式改成日期

将数字标题中第一个单元格内容改成数值

⑤单元测试

java 复制代码
 /**
     * 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();
}

运行结果:

1.5 日期、数字或者自定义格式转换
1.6 日期、数字或者自定义格式转换
1.7 读取表头数据

①监听器中重写invokeHeadMap方法方法

java 复制代码
/**
 *	读取表头数据监听器
 * @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("存储数据库成功!");
    }
}

②单元测试

java 复制代码
 /**
     * 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();
}

运行结果:

2、写Excel

相关推荐
Chen-Edward9 分钟前
有了Spring为什么还有要Spring Boot?
java·spring boot·spring
magic3341656328 分钟前
Springboot整合MinIO文件服务(windows版本)
windows·spring boot·后端·minio·文件对象存储
陈小桔1 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!1 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg36781 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July2 小时前
Hikari连接池
java
微风粼粼2 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad2 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud
天若有情6732 小时前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
祈祷苍天赐我java之术2 小时前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap