2021.05.08 easyExcel简单读写

前言

web开发中,我们经常会遇到数据导入或者导出excel的需求,如果用原生的poi(阿帕奇的*项目,项目地址:https://poi.apache.org/),虽然能够满足我们的需求,但是原生的接口用起来很不方便,数据格式都需要我们设定,而且它又是基于单元格的操作,在多个数据导出导入的场景下,每个场景都需要定制化的导出入方法,很不方便。

那有没有一个组件,能够根据我们的Vo导出excel,导出的时候,每行一条数据,传入一个lsit就可以实现所有数据的导出;导入的时候,指定vo,然后就可以把excel转成一个volist,方便我们的操作?

easyExcel是什么

有呀,当然有呀,easyExcel就是这样的一个组件,它是由阿里巴巴开发的一个基于Java的简单、省内存的读写Excel的开源组件,在尽可能节约内存的情况下支持读写百MExcel

github地址:https://github.com/alibaba/easyexcel

根据官方文档,该组件有着较好的性能:

2021.05.08 easyExcel简单读写

接下来,我们就来看下如何在我们自己的项目中快速地使用它。

简单应用

添加依赖

首先需要先引入easyExcel的依赖,目前最新的发布版本是2.2.6

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.6</version>
</dependency>

创建数据对应的VO

这里用到了lombox,所有就不需要自己创建gettersetter方法,如果你不想用lombox,可以手动创建getter/setter方法。

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

/**
 * @program: example-2021.05.08
 * @description: demoData
 * @author: syske
 * @date: 2021-05-08 18:12
 */
@Data
public class DemoData {
    @ExcelProperty("名称")
    private String name;

    @ExcelProperty("标题")
    private String title;

    @ExcelProperty("描述")
    private String description;
}

核心的点就一个,@ExcelProperty其实就是指定数据的表头,当然也可以通过注解的方式,直接指定表头样式

简单写excel

 public static String writeExcel() {
        String fileName = UUID.randomUUID().toString() + ".xlsx";
     	// 创建ExcelWriterBuilder
        ExcelWriterBuilder write = EasyExcel.write(fileName, DemoData.class);
        // 创建sheet
        ExcelWriterSheetBuilder sheet = write.sheet("模板");
       // 构建数据
        DemoData demoData = new DemoData();
        demoData.setDescription("描述信息");
        demoData.setName("名称");
        demoData.setTitle("标题");
        List<DemoData> dataList = Lists.newArrayList(demoData);
        // 写入数据
        sheet.doWrite(dataList);
        return fileName;
    }

注释已经够详细了,这里再简单解释下,我们先要创建ExcelWriterBuilder,这里要指定生成的excel的完整文件名(包括保存路径)和excel对应的VO;

然后再创建sheet,如果经常用excel的小伙伴应该知道,一个excel可以创建多个sheet,而且sheet的名字必须唯一,这里我们指定的sheet名称是“模板”;

再接着,我们要创建我们要写入的数据,也就是前面VO对应的List

最后,将List的数据写入。

跑一下上面的代码,会生成这样的excel

2021.05.08 easyExcel简单读写

我们并没有指定表头,但是发现生成的excel是有表头的,而且就是我们前面创建VO指定的ExcelProperty,这也印证了我们前面的说法,当然它也支持自定义表头,我们稍后再看

简单读excel

自定义解析事件监听

相比写,读稍微复杂一点,需要我们自定义一个EventListener,继承AnalysisEventListener就可以了:

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 = Lists.newArrayList();

    public DemoDataListener() {}


    /**
     * 这个每一条数据解析都会来调用
     *
     * @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));
        System.out.println("解析数据:" + JSON.toJSONString(data));
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }

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

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

这里其实就相当于excel的解析器,我们只需要重写相应的方法即可。 invoke(DemoData data, AnalysisContext context)方法是每解析一行数据,就会被调用,在这里你可以做数据的校验和转换操作;doAfterAllAnalysed(AnalysisContext context)是所有数据解析完成后进行的操作,类似回调函数,你可以根据自己的业务需求进行修改。

开始读

然后读取的话,就很简单了:

public static void readExcel(String filePath) {
        EasyExcel.read(filePath, DemoData.class, new DemoDataListener()).sheet().doRead();
    }

默认情况下,数据是从第二行开始读取的,第一行默认为表头,当然你也可以通过headRowNumber(Integer headRowNumber)指定从第几行开始读取:

EasyExcel.read(filePath, DemoData.class, new DemoDataListener()).sheet().headRowNumber(0).doRead();

另外,有一点需要注意,就是读取的时候,excel的字段顺序要和VO的字段顺序保持一致,否则会出现串行的问题,当然你也可以通过指定字段顺序的方式解决这个问题,索引顺序从0开始,当然这个index也会影响导出的excel里面的字段顺序。

2021.05.08 easyExcel简单读写

2021.05.08 easyExcel简单读写

设定写的样式

设定样式有两种方式,一种是通过代码的方式设定,另一种就是通过注解的方式实现,我们先看第一种方式。

代码设定样式
public static String writeExcel() {
        String fileName = UUID.randomUUID().toString() + ".xlsx";
        // 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景设置为红色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 20);
        headWriteCellStyle.setWriteFont(headWriteFont);
        // 设定bord
        headWriteCellStyle.setBorderBottom(BorderStyle.THIN);
        headWriteCellStyle.setBorderTop(BorderStyle.THIN);
        headWriteCellStyle.setBorderRight(BorderStyle.THIN);
        headWriteCellStyle.setBorderLeft(BorderStyle.THIN);
        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        // 背景绿色
        contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
         // 设定边框样式
        contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
        contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
        contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
        contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short) 20);
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        HorizontalCellStyleStrategy horizontalCellStyleStrategy =
                new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
		// 设定样式
        ExcelWriterBuilder write = EasyExcel.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy);
        ExcelWriterSheetBuilder sheet = write.sheet("模板");
        DemoData demoData = new DemoData();
        demoData.setDescription("描述信息");
        demoData.setName("名称");
        demoData.setTitle("标题");
        List<DemoData> dataList = Lists.newArrayList(demoData);
        sheet.doWrite(dataList);
        return fileName;
    }

代码和上面写的代码一样,只是增加了样式的设定,就是在创建ExcelWriterBuilder的时候,指定样式registerWriteHandler(horizontalCellStyleStrategy)

setFillForegroundColor设定背景颜色,也就是填充颜色;

setFontHeightInPoints设定字体大小;

setWriteFont是设定字体样式;

setBorder是设定边框样式;

然后我们再来看下生成的效果:

2021.05.08 easyExcel简单读写

但是我发现,这里的样式没法指定列宽,下面我们看下注解的样式是不是可以指定。

注解设定样式

首先我们看下列宽度如何设定,设置方式很简单,只需要在对应的字段上加上ColumnWidth注解即可:

2021.05.08 easyExcel简单读写

之后的效果如下

2021.05.08 easyExcel简单读写

然后,我们在通过注解设定其他样式,我在名称上面添加如下注解:

2021.05.08 easyExcel简单读写

然后生成的效果变成这样了:

2021.05.08 easyExcel简单读写

代码里面的样式设定也没有删除,说明启用注解之后,代码里面设定的样式就失效了。其中,HeadStyle是设定标题栏样式,HeadFontStyle是设定标题字体字体样式,ContentStyle是设定内容的样式,ContentFontStyle是设置内容的字体样式,样式的内容可以根据自己的需求进行调整,这里就不作过多的说明了。

总结

easyExcel对于列表式的数据导入导出,是特别友好的,而且用起来也特别方便,但是对于不规律的表格(比如票据)poi可能是更好的选择,当然easyExcel本身也是基于poi实现的,只是前者对用户更友好。

简单总结下,今天我们通过几个简单的示例,演示了如何用easyExcel实现数据的导入导出,展示了设置样式的两种方式,可以说上面的这些内容基本上可以满足我们大部分的应用场景,但如果你还有很多其他的业务需求,建议你再去研究下官方文档,更高级的用法还有很多,今天我们算是抛砖引玉了,有兴趣的小伙伴自己去看吧!

2021.05.08 easyExcel简单读写

好了,今天是母亲节,让我们一起祝所有的母亲节日快乐,当然更重要的是别忘了给自己的妈妈打个电话,给一句问候,一句关心……

项目路径:

https://github.com/Syske/example-everyday

本项目会每日更新,让我们一起学习,一起进步,遇见更好的自己,加油呀