EasyExcel文档

源自官方文档,仅供我个人学习使用,其他人请移步官方地址:https://easyexcel.opensource.alibaba.com/,官方文档更详细更好!!谢谢

新手必读

EasyExcel

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

网站

16M内存23秒读取75M(46W行25列)的Excel(3.2.1+版本)

当然还有极速模式能更快,但是内存占用会在100M多一点 img

帮忙点个⭐Star

开源不易,如果觉得EasyExcel对您的工作还是有帮助的话,请帮忙在github star 的右上角点个⭐Star,您的支持是使EasyExcel变得更好最大的动力。

如何获取帮助

如何获取帮助

维护者

姬朋飞(玉霄)、庄家钜

快速开始

读Excel

DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java

    /**     * 最简单的读     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}     * <p>3. 直接读即可     */    @Test    public void simpleRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();    }

写Excel

DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java

    /**     * 最简单的写     * <p>1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData}     * <p>2. 直接写即可     */    @Test    public void simpleWrite() {        String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        // 如果这里想使用03 则 传入excelType参数即可        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());    }

web上传、下载

DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java

   /**     * 文件下载(失败了会返回一个有部分数据的Excel)     * <p>     * 1. 创建excel对应的实体对象 参照{@link DownloadData}     * <p>     * 2. 设置返回的 参数     * <p>     * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大     */    @GetMapping("download")    public void download(HttpServletResponse response) throws IOException {        // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");        response.setCharacterEncoding("utf-8");        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系        String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");        EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());    }    /**     * 文件上传     * <p>1. 创建excel对应的实体对象 参照{@link UploadData}     * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}     * <p>3. 直接读即可     */    @PostMapping("upload")    @ResponseBody    public String upload(MultipartFile file) throws IOException {        EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead();        return "success";    }

读Excel

示例代码

DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java

最简单的读

最简单的读的excel示例

img

最简单的读的对象

@Getter@Setter@EqualsAndHashCodepublic class DemoData {    private String string;    private Date date;    private Double doubleData;}

最简单的读的监听器

// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去@Slf4jpublic class DemoDataListener implements ReadListener<DemoData> {    /**     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收     */    private static final int BATCH_COUNT = 100;    /**     * 缓存的数据     */    private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);    /**     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。     */    private DemoDAO demoDAO;    public DemoDataListener() {        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数        demoDAO = new DemoDAO();    }    /**     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来     *     * @param demoDAO     */    public DemoDataListener(DemoDAO demoDAO) {        this.demoDAO = demoDAO;    }    /**     * 这个每一条数据解析都会来调用     *     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}     * @param context     */    @Override    public void invoke(DemoData data, AnalysisContext context) {        log.info("解析到一条数据:{}", JSON.toJSONString(data));        cachedDataList.add(data);        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM        if (cachedDataList.size() >= BATCH_COUNT) {            saveData();            // 存储完成清理 list            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);        }    }    /**     * 所有数据解析完成了 都会来调用     *     * @param context     */    @Override    public void doAfterAllAnalysed(AnalysisContext context) {        // 这里也要保存数据,确保最后遗留的数据也存储到数据库        saveData();        log.info("所有数据解析完成!");    }    /**     * 加上存储数据库     */    private void saveData() {        log.info("{}条数据,开始存储数据库!", cachedDataList.size());        demoDAO.save(cachedDataList);        log.info("存储数据库成功!");    }}

持久层

/** * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。 **/public class DemoDAO {    public void save(List<DemoData> list) {        // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入    }}

代码

    /**     * 最简单的读     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}     * <p>     * 3. 直接读即可     */    @Test    public void simpleRead() {        // 写法1:JDK8+ ,不用额外写一个DemoDataListener        // since: 3.0.0-beta1        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行        // 具体需要返回多少行可以在`PageReadListener`的构造函数设置        EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {            for (DemoData demoData : dataList) {                log.info("读取到一条数据{}", JSON.toJSONString(demoData));            }        })).sheet().doRead();        // 写法2:        // 匿名内部类 不用额外写一个DemoDataListener        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭        EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() {            /**             * 单次缓存的数据量             */            public static final int BATCH_COUNT = 100;            /**             *临时存储             */            private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);            @Override            public void invoke(DemoData data, AnalysisContext context) {                cachedDataList.add(data);                if (cachedDataList.size() >= BATCH_COUNT) {                    saveData();                    // 存储完成清理 list                    cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);                }            }            @Override            public void doAfterAllAnalysed(AnalysisContext context) {                saveData();            }            /**             * 加上存储数据库             */            private void saveData() {                log.info("{}条数据,开始存储数据库!", cachedDataList.size());                log.info("存储数据库成功!");            }        }).sheet().doRead();        // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去        // 写法3:        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();        // 写法4        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 一个文件一个reader        try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) {            // 构建一个sheet 这里可以指定名字或者no            ReadSheet readSheet = EasyExcel.readSheet(0).build();            // 读取一个sheet            excelReader.read(readSheet);        }    }

指定列的下标或者列名

excel示例

参照:最简单的读的excel示例

对象

@Getter@Setter@EqualsAndHashCodepublic class IndexOrNameData {    /**     * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配     */    @ExcelProperty(index = 2)    private Double doubleData;    /**     * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据     */    @ExcelProperty("字符串标题")    private String string;    @ExcelProperty("日期标题")    private Date date;}

监听器

参照:最简单的读的监听器 只是泛型变了而已

代码

    /**     * 指定列的下标或者列名     *     * <p>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}     * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}     * <p>3. 直接读即可     */    @Test    public void indexOrNameRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里默认读取第一个sheet        EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();    }

读多个sheet

excel示例

参照:最简单的读的excel示例

对象

参照:最简单的读的对象

监听器

参照:最简单的读的监听器

代码

    /**     * 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}     * <p>     * 3. 直接读即可     */    @Test    public void repeatedRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 读取全部sheet        // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();        // 读取部分sheet        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 写法1        try (ExcelReader 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);        }    }

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

excel示例

参照:最简单的读的excel示例

对象

@Getter@Setter@EqualsAndHashCodepublic class ConverterData {    /**     * 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:”     */    @ExcelProperty(converter = CustomStringStringConverter.class)    private String string;    /**     * 这里用string 去接日期才能格式化。我想接收年月日格式     */    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")    private String date;    /**     * 我想接收百分比的数字     */    @NumberFormat("#.##%")    private String doubleData;}

监听器

参照:最简单的读的监听器 只是泛型变了

自定义转换器

public class CustomStringStringConverter implements Converter<String> {    @Override    public Class<?> supportJavaTypeKey() {        return String.class;    }    @Override    public CellDataTypeEnum supportExcelTypeKey() {        return CellDataTypeEnum.STRING;    }    /**     * 这里读的时候会调用     *     * @param context     * @return     */    @Override    public String convertToJavaData(ReadConverterContext<?> context) {        return "自定义:" + context.getReadCellData().getStringValue();    }    /**     * 这里是写的时候会调用 不用管     *     * @return     */    @Override    public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {        return new WriteCellData<>(context.getValue());    }}

代码

    /**     * 日期、数字或者自定义格式转换     * <p>     * 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()}     * <p>1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解     * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener}     * <p>3. 直接读即可     */    @Test    public void converterRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里 需要指定读用哪个class去读,然后读取第一个sheet         EasyExcel.read(fileName, ConverterData.class, new ConverterDataListener())            // 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。            // 如果就想单个字段使用请使用@ExcelProperty 指定converter            // .registerConverter(new CustomStringStringConverter())            // 读取sheet            .sheet().doRead();    }

多行头

excel示例

参照:最简单的读的excel示例

对象

参照:最简单的读的对象

监听器

参照:最简单的读的监听器

代码

    /**     * 多行头     *     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}     * <p>3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数,     * 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。     */    @Test    public void complexHeaderRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里 需要指定读用哪个class去读,然后读取第一个sheet         EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet()            // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行            .headRowNumber(1).doRead();    }

同步的返回

excel示例

参照:excel示例

对象

参照:对象

代码

    /**     * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面     */    @Test    public void synchronousRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish        List<DemoData> list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync();        for (DemoData data : list) {            LOGGER.info("读取到数据:{}", JSON.toJSONString(data));        }        // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish        List<Map<Integer, String>> listMap = EasyExcel.read(fileName).sheet().doReadSync();        for (Map<Integer, String> data : listMap) {            // 返回每条数据的键值对 表示所在的列 和所在列的值            LOGGER.info("读取到数据:{}", JSON.toJSONString(data));        }    }

读取表头数据

excel示例

参照:最简单的读的excel示例

对象

参照:最简单的读的对象

监听器

参照:最简单的读的监听器 里面多了一个方法,只要重写invokeHeadMap方法即可

    /**     * 这里会一行行的返回头     *     * @param headMap     * @param context     */    @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) 自动会转换    }

代码

    /**     * 读取表头数据     *     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}     * <p>     * 3. 直接读即可     */    @Test    public void headerRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里 需要指定读用哪个class去读,然后读取第一个sheet        EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead();    }

额外信息(批注、超链接、合并单元格信息读取)

since

2.0.0-beta1

excel示例

img

对象

@Getter@Setter@EqualsAndHashCodepublic class DemoExtraData {    private String row1;    private String row2;}

监听器

参照:最简单的读的监听器 里面多了一个 extra 方法

@Slf4jpublic class DemoExtraListener implements ReadListener<DemoExtraData> {    @Override    public void invoke(DemoExtraData data, AnalysisContext context) {}    @Override    public void doAfterAllAnalysed(AnalysisContext context) {}    @Override    public void extra(CellExtra extra, AnalysisContext context) {        log.info("读取到了一条额外信息:{}", JSON.toJSONString(extra));        switch (extra.getType()) {            case COMMENT:                log.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(),                    extra.getText());                break;            case HYPERLINK:                if ("Sheet1!A1".equals(extra.getText())) {                    log.info("额外信息是超链接,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(),                        extra.getColumnIndex(), extra.getText());                } else if ("Sheet2!A1".equals(extra.getText())) {                    log.info(                        "额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{},"                            + "内容是:{}",                        extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),                        extra.getLastColumnIndex(), extra.getText());                } else {                    Assert.fail("Unknown hyperlink!");                }                break;            case MERGE:                log.info(                    "额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",                    extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),                    extra.getLastColumnIndex());                break;            default:        }    }}

代码

    /**     * 额外信息(批注、超链接、合并单元格信息读取)     * <p>     * 由于是流式读取,没法在读取到单元格数据的时候直接读取到额外信息,所以只能最后通知哪些单元格有哪些额外信息     *     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoExtraData}     * <p>     * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExtraListener}     * <p>     * 3. 直接读即可     *     * @since 2.2.0-beat1     */    @Test    public void extraRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "extra.xlsx";        // 这里 需要指定读用哪个class去读,然后读取第一个sheet        EasyExcel.read(fileName, DemoExtraData.class, new DemoExtraListener())            // 需要读取批注 默认不读取            .extraRead(CellExtraTypeEnum.COMMENT)            // 需要读取超链接 默认不读取            .extraRead(CellExtraTypeEnum.HYPERLINK)            // 需要读取合并单元格信息 默认不读取            .extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();    }

读取公式和单元格类型

excel示例

img

对象

@Getter@Setter@EqualsAndHashCodepublic class CellDataReadDemoData {    private CellData<String> string;    // 这里注意 虽然是日期 但是 类型 存储的是number 因为excel 存储的就是number    private CellData<Date> date;    private CellData<Double> doubleData;    // 这里并不一定能完美的获取 有些公式是依赖性的 可能会读不到 这个问题后续会修复    private CellData<String> formulaValue;}

监听器

参照:最简单的读的监听器 代码

       /**     * 读取公式和单元格类型     *     * <p>     * 1. 创建excel对应的实体对象 参照{@link CellDataReadDemoData}     * <p>     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}     * <p>     * 3. 直接读即可     *     * @since 2.2.0-beat1     */    @Test    public void cellDataRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "cellDataDemo.xlsx";        // 这里 需要指定读用哪个class去读,然后读取第一个sheet        EasyExcel.read(fileName, CellDataReadDemoData.class, new CellDataDemoHeadDataListener()).sheet().doRead();    }

数据转换等异常处理

excel示例

参照:最简单的读的excel示例

对象

@Getter@Setter@EqualsAndHashCodepublic class ExceptionDemoData {    /**     * 用日期去接字符串 肯定报错     */    private Date date;}

监听器

参照:最简单的读的监听器 里面多了一个方法,只要重写onException方法即可

    /**     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。     *     * @param exception     * @param context     * @throws Exception     */    @Override    public void onException(Exception exception, AnalysisContext context) {        log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());        // 如果是某一个单元格的转换异常 能获取到具体行号        // 如果要获取头的信息 配合invokeHeadMap使用        if (exception instanceof ExcelDataConvertException) {            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;            log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),                excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());        }    }

代码

    /**     * 数据转换等异常处理     *     * <p>     * 1. 创建excel对应的实体对象 参照{@link ExceptionDemoData}     * <p>     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExceptionListener}     * <p>     * 3. 直接读即可     */    @Test    public void exceptionRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里 需要指定读用哪个class去读,然后读取第一个sheet        EasyExcel.read(fileName, ExceptionDemoData.class, new DemoExceptionListener()).sheet().doRead();    }

不创建对象的读

excel示例

参照:最简单的读的excel示例

监听器

@Slf4jpublic class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>> {    /**     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收     */    private static final int BATCH_COUNT = 5;    private List<Map<Integer, String>> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);    @Override    public void invoke(Map<Integer, String> data, AnalysisContext context) {        log.info("解析到一条数据:{}", JSON.toJSONString(data));        cachedDataList.add(data);        if (cachedDataList.size() >= BATCH_COUNT) {            saveData();            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);        }    }    @Override    public void doAfterAllAnalysed(AnalysisContext context) {        saveData();        log.info("所有数据解析完成!");    }    /**     * 加上存储数据库     */    private void saveData() {        log.info("{}条数据,开始存储数据库!", cachedDataList.size());        log.info("存储数据库成功!");    }}

代码

    /**     * 不创建对象的读     */    @Test    public void noModelRead() {        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        // 这里 只要,然后读取第一个sheet 同步读取会自动finish        EasyExcel.read(fileName, new NoModelDataListener()).sheet().doRead();    }

web中的读

示例代码

DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java

excel示例

参照:最简单的读的excel示例

对象

参照:最简单的读的对象 只是名字变了

监听器

参照:最简单的读的监听器 只是泛型变了

代码

    /**     * 文件上传     * <p>     * 1. 创建excel对应的实体对象 参照{@link UploadData}     * <p>     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}     * <p>     * 3. 直接读即可     */    @PostMapping("upload")    @ResponseBody    public String upload(MultipartFile file) throws IOException {        EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead();        return "success";    }

写Excel

通用数据生成 后面不会重复写

    private List<DemoData> data() {        List<DemoData> list = ListUtils.newArrayList();        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;    }

示例代码

DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java

最简单的写

excel示例

img

最简单的写的对象

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

代码

    /**     * 最简单的写     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>     * 2. 直接写即可     */    @Test    public void simpleWrite() {        // 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入        // 写法1 JDK8+        // since: 3.0.0-beta1        String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        // 如果这里想使用03 则 传入excelType参数即可        EasyExcel.write(fileName, DemoData.class)            .sheet("模板")            .doWrite(() -> {                // 分页查询数据                return data();            });        // 写法2        fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        // 如果这里想使用03 则 传入excelType参数即可        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());        // 写法3        fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();            excelWriter.write(data(), writeSheet);        }    }

根据参数只导出指定列

excel示例

img

对象

参照:最简单的写的对象

代码

    /**     * 根据参数只导出指定列     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>     * 2. 根据自己或者排除自己需要的列     * <p>     * 3. 直接写即可     *     * @since 2.1.1     */    @Test    public void excludeOrIncludeWrite() {        String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";        // 这里需要注意 在使用ExcelProperty注解的使用,如果想不空列则需要加入order字段,而不是index,order会忽略空列,然后继续往后,而index,不会忽略空列,在第几列就是第几列。        // 根据用户传入字段 假设我们要忽略 date        Set<String> excludeColumnFiledNames = new HashSet<String>();        excludeColumnFiledNames.add("date");        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板")            .doWrite(data());        fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";        // 根据用户传入字段 假设我们只要导出 date        Set<String> includeColumnFiledNames = new HashSet<String>();        includeColumnFiledNames.add("date");        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板")            .doWrite(data());    }

指定写入的列

excel示例

img

对象

@Getter@Setter@EqualsAndHashCodepublic class IndexData {    @ExcelProperty(value = "字符串标题", index = 0)    private String string;    @ExcelProperty(value = "日期标题", index = 1)    private Date date;    /**     * 这里设置3 会导致第二列空的     */    @ExcelProperty(value = "数字标题", index = 3)    private Double doubleData;}

代码

    /**     * 指定写入的列     * <p>1. 创建excel对应的实体对象 参照{@link IndexData}     * <p>2. 使用{@link ExcelProperty}注解指定写入的列     * <p>3. 直接写即可     */    @Test    public void indexWrite() {        String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data());    }

复杂头写入

excel示例

img

对象

@Getter@Setter@EqualsAndHashCodepublic class ComplexHeadData {    @ExcelProperty({"主标题", "字符串标题"})    private String string;    @ExcelProperty({"主标题", "日期标题"})    private Date date;    @ExcelProperty({"主标题", "数字标题"})    private Double doubleData;}

代码

    /**     * 复杂头写入     * <p>1. 创建excel对应的实体对象 参照{@link ComplexHeadData}     * <p>2. 使用{@link ExcelProperty}注解指定复杂的头     * <p>3. 直接写即可     */    @Test    public void complexHeadWrite() {        String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());    }

重复多次写入(写到单个或者多个Sheet)

excel示例

img

对象

参照:最简单的写的对象

代码

    /**     * 重复多次写入     * <p>     * 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}     * <p>     * 2. 使用{@link ExcelProperty}注解指定复杂的头     * <p>     * 3. 直接调用二次写入即可     */    @Test    public void repeatedWrite() {        // 方法1: 如果写到同一个sheet        String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {            // 这里注意 如果同一个sheet只要创建一次            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来            for (int i = 0; i < 5; i++) {                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据                List<DemoData> data = data();                excelWriter.write(data, writeSheet);            }        }        // 方法2: 如果写到不同的sheet 同一个对象        fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 指定文件        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面            for (int i = 0; i < 5; i++) {                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据                List<DemoData> data = data();                excelWriter.write(data, writeSheet);            }        }        // 方法3 如果写到不同的sheet 不同的对象        fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 指定文件        try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面            for (int i = 0; i < 5; i++) {                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class                // 实际上可以一直变                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(DemoData.class).build();                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据                List<DemoData> data = data();                excelWriter.write(data, writeSheet);            }        }    }

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

excel示例

img

对象

@Getter@Setter@EqualsAndHashCodepublic class ConverterData {    /**     * 我想所有的 字符串起前面加上"自定义:"三个字     */    @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)    private String string;    /**     * 我想写到excel 用年月日的格式     */    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")    @ExcelProperty("日期标题")    private Date date;    /**     * 我想写到excel 用百分比表示     */    @NumberFormat("#.##%")    @ExcelProperty(value = "数字标题")    private Double doubleData;}

代码

    /**     * 日期、数字或者自定义格式转换     * <p>1. 创建excel对应的实体对象 参照{@link ConverterData}     * <p>2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解     * <p>3. 直接写即可     */    @Test    public void converterWrite() {        String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(data());    }

图片导出

excel示例

img

对象

@Getter@Setter@EqualsAndHashCode@ContentRowHeight(100)@ColumnWidth(100 / 8)public class ImageDemoData {    private File file;    private InputStream inputStream;    /**     * 如果string类型 必须指定转换器,string默认转换成string     */    @ExcelProperty(converter = StringImageConverter.class)    private String string;    private byte[] byteArray;    /**     * 根据url导出     *     * @since 2.1.1     */    private URL url;    /**     * 根据文件导出 并设置导出的位置。     *     * @since 3.0.0-beta1     */    private WriteCellData<Void> writeCellDataFile;}

代码

   /**     * 图片导出     * <p>     * 1. 创建excel对应的实体对象 参照{@link ImageDemoData}     * <p>     * 2. 直接写即可     */    @Test    public void imageWrite() throws Exception {        String fileName = TestFileUtil.getPath() + "imageWrite" + System.currentTimeMillis() + ".xlsx";        // 这里注意下 所有的图片都会放到内存 暂时没有很好的解法,大量图片的情况下建议 2选1:        // 1. 将图片上传到oss 或者其他存储网站: https://www.aliyun.com/product/oss ,然后直接放链接        // 2. 使用: https://github.com/coobird/thumbnailator 或者其他工具压缩图片                String imagePath = TestFileUtil.getPath() + "converter" + File.separator + "img.jpg";        try (InputStream inputStream = FileUtils.openInputStream(new File(imagePath))) {            List<ImageDemoData> list =  ListUtils.newArrayList();            ImageDemoData imageDemoData = new ImageDemoData();            list.add(imageDemoData);            // 放入五种类型的图片 实际使用只要选一种即可            imageDemoData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath)));            imageDemoData.setFile(new File(imagePath));            imageDemoData.setString(imagePath);            imageDemoData.setInputStream(inputStream);            imageDemoData.setUrl(new URL(                "https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg"));            // 这里演示            // 需要额外放入文字            // 而且需要放入2个图片            // 第一个图片靠左            // 第二个靠右 而且要额外的占用他后面的单元格            WriteCellData<Void> writeCellData = new WriteCellData<>();            imageDemoData.setWriteCellDataFile(writeCellData);            // 这里可以设置为 EMPTY 则代表不需要其他数据了            writeCellData.setType(CellDataTypeEnum.STRING);            writeCellData.setStringValue("额外的放一些文字");            // 可以放入多个图片            List<ImageData> imageDataList = new ArrayList<>();            ImageData imageData = new ImageData();            imageDataList.add(imageData);            writeCellData.setImageDataList(imageDataList);            // 放入2进制图片            imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath)));            // 图片类型            imageData.setImageType(ImageType.PICTURE_TYPE_PNG);            // 上 右 下 左 需要留空            // 这个类似于 css 的 margin            // 这里实测 不能设置太大 超过单元格原始大小后 打开会提示修复。暂时未找到很好的解法。            imageData.setTop(5);            imageData.setRight(40);            imageData.setBottom(5);            imageData.setLeft(5);            // 放入第二个图片            imageData = new ImageData();            imageDataList.add(imageData);            writeCellData.setImageDataList(imageDataList);            imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath)));            imageData.setImageType(ImageType.PICTURE_TYPE_PNG);            imageData.setTop(5);            imageData.setRight(5);            imageData.setBottom(5);            imageData.setLeft(50);            // 设置图片的位置 假设 现在目标 是 覆盖 当前单元格 和当前单元格右边的单元格            // 起点相对于当前单元格为0 当然可以不写            imageData.setRelativeFirstRowIndex(0);            imageData.setRelativeFirstColumnIndex(0);            imageData.setRelativeLastRowIndex(0);            // 前面3个可以不写  下面这个需要写 也就是 结尾 需要相对当前单元格 往右移动一格            // 也就是说 这个图片会覆盖当前单元格和 后面的那一格            imageData.setRelativeLastColumnIndex(1);            // 写入数据            EasyExcel.write(fileName, ImageDemoData.class).sheet().doWrite(list);        }    }

超链接、备注、公式、指定单个单元格的样式、单个单元格多种样式

excel示例

img

对象

@Getter@Setter@EqualsAndHashCodepublic class WriteCellDemoData {    /**     * 超链接     *     * @since 3.0.0-beta1     */    private WriteCellData<String> hyperlink;    /**     * 备注     *     * @since 3.0.0-beta1     */    private WriteCellData<String> commentData;    /**     * 公式     *     * @since 3.0.0-beta1     */    private WriteCellData<String> formulaData;    /**     * 指定单元格的样式。当然样式 也可以用注解等方式。     *     * @since 3.0.0-beta1     */    private WriteCellData<String> writeCellStyle;    /**     * 指定一个单元格有多个样式     *     * @since 3.0.0-beta1     */    private WriteCellData<String> richText;}

代码

    /**     * 超链接、备注、公式、指定单个单元格的样式、单个单元格多种样式     * <p>     * 1. 创建excel对应的实体对象 参照{@link WriteCellDemoData}     * <p>     * 2. 直接写即可     *     * @since 3.0.0-beta1     */    @Test    public void writeCellDataWrite() {        String fileName = TestFileUtil.getPath() + "writeCellDataWrite" + System.currentTimeMillis() + ".xlsx";        WriteCellDemoData writeCellDemoData = new WriteCellDemoData();        // 设置超链接        WriteCellData<String> hyperlink = new WriteCellData<>("官方网站");        writeCellDemoData.setHyperlink(hyperlink);        HyperlinkData hyperlinkData = new HyperlinkData();        hyperlink.setHyperlinkData(hyperlinkData);        hyperlinkData.setAddress("https://github.com/alibaba/easyexcel");        hyperlinkData.setHyperlinkType(HyperlinkType.URL);        // 设置备注        WriteCellData<String> comment = new WriteCellData<>("备注的单元格信息");        writeCellDemoData.setCommentData(comment);        CommentData commentData = new CommentData();        comment.setCommentData(commentData);        commentData.setAuthor("Jiaju Zhuang");        commentData.setRichTextStringData(new RichTextStringData("这是一个备注"));        // 备注的默认大小是按照单元格的大小 这里想调整到4个单元格那么大 所以向后 向下 各额外占用了一个单元格        commentData.setRelativeLastColumnIndex(1);        commentData.setRelativeLastRowIndex(1);        // 设置公式        WriteCellData<String> formula = new WriteCellData<>();        writeCellDemoData.setFormulaData(formula);        FormulaData formulaData = new FormulaData();        formula.setFormulaData(formulaData);        // 将 123456789 中的第一个数字替换成 2        // 这里只是例子 如果真的涉及到公式 能内存算好尽量内存算好 公式能不用尽量不用        formulaData.setFormulaValue("REPLACE(123456789,1,1,2)");        // 设置单个单元格的样式 当然样式 很多的话 也可以用注解等方式。        WriteCellData<String> writeCellStyle = new WriteCellData<>("单元格样式");        writeCellStyle.setType(CellDataTypeEnum.STRING);        writeCellDemoData.setWriteCellStyle(writeCellStyle);        WriteCellStyle writeCellStyleData = new WriteCellStyle();        writeCellStyle.setWriteCellStyle(writeCellStyleData);        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.        writeCellStyleData.setFillPatternType(FillPatternType.SOLID_FOREGROUND);        // 背景绿色        writeCellStyleData.setFillForegroundColor(IndexedColors.GREEN.getIndex());        // 设置单个单元格多种样式        // 这里需要设置 inMomery=true 不然会导致无法展示单个单元格多种样式,所以慎用        WriteCellData<String> richTest = new WriteCellData<>();        richTest.setType(CellDataTypeEnum.RICH_TEXT_STRING);        writeCellDemoData.setRichText(richTest);        RichTextStringData richTextStringData = new RichTextStringData();        richTest.setRichTextStringDataValue(richTextStringData);        richTextStringData.setTextString("红色绿色默认");        // 前2个字红色        WriteFont writeFont = new WriteFont();        writeFont.setColor(IndexedColors.RED.getIndex());        richTextStringData.applyFont(0, 2, writeFont);        // 接下来2个字绿色        writeFont = new WriteFont();        writeFont.setColor(IndexedColors.GREEN.getIndex());        richTextStringData.applyFont(2, 4, writeFont);        List<WriteCellDemoData> data = new ArrayList<>();        data.add(writeCellDemoData);        EasyExcel.write(fileName, WriteCellDemoData.class).inMemory(true).sheet("模板").doWrite(data);    }

根据模板写入

模板excel示例

img

excel示例

img

对象

参照:最简单的写的对象

代码

    /**     * 根据模板写入     * <p>1. 创建excel对应的实体对象 参照{@link IndexData}     * <p>2. 使用{@link ExcelProperty}注解指定写入的列     * <p>3. 使用withTemplate 写取模板     * <p>4. 直接写即可     */    @Test    public void templateWrite() {        String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";        String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        // 这里要注意 withTemplate 的模板文件会全量存储在内存里面,所以尽量不要用于追加文件,如果文件模板文件过大会OOM        // 如果要再文件中追加(无法在一个线程里面处理,可以在一个线程的建议参照多次写入的demo) 建议临时存储到数据库 或者 磁盘缓存(ehcache) 然后再一次性写入        EasyExcel.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data());    }

列宽、行高

excel示例

img

对象

@Getter@Setter@EqualsAndHashCode@ContentRowHeight(10)@HeadRowHeight(20)@ColumnWidth(25)public class WidthAndHeightData {    @ExcelProperty("字符串标题")    private String string;    @ExcelProperty("日期标题")    private Date date;    /**     * 宽度为50     */    @ColumnWidth(50)    @ExcelProperty("数字标题")    private Double doubleData;}

代码

    /**     * 列宽、行高     * <p>1. 创建excel对应的实体对象 参照{@link WidthAndHeightData}     * <p>2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度     * <p>3. 直接写即可     */    @Test    public void widthAndHeightWrite() {        String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data());    }

注解形式自定义样式

since

2.2.0-beta1

excel示例

img

对象

@Getter@Setter@EqualsAndHashCode// 头背景设置成红色 IndexedColors.RED.getIndex()@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 10)// 头字体设置成20@HeadFontStyle(fontHeightInPoints = 20)// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex()@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 17)// 内容字体设置成20@ContentFontStyle(fontHeightInPoints = 20)public class DemoStyleData {    // 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex()    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 14)    // 字符串的头字体设置成20    @HeadFontStyle(fontHeightInPoints = 30)    // 字符串的内容的背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex()    @ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40)    // 字符串的内容字体设置成20    @ContentFontStyle(fontHeightInPoints = 30)    @ExcelProperty("字符串标题")    private String string;    @ExcelProperty("日期标题")    private Date date;    @ExcelProperty("数字标题")    private Double doubleData;}/** * 样式的数据类 * * @author Jiaju Zhuang **/@Data// 头背景设置成红色 IndexedColors.RED.getIndex()@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 10)// 头字体设置成20@HeadFontStyle(fontHeightInPoints = 20)// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex()@ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 17)// 内容字体设置成20@ContentFontStyle(fontHeightInPoints = 20)public class DemoStyleData {    // 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex()    @HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 14)    // 字符串的头字体设置成20    @HeadFontStyle(fontHeightInPoints = 30)    // 字符串的内容的背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex()    @ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 40)    // 字符串的内容字体设置成20    @ContentFontStyle(fontHeightInPoints = 30)    @ExcelProperty("字符串标题")    private String string;    @ExcelProperty("日期标题")    private Date date;    @ExcelProperty("数字标题")    private Double doubleData;}

代码

    /**     * 注解形式自定义样式     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoStyleData}     * <p>     * 3. 直接写即可     *     * @since 2.2.0-beta1     */    @Test    public void annotationStyleWrite() {        String fileName = TestFileUtil.getPath() + "annotationStyleWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, DemoStyleData.class).sheet("模板").doWrite(data());    }

自定义样式

excel示例

img

对象

参照:最简单的写的对象

代码

    /**     * 拦截器形式自定义样式     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>     * 2. 创建一个style策略 并注册     * <p>     * 3. 直接写即可     */    @Test    public void handlerStyleWrite() {        // 方法1 使用已有的策略 推荐        // HorizontalCellStyleStrategy 每一行的样式都一样 或者隔行一样        // AbstractVerticalCellStyleStrategy 每一列的样式都一样 需要自己回调每一页        String fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";        // 头的策略        WriteCellStyle headWriteCellStyle = new WriteCellStyle();        // 背景设置为红色        headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());        WriteFont headWriteFont = new WriteFont();        headWriteFont.setFontHeightInPoints((short)20);        headWriteCellStyle.setWriteFont(headWriteFont);        // 内容的策略        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);        // 背景绿色        contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());        WriteFont contentWriteFont = new WriteFont();        // 字体大小        contentWriteFont.setFontHeightInPoints((short)20);        contentWriteCellStyle.setWriteFont(contentWriteFont);        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现        HorizontalCellStyleStrategy horizontalCellStyleStrategy =            new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, DemoData.class)            .registerWriteHandler(horizontalCellStyleStrategy)            .sheet("模板")            .doWrite(data());        // 方法2: 使用easyexcel的方式完全自己写 不太推荐 尽量使用已有策略        // @since 3.0.0-beta2        fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";        EasyExcel.write(fileName, DemoData.class)            .registerWriteHandler(new CellWriteHandler() {                @Override                public void afterCellDispose(CellWriteHandlerContext context) {                    // 当前事件会在 数据设置到poi的cell里面才会回调                    // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true                    if (BooleanUtils.isNotTrue(context.getHead())) {                        // 第一个单元格                        // 只要不是头 一定会有数据 当然fill的情况 可能要context.getCellDataList() ,这个需要看模板,因为一个单元格会有多个 WriteCellData                        WriteCellData<?> cellData = context.getFirstCellData();                        // 这里需要去cellData 获取样式                        // 很重要的一个原因是 WriteCellStyle 和 dataFormatData绑定的 简单的说 比如你加了 DateTimeFormat                        // ,已经将writeCellStyle里面的dataFormatData 改了 如果你自己new了一个WriteCellStyle,可能注解的样式就失效了                        // 然后 getOrCreateStyle 用于返回一个样式,如果为空,则创建一个后返回                        WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();                        writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());                        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND                        writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);                        // 这样样式就设置好了 后面有个FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 cell里面去 所以可以不用管了                    }                }            }).sheet("模板")            .doWrite(data());        // 方法3: 使用poi的样式完全自己写 不推荐        // @since 3.0.0-beta2        // 坑1:style里面有dataformat 用来格式化数据的 所以自己设置可能导致格式化注解不生效        // 坑2:不要一直去创建style 记得缓存起来 最多创建6W个就挂了        fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";        EasyExcel.write(fileName, DemoData.class)            .registerWriteHandler(new CellWriteHandler() {                @Override                public void afterCellDispose(CellWriteHandlerContext context) {                    // 当前事件会在 数据设置到poi的cell里面才会回调                    // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true                    if (BooleanUtils.isNotTrue(context.getHead())) {                        Cell cell = context.getCell();                        // 拿到poi的workbook                        Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();                        // 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式                        // 不同单元格尽量传同一个 cellStyle                        CellStyle cellStyle = workbook.createCellStyle();                        cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());                        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND                        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);                        cell.setCellStyle(cellStyle);                        // 由于这里没有指定dataformat 最后展示的数据 格式可能会不太正确                        // 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到                        // cell里面去 会导致自己设置的不一样                        context.getFirstCellData().setWriteCellStyle(null);                    }                }            }).sheet("模板")            .doWrite(data());    }

合并单元格

since

2.2.0-beta1

excel示例

img

对象

方法1

@Getter@Setter@EqualsAndHashCode// 将第6-7行的2-3列合并成一个单元格// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)public class DemoMergeData {    // 这一列 每隔2行 合并单元格    @ContentLoopMerge(eachRow = 2)    @ExcelProperty("字符串标题")    private String string;    @ExcelProperty("日期标题")    private Date date;    @ExcelProperty("数字标题")    private Double doubleData;}

方法2参照:最简单的写的对象

代码

   /**     * 合并单元格     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData} {@link DemoMergeData}     * <p>     * 2. 创建一个merge策略 并注册     * <p>     * 3. 直接写即可     *     * @since 2.2.0-beta1     */    @Test    public void mergeWrite() {        // 方法1 注解        String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";        // 在DemoStyleData里面加上ContentLoopMerge注解        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, DemoMergeData.class).sheet("模板").doWrite(data());        // 方法2 自定义合并单元格策略        fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";        // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写        LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data());    }

使用table去写入

excel示例

img

对象

参照:最简单的写的对象

代码

    /**     * 使用table去写入     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>     * 2. 然后写入table即可     */    @Test    public void tableWrite() {        String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx";        // 方法1 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案        // 这里 需要指定写用哪个class去写        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {            // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了            WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();            // 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要            WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();            WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();            // 第一次写入会创建头            excelWriter.write(data(), writeSheet, writeTable0);            // 第二次写如也会创建头,然后在第一次的后面写入数据            excelWriter.write(data(), writeSheet, writeTable1);        }    }

动态头,实时生成头写入

excel示例

img

对象

参照:最简单的写的对象

代码

    /**     * 动态头,实时生成头写入     * <p>     * 思路是这样子的,先创建List<String>头格式的sheet仅仅写入头,然后通过table 不写入头的方式 去写入数据     *     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>     * 2. 然后写入table即可     */    @Test    public void dynamicHeadWrite() {        String fileName = TestFileUtil.getPath() + "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";        EasyExcel.write(fileName)            // 这里放入动态头            .head(head()).sheet("模板")            // 当然这里数据也可以用 List<List<String>> 去传入            .doWrite(data());    }    private List<List<String>> head() {        List<List<String>> list = new ArrayList<List<String>>();        List<String> head0 = new ArrayList<String>();        head0.add("字符串" + System.currentTimeMillis());        List<String> head1 = new ArrayList<String>();        head1.add("数字" + System.currentTimeMillis());        List<String> head2 = new ArrayList<String>();        head2.add("日期" + System.currentTimeMillis());        list.add(head0);        list.add(head1);        list.add(head2);        return list;    }

自动列宽(不太精确)

excel示例

img

对象

@Getter@Setter@EqualsAndHashCodepublic class LongestMatchColumnWidthData {    @ExcelProperty("字符串标题")    private String string;    @ExcelProperty("日期标题很长日期标题很长日期标题很长很长")    private Date date;    @ExcelProperty("数字")    private Double doubleData;}

代码

   /**     * 自动列宽(不太精确)     * <p>     * 这个目前不是很好用,比如有数字就会导致换行。而且长度也不是刚好和实际长度一致。 所以需要精确到刚好列宽的慎用。 当然也可以自己参照     * {@link LongestMatchColumnWidthStyleStrategy}重新实现.     * <p>     * poi 自带{@link SXSSFSheet#autoSizeColumn(int)} 对中文支持也不太好。目前没找到很好的算法。 有的话可以推荐下。     *     * <p>     * 1. 创建excel对应的实体对象 参照{@link LongestMatchColumnWidthData}     * <p>     * 2. 注册策略{@link LongestMatchColumnWidthStyleStrategy}     * <p>     * 3. 直接写即可     */    @Test    public void longestMatchColumnWidthWrite() {        String fileName =            TestFileUtil.getPath() + "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, LongestMatchColumnWidthData.class)            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong());    }    private List<LongestMatchColumnWidthData> dataLong() {        List<LongestMatchColumnWidthData> list = new ArrayList<LongestMatchColumnWidthData>();        for (int i = 0; i < 10; i++) {            LongestMatchColumnWidthData data = new LongestMatchColumnWidthData();            data.setString("测试很长的字符串测试很长的字符串测试很长的字符串" + i);            data.setDate(new Date());            data.setDoubleData(1000000000000.0);            list.add(data);        }        return list;    }

自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)

excel示例

img

对象

参照:最简单的写的对象

定义拦截器

/** * 自定义拦截器。对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel * * @author Jiaju Zhuang */@Slf4jpublic class CustomCellWriteHandler implements CellWriteHandler {    @Override    public void afterCellDispose(CellWriteHandlerContext context) {        Cell cell = context.getCell();        // 这里可以对cell进行任何操作        log.info("第{}行,第{}列写入完成。", cell.getRowIndex(), cell.getColumnIndex());        if (BooleanUtils.isTrue(context.getHead()) && cell.getColumnIndex() == 0) {            CreationHelper createHelper = context.getWriteSheetHolder().getSheet().getWorkbook().getCreationHelper();            Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL);            hyperlink.setAddress("https://github.com/alibaba/easyexcel");            cell.setHyperlink(hyperlink);        }    }}
/** * 自定义拦截器.对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2 * * @author Jiaju Zhuang */@Slf4jpublic class CustomSheetWriteHandler implements SheetWriteHandler {    @Override    public void afterSheetCreate(SheetWriteHandlerContext context) {        log.info("第{}个Sheet写入成功。", context.getWriteSheetHolder().getSheetNo());        // 区间设置 第一列第一行和第二行的数据。由于第一行是头,所以第一、二行的数据实际上是第二三行        CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 2, 0, 0);        DataValidationHelper helper = context.getWriteSheetHolder().getSheet().getDataValidationHelper();        DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"测试1", "测试2"});        DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);        context.getWriteSheetHolder().getSheet().addValidationData(dataValidation);    }}

代码

    /**     * 下拉,超链接等自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)     * <p>     * demo这里实现2点。1. 对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel 2. 对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>     * 2. 注册拦截器 {@link CustomCellWriteHandler} {@link CustomSheetWriteHandler}     * <p>     * 2. 直接写即可     */    @Test    public void customHandlerWrite() {        String fileName = TestFileUtil.getPath() + "customHandlerWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, DemoData.class).registerWriteHandler(new CustomSheetWriteHandler())            .registerWriteHandler(new CustomCellWriteHandler()).sheet("模板").doWrite(data());    }

插入批注

excel示例

img

对象

参照:最简单的写的对象

定义拦截器

/** * 自定义拦截器.新增注释,第一行头加批注 * * @author Jiaju Zhuang */@Slf4jpublic class CommentWriteHandler implements RowWriteHandler {    @Override    public void afterRowDispose(RowWriteHandlerContext context) {        if (BooleanUtils.isTrue(context.getHead())) {            Sheet sheet = context.getWriteSheetHolder().getSheet();            Drawing<?> drawingPatriarch = sheet.createDrawingPatriarch();            // 在第一行 第二列创建一个批注            Comment comment =                drawingPatriarch.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short)1, 0, (short)2, 1));            // 输入批注信息            comment.setString(new XSSFRichTextString("创建批注!"));            // 将批注添加到单元格对象中            sheet.getRow(0).getCell(1).setCellComment(comment);        }    }}

代码

    /**     * 插入批注     * <p>     * 1. 创建excel对应的实体对象 参照{@link DemoData}     * <p>     * 2. 注册拦截器 {@link CommentWriteHandler}     * <p>     * 2. 直接写即可     */    @Test    public void commentWrite() {        String fileName = TestFileUtil.getPath() + "commentWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        // 这里要注意inMemory 要设置为true,才能支持批注。目前没有好的办法解决 不在内存处理批注。这个需要自己选择。        EasyExcel.write(fileName, DemoData.class).inMemory(Boolean.TRUE).registerWriteHandler(new CommentWriteHandler())            .sheet("模板").doWrite(data());    }

可变标题处理(包括标题国际化等)

excel示例

img

对象

@Getter@Setter@EqualsAndHashCodepublic class ConverterData {    /**     * 我想所有的 字符串起前面加上"自定义:"三个字     */    @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)    private String string;    /**     * 我想写到excel 用年月日的格式     */    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")    @ExcelProperty("日期标题")    private Date date;    /**     * 我想写到excel 用百分比表示     */    @NumberFormat("#.##%")    @ExcelProperty(value = "数字标题")    private Double doubleData;}

代码

    /**     * 可变标题处理(包括标题国际化等)     * <p>     * 简单的说用List<List<String>>的标题 但是还支持注解     * <p>     * 1. 创建excel对应的实体对象 参照{@link ConverterData}     * <p>     * 2. 直接写即可     */    @Test    public void variableTitleWrite() {        // 写法1        String fileName = TestFileUtil.getPath() + "variableTitleWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName, ConverterData.class).head(variableTitleHead()).sheet("模板").doWrite(data());    }    private List<List<String>> variableTitleHead() {        List<List<String>> list = ListUtils.newArrayList();        List<String> head0 = ListUtils.newArrayList();        head0.add("string" + System.currentTimeMillis());        List<String> head1 = ListUtils.newArrayList();        head1.add("number" + System.currentTimeMillis());        List<String> head2 = ListUtils.newArrayList();        head2.add("date" + System.currentTimeMillis());        list.add(head0);        list.add(head1);        list.add(head2);        return list;    }    private List<List<String>> variableTitleHead() {        List<List<String>> list = new ArrayList<>();        List<String> head0 = new ArrayList<>();        head0.add("string" + System.currentTimeMillis());        List<String> head1 = new ArrayList<>();        head1.add("number" + System.currentTimeMillis());        List<String> head2 = new ArrayList<>();        head2.add("date" + System.currentTimeMillis());        list.add(head0);        list.add(head1);        list.add(head2);        return list;    }

不创建对象的写

excel示例

img

代码

    /**     * 不创建对象的写     */    @Test    public void noModelWrite() {        // 写法1        String fileName = TestFileUtil.getPath() + "noModelWrite" + System.currentTimeMillis() + ".xlsx";        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭        EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList());    }    private List<List<String>> head() {        List<List<String>> list = ListUtils.newArrayList();        List<String> head0 = ListUtils.newArrayList();        head0.add("字符串" + System.currentTimeMillis());        List<String> head1 = ListUtils.newArrayList();        head1.add("数字" + System.currentTimeMillis());        List<String> head2 = ListUtils.newArrayList();        head2.add("日期" + System.currentTimeMillis());        list.add(head0);        list.add(head1);        list.add(head2);        return list;    }    private List<List<Object>> dataList() {        List<List<Object>> list = ListUtils.newArrayList();        for (int i = 0; i < 10; i++) {            List<Object> data = ListUtils.newArrayList();            data.add("字符串" + i);            data.add(0.56);            data.add(new Date());            list.add(data);        }        return list;    }

web中的写

示例代码

DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java

对象

参照:最简单的写的对象 就是名称变了下

代码

    /**     * 文件下载(失败了会返回一个有部分数据的Excel)     * <p>     * 1. 创建excel对应的实体对象 参照{@link DownloadData}     * <p>     * 2. 设置返回的 参数     * <p>     * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大     */    @GetMapping("download")    public void download(HttpServletResponse response) throws IOException {        // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");        response.setCharacterEncoding("utf-8");        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系        String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");        EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());    }

web中的写并且失败的时候返回json

since

2.1.1

对象

参照:最简单的写的对象 就是名称变了下

代码

    /**     * 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)     *     * @since 2.1.1     */    @GetMapping("downloadFailedUsingJson")    public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {        // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman        try {            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");            response.setCharacterEncoding("utf-8");            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系            String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");            // 这里需要设置不关闭流            EasyExcel.write(response.getOutputStream(), DownloadData.class).autoCloseStream(Boolean.FALSE).sheet("模板")                .doWrite(data());        } catch (Exception e) {            // 重置response            response.reset();            response.setContentType("application/json");            response.setCharacterEncoding("utf-8");            Map<String, String> map = MapUtils.newHashMap();            map.put("status", "failure");            map.put("message", "下载文件失败" + e.getMessage());            response.getWriter().println(JSON.toJSONString(map));        }    }

填充Excel

示例代码

DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java

最简单的填充

since

2.1.1

最简单的填充的模板

img

最终效果

img

最简单的填充的对象

@Getter@Setter@EqualsAndHashCodepublic class FillData {    private String name;    private double number;    private Date date;}

代码

    /**     * 最简单的填充     *     * @since 2.1.1     */    @Test    public void simpleFill() {        // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替        String templateFileName =            TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "simple.xlsx";        // 方案1 根据对象填充        String fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx";        // 这里 会填充到第一个sheet, 然后文件流会自动关闭        FillData fillData = new FillData();        fillData.setName("张三");        fillData.setNumber(5.2);        EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);        // 方案2 根据Map填充        fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx";        // 这里 会填充到第一个sheet, 然后文件流会自动关闭        Map<String, Object> map = MapUtils.newHashMap();        map.put("name", "张三");        map.put("number", 5.2);        EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);    }

填充列表

since

2.1.1

模板

img

最终效果

img

对象

参照:最简单的填充的对象

代码

    /**     * 填充列表     *     * @since 2.1.1     */    @Test    public void listFill() {        // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替        // 填充list 的时候还要注意 模板中{.} 多了个点 表示list        // 如果填充list的对象是map,必须包涵所有list的key,哪怕数据为null,必须使用map.put(key,null)        String templateFileName =            TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "list.xlsx";        // 方案1 一下子全部放到内存里面 并填充        String fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";        // 这里 会填充到第一个sheet, 然后文件流会自动关闭        EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(data());        // 方案2 分多次 填充 会使用文件缓存(省内存)        fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";        try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {            WriteSheet writeSheet = EasyExcel.writerSheet().build();            excelWriter.fill(data(), writeSheet);            excelWriter.fill(data(), writeSheet);        }    }

复杂的填充

since

2.1.1

模板

img

最终效果

img

对象

参照:最简单的填充的对象

代码

    /**     * 复杂的填充     *     * @since 2.1.1     */    @Test    public void complexFill() {        // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替        // {} 代表普通变量 {.} 代表是list的变量        String templateFileName =            TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complex.xlsx";        String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx";        // 方案1        try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {            WriteSheet writeSheet = EasyExcel.writerSheet().build();            // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。            // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用            // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存            // 如果数据量大 list不是最后一行 参照下一个            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();            excelWriter.fill(data(), fillConfig, writeSheet);            excelWriter.fill(data(), fillConfig, writeSheet);            Map<String, Object> map = MapUtils.newHashMap();            map.put("date", "2019年10月9日13:28:28");            map.put("total", 1000);            excelWriter.fill(map, writeSheet);        }    }

数据量大的复杂填充

since

2.1.1

模板

img

最终效果

img

对象

参照:最简单的填充的对象

代码

    /**     * 数据量大的复杂填充     * <p>     * 这里的解决方案是 确保模板list为最后一行,然后再拼接table.还有03版没救,只能刚正面加内存。     *     * @since 2.1.1     */    @Test    public void complexFillWithTable() {        // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替        // {} 代表普通变量 {.} 代表是list的变量        // 这里模板 删除了list以后的数据,也就是统计的这一行        String templateFileName =            TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complexFillWithTable.xlsx";        String fileName = TestFileUtil.getPath() + "complexFillWithTable" + System.currentTimeMillis() + ".xlsx";        // 方案1        try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {            WriteSheet writeSheet = EasyExcel.writerSheet().build();            // 直接写入数据            excelWriter.fill(data(), writeSheet);            excelWriter.fill(data(), writeSheet);            // 写入list之前的数据            Map<String, Object> map = new HashMap<String, Object>();            map.put("date", "2019年10月9日13:28:28");            excelWriter.fill(map, writeSheet);            // list 后面还有个统计 想办法手动写入            // 这里偷懒直接用list 也可以用对象            List<List<String>> totalListList = ListUtils.newArrayList();            List<String> totalList = ListUtils.newArrayList();            totalListList.add(totalList);            totalList.add(null);            totalList.add(null);            totalList.add(null);            // 第四列            totalList.add("统计:1000");            // 这里是write 别和fill 搞错了            excelWriter.write(totalListList, writeSheet);            // 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以            // 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案        }    }

横向的填充

since

2.1.1

模板

img

最终效果

img

对象

参照:最简单的填充的对象

代码

    /**     * 横向的填充     *     * @since 2.1.1     */    @Test    public void horizontalFill() {        // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替        // {} 代表普通变量 {.} 代表是list的变量        String templateFileName =            TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "horizontal.xlsx";        String fileName = TestFileUtil.getPath() + "horizontalFill" + System.currentTimeMillis() + ".xlsx";        // 方案1        try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {            WriteSheet writeSheet = EasyExcel.writerSheet().build();            FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();            excelWriter.fill(data(), fillConfig, writeSheet);            excelWriter.fill(data(), fillConfig, writeSheet);            Map<String, Object> map = new HashMap<>();            map.put("date", "2019年10月9日13:28:28");            excelWriter.fill(map, writeSheet);        }    }

多列表组合填充填充

since

2.2.0-beta1

模板

img

最终效果

img

对象

参照:最简单的填充的对象

代码

    /**     * 多列表组合填充填充     *     * @since 2.2.0-beta1     */    @Test    public void compositeFill() {        // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替        // {} 代表普通变量 {.} 代表是list的变量 {前缀.} 前缀可以区分不同的list        String templateFileName =            TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "composite.xlsx";        String fileName = TestFileUtil.getPath() + "compositeFill" + System.currentTimeMillis() + ".xlsx";        // 方案1        try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {            WriteSheet writeSheet = EasyExcel.writerSheet().build();            FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();            // 如果有多个list 模板上必须有{前缀.} 这里的前缀就是 data1,然后多个list必须用 FillWrapper包裹            excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet);            excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet);            excelWriter.fill(new FillWrapper("data2", data()), writeSheet);            excelWriter.fill(new FillWrapper("data2", data()), writeSheet);            excelWriter.fill(new FillWrapper("data3", data()), writeSheet);            excelWriter.fill(new FillWrapper("data3", data()), writeSheet);            Map<String, Object> map = new HashMap<String, Object>();            //map.put("date", "2019年10月9日13:28:28");            map.put("date", new Date());            excelWriter.fill(map, writeSheet);        }    }
posted @ 2024-08-23 00:14  起跑线小言  阅读(20)  评论(0编辑  收藏  举报