EasyExcel拓展,让你的Execl操作更加简单

3,917 阅读3分钟

一、背景

        最近在做Execl数据导入的需求的时候发现团队这边Excel工具类实现方式都不一样,有的基于poi实现,有的是基于easyexcel实现,而且封装的方式也不一样,没有一个统一的使用方式,所以基于该问题对Execl操作工具进行了调研。

二、常用框架对比

业界常用的框架有POI和EasyExecl两个框架,执行流程图对比如下:

POI

对照上面的流程图,当利用POI去读取Excel时,首先会将数据全部加载到内存中,然后返回给调用者,当数据量比较大时,及其容易发生OOM。

EasyExecl

对照上面的流程图,与POI 不用的是,EasyExcel主要是采用sax模式一行一行解析,并将一行的解析结果以观察者的模式通知处理,即使数据量较大时也不会发生OOM。

三、EasyExecl实现原理

官网地址:easyexcel.opensource.alibaba.com/

四、EasyExecl用法

参考官网API:

easyexcel.opensource.alibaba.com/docs/curren…

五、EasyExecl问题

1、每读取一个不同的Execl需要定义不同的监听器,使用起来比较繁琐

2、写入的时候原生代码比较零碎,对动态列,多sheet写使用起来代码复杂度高。

六、EasyExecl增强方案

1、设计原则

       读取:Consumer+环绕执行模式

       写入:自定义函数式接口

2、实现方式

2.1、单Sheet读

public class ExcelConsumerListener<T> extends AnalysisEventListener<T> {   private Consumer<List<T>> consumer;   private List<T> list;      @Override   public void invoke(T data, AnalysisContext analysisContext) {        if(Objects.isNull(data)){            return;        }        //所有属性为空是模板错误或者空表格        if(BeanUtils.checkAllFieldIsNULL(data)){            throw new RuntimeException("Excel内容存在错误");        }        list.add(data);        if (list.size() >= segmentSize) {            consumer.accept(list);            list.clear();        }    }
    @Override    public void doAfterAllAnalysed(AnalysisContext analysisContext) {        if(CollectionUtils.isEmpty(list)){            return;        }        consumer.accept(list);        list.clear();    }}

2.2、多Sheet读

public class MultiSheetExcelConsumerListener<T> extends AnalysisEventListener<T> {    @Override    public void invoke(T data, AnalysisContext analysisContext) {        if(Objects.isNull(data)){            return;        }        //所有属性为空是模板错误或者空表格        if(BeanUtils.checkAllFieldIsNULL(data)){            throw new RuntimeException("Excel内容存在错误");        }        list.add(data);        if (list.size() >= segmentSize) {            dealConsumer(analysisContext);        }    }
    @Override    public void doAfterAllAnalysed(AnalysisContext analysisContext) {        if(CollectionUtils.isEmpty(list)){            return;        }        dealConsumer(analysisContext);    }
    private void dealConsumer(AnalysisContext analysisContext){        MultiSheet<T> multiSheet=new MultiSheet<>();        multiSheet.setList(list);        multiSheet.setSheetNo(analysisContext.readSheetHolder().getSheetNo());        multiSheet.setSheetName(analysisContext.readSheetHolder().getSheetName());        consumer.accept(multiSheet);        list.clear();    }}

2.3、动态列读

public class NoModelExcelConsumerListener<T> extends AnalysisEventListener<Map<Integer,String>> {    @Override    public void invokeHeadMap(Map headMap, AnalysisContext context) {        this.headMap=headMap;    }

    @Override    public void invoke(Map<Integer,String> data, AnalysisContext analysisContext) {        if(Objects.isNull(data)||data.isEmpty()){            return;        }        if(Objects.isNull(headMap)||headMap.isEmpty()){            return;        }        //所有属性为空是模板错误或者空表格        if(BeanUtils.checkAllFieldIsNULL(data)){            throw new RuntimeException("Excel内容存在错误");        }        //组装数据对象        Map<String,String> map=new HashMap<>();        headMap.forEach((k, v) -> {            map.put(v,data.get(k));        });        list.add(map);        if (list.size() >= segmentSize) {            consumer.accept(list);            list.clear();        }    }
    @Override    public void doAfterAllAnalysed(AnalysisContext analysisContext) {        if(CollectionUtils.isEmpty(list)){            return;        }        consumer.accept(list);    }}

2.4、写入

@FunctionalInterfacepublic interface ExcelWrite {    /**     * 写入     * @param excelWriter     * @param writeSheet     */    void doWrite(ExcelWriter excelWriter, WriteSheet writeSheet);}

七、EasyExecl增强后使用

1、读

//指定分片大小读取,最大支持2000,即segmentList大小为30ExcelUtil.read("D://20200903.xlsx", ExcelDataDTO.class,30,segmentList ->{    //对读取到的数据进行业务处理     }).sheet().doRead();
//动态列读取ExcelUtil.noModelRead("D://assign-apply.xlsx",2,segmentList ->{    //对读取到的数据进行业务处理}).sheet().doRead();
//多sheet读取InputStream inputStream=new FileInputStream("/Users/xxx/abc.xlsx");        ExcelUtil.readMultiSheet(inputStream, XxxxDTO.class, multiSheet -> {            List<XxxxDTO> list=multiSheet.getList();            if(CollectionUtils.isEmpty(list)){                return;        }}).doReadAll();

2、写

//testWrite为excel里sheet的名称ExcelUtil.write("D://20200903.xlsx","testWrite",ExcelDataDTO.class,(excelWriter,writeSheet) -> {    //查询数据list进行excel写入,如果list数据很大可以进行循环写入    excelWriter.write(list,writeSheet);});
//动态列写入//testWrite为excel里sheet的名称ExcelUtil.noModelWrite("D://20210510.xlsx","testWrite",head(),(excelWriter,writeSheet) -> {    //查询数据list进行excel写入,如果list数据很大可以进行循环写入cd    excelWriter.write(dataList(),writeSheet);});
//多sheet不同对象写ExcelUtil.write("/Users/xxx/20200903.xlsx","testWrite",ExcelDataDTO.class,(excelWriter,writeSheet) -> {            WriteSheet test1 = EasyExcel.writerSheet(0, "test1").head(ExcelDataDTO.class).build();            excelWriter.write(list1,test1);
            WriteSheet test2 = EasyExcel.writerSheet(1, "test2").head(ExcelDataOtherDTO.class).build();            excelWriter.write(list2,test2);});

参考资料

原文链接https://mp.weixin.qq.com/s?__biz=MzU2NzY1Mzk2Mw==&mid=2247483683&idx=1&sn=830a0ba36d1fa462d283a8a4d9640b05&chksm=fc98a207cbef2b11f1fdd1129718b75eea7672d84eec1548666bbdda1523256b5895b8edf7f4&token=417195537&lang=zh_CN#rd