Spring Boot学习(二十五):Spring Boot整合EasyExcel,操作Excel太简单了

前言

Spring Boot系列: 点击查看Spring Boot系列文章


EasyExcel

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称,能避免OOM(使用poi可能会OOM)。

EasyExcel能大大减少占用内存的主要原因是,在解析Excel时没有将文件一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。

以下是官方的介绍:

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

性能:64M内存1分钟内读取75M(46W行25列)的Excel,当然还有急速模式能更快,但是内存占用会在100M多一点


Spring Boot集成EasyExcel

1、添加依赖,EasyExcel的更新是特快的,对于EasyExcel,我的建议是使用较新的版本,因为比较旧的版本会有些小bug,所以我用的是最新的2.2.6

<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.6</version>
</dependency>

Spring Boot学习(二十五):Spring Boot整合EasyExcel,操作Excel太简单了_第1张图片

下面我们来按照官网的例子来实践下easyexcel的使用,以下只演示最基础的读写,需要更复杂的功能请查看官方文档

官方文档:https://www.yuque.com/easyexcel/doc/easyexcel


写excel

1、创建excel对应的实体对象

@Data
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}

解析: @ExcelProperty表示excel的列名,写入excle时会自动帮我们创建列名,并将相应的数据赋值到对应的列中


2、获取写入数据,在这里是自己生成的数据对象,实际中,我们根据自己业务来生成数据对象

    //创建写入数据
    private List<DemoData> data() {
        List<DemoData> list = new ArrayList<DemoData>();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        return list;
    }

3、测试写入excel

    @Test
    public void simpleWrite() {
        // 写法1
        String fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        System.out.println(TestFileUtil.getPath());
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());

        // 写法2
        fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写
        ExcelWriter excelWriter = null;
        try {
            excelWriter = EasyExcel.write(fileName, DemoData.class).build();
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            excelWriter.write(data(), writeSheet);
        } finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

解析:
第一种写入的方法是通过pathName(要写入的路径文件名,如果没有路径则默认为项目根路径下)和excel实体类的class类型来生成一个ExcelWriterBuilder构造器。通过ExcelWriterBuilder的sheet(String sheetName)来生成一个指定名称的sheet。然后通过doWrite来进行写入,doWrite会自动关闭流

sheet(String sheetName)默认是生成第一个sheet

第二种方法写入方法是先通过pathName(要写入的路径文件名,如果没有路径则默认为项目根路径下)和excel实体类的class类型来生成一个ExcelWriter,然后创建WriteSheet对象(sheet名称和数量),最后调用ExcelWriter的write来写入数据,不自动关闭流,所以需要手动关闭流。

第二种方法可以写入多个sheet,如下代码,就写入了两个sheet

        ExcelWriter excelWriter = null;
        try {
            excelWriter = EasyExcel.write(fileName, DemoData.class).build();
            WriteSheet writeSheet = EasyExcel.writerSheet(0,"模板1").build();
            WriteSheet writeSheet1 = EasyExcel.writerSheet(1,"模板2").build();
            excelWriter.write(data(), writeSheet);
            excelWriter.write(data(),writeSheet1);
        } finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }

解析:写入多个sheet只需要创建多个WriteSheet 对象,然后调用多次ExcelWriter 的write即可。

运行测试方法后,可以在项目的根路径下看到生成的excel文件

Spring Boot学习(二十五):Spring Boot整合EasyExcel,操作Excel太简单了_第2张图片


读excel

1、创建回调监听器,监听器用于处理EasyExcel中读取的数据

// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener  extends AnalysisEventListener<DemoData> {

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


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

    }

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


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

}

解析:EasyExcel的回调监听器其实就是一个继承AnalysisEventListener抽象类的类。继承AnalysisEventListener抽象类需要实现两个方法,一个invoke()是每解析一条数据都要调用一次的方法,我们在这个方法里可以对读取到的数据进行处理。还有一个方法是doAfterAllAnalysed(),这个方法是读取完一个sheet所有数据后调用的一个方法,我们根据自己的需求来进行逻辑处理,如:确保最后遗留的数据也存储到数据库。

2、读取excel

    @Test
    public void simpleRead() {
        // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
        // 写法1:
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

        // 写法2:
        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        ExcelReader excelReader = null;
        try {
            excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            ExcelReader read = excelReader.read(readSheet);
        } finally {
            if (excelReader != null) {
                // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
                excelReader.finish();
            }
        }
    }

注:file.separator这个代表系统目录中的间隔符,说白了就是斜线,不过有时候需要双线,有时候是单线,你用这个静态变量就解决兼容问题了。在 UNIX 系统上,此字段的值为 ‘/’;在 Microsoft Windows 系统上,它为 ‘’。

上述例子都是读取一个sheet,有时候我们需要读取全部sheet或者多个sheet,那么我们看下面的例子

 // 读取全部sheet
 // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写
 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();

 //读取全部sheet,使用doReadAll方法。doReadAll方法会自动关闭流,所以我们不需要手动关流

  public void doReadAll() {
        ExcelReader excelReader = build();
        excelReader.readAll();
        excelReader.finish();
    }


        // 读取部分sheet
        ExcelReader excelReader = null;
        try {
            excelReader = EasyExcel.read(fileName).build();

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

你可能感兴趣的:(springboot,java,spring,boot,poi,spring,后端)