(五)产品给的报表导出需求太花哨?快来EasyExcel模板填充技术的魅力!

4,907 阅读18分钟

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

引言

关于EasyExcel这项技术,迄今为止已经使用了四章的篇幅来讲述:

其实一开始,只是想着分享”百万级大报表处理“这个话题,可转念一想,或许在其中使用EasyExcel时,有些小伙伴还未曾接触过。也正因如此,才使用了前面两章篇幅,先去讲述它常用的核心API,以及封装通用的工具类、监听器与多个案例实操。

掌握EasyExcel框架的核心API与常见用法后,然后才开始着手撰写起初规划的百万级大报表读写,这类场景的特点是 接口响应时间慢、服务资源占用高,这也是许多并发场景下的通病。面对此类问题时,如何去改善性能、怎么去控制资源,这两点经验尤为重要。

所以,在百万级读写的两篇内容中,我们结合了异步回调、流式处理、多线程并发、Reactor线程模型等多种技术来兼顾性能、资源这两个问题,最终完美驾驭了性能、资源问题,报表的处理性能也好,资源的占用情况也罢,都可以通过调整参数来灵活控制,以此满足不同业务场景下的动态需求。

可是面对复杂报表时,比如系统需支持自动导出花里胡哨的合并表格,前面的知识显然无法满足。因此,为了有始有终,本文则会来聊聊EasyExcel如何应对复杂报表导出的场景。

PS:个人编写的《技术人求职指南》小册已完结,其中从技术总结开始,到制定期望、技术突击、简历优化、面试准备、面试技巧、谈薪技巧、面试复盘、选Offer方法、新人入职、进阶提升、职业规划、技术管理、涨薪跳槽、仲裁赔偿、副业兼职……,为大家打造了一套“从求职到跳槽”的一条龙服务,同时也为诸位准备了七折优惠码:3DoleNaE,近期需要找工作的小伙伴可以点击:s.juejin.cn/ds/USoa2R3/了解详情!

一、EasyExcel模板填充技术

回想之前的内容,不管是导入还是导出,我们都是基于数据模型类,来映射Java对象与excel数据的关系。成也萧何败也萧何,这种模式虽然用起来很方便,但天然就无法支持复杂报表的场景,毕竟我们没有办法通过数据模型类,来描述一个毫无规则可言的复杂报表

那么,面对复杂报表需求EasyExcel会如何应对呢?答案是基于已有的Excel模板去填充数据,这就有点像编写前端页面,为了将美观的界面和动态的数据相结合,前端页面代码通常会使用各种占位符,在真正拿到数据时再渲染上去,EasyExcel里的模板填充技术也很类似。

当然,EasyExcel的模板填充,实际上还是对POI的封装,只是简化了填充的使用成本罢了,下面一起来看看模板填充的基础用法,比如我想导出一个这样的报表:

简单填充

如果想要导出这样一个熊猫基本信息的报表,那么首先咱们得定义对应的模板,如下:

简单填充模板

在这个模板里,需要动态填充的数据,会通过{}占位符来表示,中间则是对应的变量名字,下面来看具体用法:

public static void main(String[] args) {
    // 创建要填充的数据对象
    Panda panda = new Panda();
    panda.setName("竹子");
    panda.setNickname("小竹");
    panda.setUniqueCode("P888888");
    panda.setAddress("地球村888号");
    panda.setHeight(new BigDecimal("188.88"));
    panda.setMotto("今天的事能拖就拖,明天的事明天再说!");
    
    // 声明模板的位置、生成的excel文件名,开始正式填充数据
    String templatePath = "excel/simple_template.xlsx";
    String fileName1 = "panda_entity_info.xlsx";
    EasyExcelFactory.write(fileName1).withTemplate(templatePath).sheet().doFill(panda);
}

其实十分简单,首先创建一个Panda对象,然后给对应的字段赋值,接着通过EasyExcelFactory来生成excel文件,这里通过withTemplate()来声明是基于模板生成,最后调用doFill()方法开始填充即可。

PS:withTemplate()支持多种方式指定excel模板,可以通过输入流、文件路径、File对象三种方式指定模板。

将上述这段代码一执行,就能得到前面给出的示例效果。当然,除开可以基于实体类去填充数据外,还可以直接通过Map来传递要填充的目标数据,如:

public static void main(String[] args){
    Map<String, Object> dataMap=new HashMap<>();
    dataMap.put("name","竹子");
    dataMap.put("nickname","小竹");
    dataMap.put("uniqueCode","P888888");
    dataMap.put("address","地球村888号");
    dataMap.put("height",new BigDecimal("188.88"));
    dataMap.put("motto","今天的事能拖就拖,明天的事明天再说!");

    String templatePath = "excel/simple_template.xlsx";
    String fileName2="panda_map_info.xlsx";
    EasyExcelFactory.write(fileName2).withTemplate(templatePath).sheet().doFill(dataMap);
}

这段代码的效果和上面的没有区别,只不过当传递的数据是Map时,填充数据会根据占位符名称去Map里面get数据;而传递是实体类时,则会通过实体类的字段名称,与占位符匹配获取数据。

二、模板填充功能详解

经过前面这个案例,我们快速熟悉了一下EasyExcel框架的模板填充机制,相较于POI原生的模板填充机制简单了许多,不过使用时有两点要注意:

①填充数据时,会以提前声明的占位符进行匹配,所以实体类的字段名、或Map的键名一定要对应,否则无法填充会导致空数据出现;
②模板中声明占位符的语法是{xxx},如果内容正文中就包含{、}这两个特殊字符,那么一定要手动将其转义,即\{、\}这么去写,否则会导致对应的正文为空。

搞清楚这两个注意点后,下面我们再来详细看看EasyExcel提供的模板填充功能。

2.1、填充列表数据

前面提到,要填充的数据通过{xxx}占位符的形式来指定,那假设现在我有多条数据需要填充呢?难道定义模板的时候就声明多条吗?可是这也不合适啊,因为可以从数据库里查询,每次查询得到的行数并不确定。

EasyExcel专门针对列表类型的数据,又推出了一种新的占位符,{.xxx},比普通占位符前面多了个.。在填充数据时,如果读取到这类占位符,则会自动将其识别为列表类型的数据进行填充,下面来看例子:

列表模板

那如何将列表数据填充到这个模板呢?其实很简单:

private static void listFillV1() {
    List<Panda> pandas = new ArrayList<>();
    for (int i = 1; i <= 5; i++) {
        Panda panda = new Panda();
        panda.setName("竹子" + i + "号");
        panda.setUniqueCode("P" + i);
        panda.setBirthday(new Date());
        panda.setAddress("地球村" + i + "号");
        pandas.add(panda);
    }

    String fileName = "pandas_v1.xlsx";
    String templatePath = "excel/list_template.xlsx";
    EasyExcelFactory.write(fileName).withTemplate(templatePath).sheet().doFill(pandas);
}

和简单填充的代码完全一样,只不过调用doFill()方法时,传入的数据从一个对象变为了一个List集合罢了,来看效果:

列表填充效果

效果十分明显,的确是按列表形式将数据填充到了模板里,当然,这种方式是一次性将所有数据都填充到模板,如果你数据量较大,想要分批填充是否可以呢?可以,来看实现:

private static void listFillV2() {
    String fileName = "pandas_v2.xlsx";
    String templatePath = "excel/list_template.xlsx";

    // 先创建一个Excel工作簿写对象、Sheet写对象
    ExcelWriter excelWriter = EasyExcelFactory.write(fileName).withTemplate(templatePath).build();
    WriteSheet writeSheet = EasyExcelFactory.writerSheet().build();
    // 模拟多次填充(填充五次,每次填充一条数据)
    for (int i = 1; i <= 5; i++) {
        List<Panda> pandas = new ArrayList<>();
        Panda panda = new Panda();
        panda.setName("竹子" + i + "号");
        panda.setUniqueCode("P" + i);
        panda.setBirthday(new Date());
        panda.setAddress("地球村" + i + "号");
        pandas.add(panda);
        // 开始触发数据填充
        excelWriter.fill(pandas, writeSheet);
    }
    // 手动创建的写对象记得手动关
    excelWriter.finish();
}

注意看代码,这里是手动先创建了写入对象,然后再在循环内部去多次触发填充动作,这种适用于数据量较大的填充场景(效果和前面的一样,就不贴图了)。

2.2、混合式填充场景

前面出现了两种占位符,即普通占位符和列表占位符,那么如果一个表格里,既需要填充普通数据,也需要填充列表数据时咋整呢?来看例子,先定义模板:

混合填充模板

正如这个模板所示,目前需要导出一个熊猫信息统计表,整个表格由三部分组成:

  • ①第二行有两个合并单元格,左侧为统计人姓名,右侧为统计时间;
  • ②第四行开始的熊猫列表数据,包含名称、编码、身高、地址四个列;
  • ③熊猫列表数据后面跟着的统计行,D列包含的平均身高和总和。

这里注意,①合并了多个单元格,③则是在一个单元格内需要填充多个数据,这能正常填充吗?拭目以待,上代码:

private static void mixedFill() {
    // 导出的位置、模板的位置
    String fileName = "mixed_data.xlsx";
    String templatePath = "excel/mixed_template.xlsx";

    // 提前创建excel写对象
    ExcelWriter excelWriter = EasyExcelFactory.write(fileName).withTemplate(templatePath).build();
    WriteSheet writeSheet = EasyExcelFactory.writerSheet().build();
    FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

    // 初始化熊猫列表数据
    List<Panda> pandas = new ArrayList<>();
    BigDecimal total = BigDecimal.ZERO;
    for (int i = 1; i <= 5; i++) {
        Panda panda = new Panda();
        panda.setName("竹子" + i + "号");
        panda.setUniqueCode("P" + i);
        // 对熊猫身高求和
        BigDecimal height = new BigDecimal(i * 15.5);
        total = total.add(height);
        panda.setHeight(height);
        panda.setAddress("地球村" + i + "号");
        pandas.add(panda);
    }

    // 组装普通的数据(这里用了Map,不嫌麻烦也可以定义单独的类)
    BigDecimal avg = total.divide(new BigDecimal(pandas.size()), RoundingMode.HALF_UP);
    Map<String, Object> dataMap = new HashMap<>();
    dataMap.put("username", "竹子爱熊猫");
    dataMap.put("date", new Date());
    dataMap.put("totalHeight", total);
    dataMap.put("avgHeight", avg);
    
    // 写入熊猫集合数据、普通数据
    excelWriter.fill(pandas, fillConfig, writeSheet);
    excelWriter.fill(dataMap, writeSheet);
    excelWriter.finish();
}

代码相较于之前,看起来长了不少,但其实特别简单,创建对象、初始化数据这些一眼能看明白的就不啰嗦了,重点看这里出现了一个FillConfig类。

这个类从名字上就能看出作用,主要就是用于开启一些填充数据时的配置项,比如上述案例中的forceNewRow(Boolean.TRUE)代表开启自动创建新行,为啥要这么设置呢?来看官方的说法:

forceNewRow代表在填充列表数据时,不管列表下面有没有空行,都会创建一行,然后下面的数据往后移动。默认是false,会直接使用下一行,如果没有则创建。

啥意思呢?有点迷糊是不?其实简单的说,就是当模板需要填充列表数据,并且list占位符不在最后一行,就必须将forceNewRow设置为true

结合前面定义的模板来看,列表占位符定义在第四行,可是在第五行还有身高统计的占位符,这说明在列表填充完成后,下面还有数据需要填充,如果不开启自动创建新行,就会导致“身高统计行”被覆盖掉。

好了,搞明白这个配置项的作用后,剩余的代码没啥含金量,来看填充效果:

混合填充效果

显然,尽管这个报表里列表占位符、普通占位符都有,并且还有单元格合并的情况,以及一个单元格需要换行填充多个数据的情况,EasyExcel照样能够正常填充数据~

2.3、列表横向填充

前面的列表填充,是以纵向形式、从上至下填充数据,那如果想横着填充呢?先来定义一个模板:

横向列表模板

这是一个经典的值班表,通常值班信息会以横向展示,咋实现?先来定义一个对应的实体类:

@Data
public class DutyPanda {
    private String week;
    private String name;
}

这个类就两个字段,如果嫌定义一个类麻烦,完全可以直接用List<Map>类型来代替,下面来看代码实现:

private static void transverseListFill() {
    String fileName = "transverse_list_data.xlsx";
    String templatePath = "excel/transverse_list_template.xlsx";

    // 构建需要填充的数据
    List<DutyPanda> dutyPandas = new ArrayList<>();
    for (int i = 1; i <= 7; i++) {
        DutyPanda dutyPanda = new DutyPanda();
        dutyPanda.setWeek("周" + i);
        dutyPanda.setName("竹子" + i + "号");
        dutyPandas.add(dutyPanda);
    }
    Map<String, Object> dataMap = new HashMap<>();
    dataMap.put("date", new Date());

    // 提前创建excel写对象
    ExcelWriter excelWriter = EasyExcelFactory.write(fileName).withTemplate(templatePath).build();
    WriteSheet writeSheet = EasyExcelFactory.writerSheet().build();
    // 设置填充模式为水平填充
    FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
    excelWriter.fill(dutyPandas, fillConfig, writeSheet);
    excelWriter.fill(dataMap, writeSheet);
    excelWriter.finish();
}

这里横向填充列表数据,又用到了FillConfig这个类,这个类提供的可选项总共只有四个:

  • direction:填充的方向,默认为VERTICAL垂直方向,HORIZONTAL水平方向;
  • forceNewRow:十分自动创建新行填充数据,默认关闭,某些场景下会造成数据覆盖;
  • autoStyle:是否开启样式自动适配,开启后会根据模板里的样式填充数据,默认开启;
  • hasInit:是否允许默认初始化,关闭后每个选项没有默认值,必须手动指定才生效。

因为咱们需要横向填充数据,所以得将direction选项设置为HORIZONTAL水平方向,其余的代码则与之前没有太大差异,来看最终效果:

横向列表填充效果

从结果图来看,这显然满足了咱们的预期效果,所以当你需要面对这种横向填充的需求时,就可以使用这种方式来实现。

2.4、多列表数据填充

掌握列表数据怎么横向填充后,现在来想一个问题,列表占位符是通过{.xxx}这种形式声明,那如果一个报表中,存在多个列表需要填充,这该怎么办?

如果两个集合都有name字段,这时该填充到哪儿去?对于这种场景,如果再依靠前面那种占位符,显然会导致数据乱套。EasyExcel为了解决这类填充场景,推出了新的占位符写法:{xx.xxx},也就是前面再新增一个变量名,来看模板定义:

多列表填充模板

这个模板中,需要填充两个列表数据,所以定义占位符时用list1、list2两个变量名来区分,那填充数据时,如何指定Java集合数据与占位符的对应关系呢?来看代码:

private static void manyListFill() {
    String fileName = "many_list_data.xlsx";
    String templatePath = "excel/many_list_template.xlsx";

    // 构建需要填充的数据
    List<DutyPanda> dutyPandas = new ArrayList<>();
    List<Panda> pandas = new ArrayList<>();
    for (int i = 1; i <= 10; i++) {
        String name = "竹子" + i + "号";
        if (i <= 5) {
            DutyPanda dutyPanda = new DutyPanda();
            dutyPanda.setWeek("周" + i);
            dutyPanda.setName(name);
            dutyPandas.add(dutyPanda);
        } else {
            Panda panda = new Panda();
            panda.setName(name);
            panda.setUniqueCode("P" + i);
            panda.setAddress("地球村" + i + "号");
            pandas.add(panda);
        }
    }
    Map<String, Object> dataMap = new HashMap<>();
    dataMap.put("date", new Date());
    dataMap.put("username", "竹子爱熊猫");
    dataMap.put("remark", "最终解释权归分配人所有!");

    // 提前创建excel写对象
    ExcelWriter excelWriter = EasyExcelFactory.write(fileName).withTemplate(templatePath).build();
    WriteSheet writeSheet = EasyExcelFactory.writerSheet().build();
    // 设置填充模式为水平填充
    FillConfig config1 = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
    // 设置自动创建新行填充
    FillConfig config2 = FillConfig.builder().forceNewRow(true).build();
    
    excelWriter.fill(new FillWrapper("list1", dutyPandas), config1, writeSheet);
    excelWriter.fill(new FillWrapper("list2", pandas), config2, writeSheet);
    excelWriter.fill(dataMap, writeSheet);
    excelWriter.finish();
}

代码中大多数都是熟悉的类,只不过出现了一个FillWrapper类,这个类就是用来指定Java集合,和模板中列表占位符的映射关系。上述代码中,dutyPandas对应模板里的list1pandas对应list2,来看最终效果:

多列表填充效果

很明显,最终填充的数据并未出现错乱,这种方式就能很好的满足多列表填充场景。

三、复杂报表导出实战

OK,前面已经将EasyExcel提供的填充功能做了全面讲述,下面来结合真正的业务场景,来看下复杂报表的导出实战案例。

首先,对于提前定义的Excel模板,最好统一去做管理,而不是以硬编码的形式分散在各个业务代码处。可以选择枚举类的形式来管理,但更好的方式是设计一张excel模板表,用于动态维护模板的信息

这里我就使用枚举类形式了,对应的枚举类如下:

@Getter
@AllArgsConstructor
public enum ExcelTemplate {
    Panda_Statistics(1, "excel/panda_statistics_template.xlsx", "熊猫统计模板");

    private final Integer code;
    private final String path;
    private final String desc;
}

如果你要将模板信息放在数据库维护,就是将这个枚举类的字段抽象成表结构即可,还可以增加一个excel_type模板类型字段,从而使得表结构更加灵活~

好了,有了这个枚举类后,下面来看看本次的复杂报表导出需求,对应的模板如下:

熊猫统计模板

这个报表也是多数场景下的需求,即系统自动统计并导出各项数据,如何实现呢?关于这点先不着急,咱们先来封装下模板填充导出的公用方法:

/*
 * 初始化模板填充导出所需的写对象
 * */
public static ExcelWriter initExportFillExcel(String fileName, ExcelTypeEnum excelType, 
        ExcelTemplate template, HttpServletResponse response) throws IOException {
    setResponse(fileName, excelType, response);
    return EasyExcelFactory.write(response.getOutputStream())
            .excelType(excelType)
            .withTemplate(template.getPath()).build();
}

/*
 * 设置通用的响应头信息
 * */
public static void setResponse(String fileName, ExcelTypeEnum excelType, HttpServletResponse response) {
    // 对文件名进行UTF-8编码、拼接文件后缀名
    try {
        fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20") + excelType.getValue();
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    }
    switch (excelType) {
        case XLS:
            response.setContentType(XLS_CONTENT_TYPE);
            break;
        case XLSX:
            response.setContentType(XLSX_CONTENT_TYPE);
            break;
        case CSV:
            response.setContentType(CSV_CONTENT_TYPE);
            break;
    }
    response.setCharacterEncoding("utf-8");
    response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + fileName);
}

这里的两个方法一眼就能看明白,setResponse()方法的作用是设置响应头信息,如文件名、响应数据格式、编码格式等。而initExportFillWriter()方法则是创建一个ExcelWriter对象,只不过是一个已经初始化Excel类型、模板、输出流等属性的对象罢了。

下面正式来看如何实现前面的导出场景,接口如下:

@PostMapping("/export/v7")
public void exportExcelV7(HttpServletResponse response) {
    pandaService.exportPandaStatisticsData(response);
}

这个接口定义非常简单,重点来看看service层的导出逻辑:

@Override
public void exportPandaStatisticsData(HttpServletResponse response) {
    // 查询熊猫集合数据
    List<Panda> pandas = baseMapper.selectAll();
    

    // 初始化各统计项变量
    String statisticsUser = "竹子爱熊猫";
    Date statisticsDate = new Date();
    int maleNumber=0, femaleNumber=0, unknownNumber=0, totalNumber=0,
            weekIncreaseNumber=0, monthIncreaseNumber=0, yearIncreaseNumber=0;
    BigDecimal avgHeight, maxHeight=null, minHeight=null, sumHeight=BigDecimal.ZERO;

    Calendar today = Calendar.getInstance();
    int currentYear = today.get(Calendar.YEAR);
    int currentMonth = today.get(Calendar.MONTH) + 1;
    int currentWeek = today.get(Calendar.WEEK_OF_YEAR);

    for (Panda panda : pandas) {
        // 统计各项性别数据
        Sex sex = Sex.ofCode(panda.getSex());
        switch (sex) {
            case MALE:
                maleNumber++;
                break;
            case FEMALE:
                femaleNumber++;
                break;
            default:
                unknownNumber++;
                break;
        }

        // 统计各项身高数据
        BigDecimal height = panda.getHeight();
        sumHeight = sumHeight.add(height);
        if (null == maxHeight) {
            maxHeight = height;
        }
        if (null == minHeight) {
            minHeight = height;
        }

        if (minHeight.compareTo(height) < 0) {
            minHeight = height;
        }
        if (maxHeight.compareTo(height) > 0) {
            maxHeight = height;
        }

        // 统计本年、本月、本周新增人数
        Calendar createCal = Calendar.getInstance();
        createCal.setTime(panda.getCreateTime());
        int year = createCal.get(Calendar.YEAR);
        int month = createCal.get(Calendar.MONTH) + 1;
        int week = createCal.get(Calendar.WEEK_OF_YEAR);
        if (year == currentYear) {
            yearIncreaseNumber++;
            if (month == currentMonth) {
                monthIncreaseNumber++;
                if (week == currentWeek) {
                    weekIncreaseNumber++;
                }
            }
        }
    }
    totalNumber = pandas.size();
    avgHeight = sumHeight.divide(new BigDecimal(totalNumber), RoundingMode.HALF_UP);
    
    Map<String, Object> dataMap = new HashMap<>(13);
    dataMap.put("statisticsUser", statisticsUser);
    dataMap.put("statisticsDate", statisticsDate);
    dataMap.put("maleNumber", maleNumber);
    dataMap.put("femaleNumber", femaleNumber);
    dataMap.put("unknownNumber", unknownNumber);
    dataMap.put("weekIncreaseNumber", weekIncreaseNumber);
    dataMap.put("monthIncreaseNumber", monthIncreaseNumber);
    dataMap.put("yearIncreaseNumber", yearIncreaseNumber);
    dataMap.put("avgHeight", avgHeight);
    dataMap.put("maxHeight", maxHeight);
    dataMap.put("minHeight", minHeight);
    dataMap.put("totalNumber", totalNumber);
    dataMap.put("illustrate", "以上数据来自熊猫表");

    String fileName = "熊猫数据统计报表";
    try {
        // 获取一个已设置基础参数的ExcelWriter对象
        ExcelWriter excelWriter = ExcelUtil.initExportFillWriter(fileName,
                ExcelTypeEnum.XLSX, ExcelTemplate.PANDA_STATISTICS, response);
        WriteSheet writeSheet = EasyExcelFactory.writerSheet().build();
        // 基于模板填充数据
        FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
        excelWriter.fill(pandas, fillConfig, writeSheet);
        excelWriter.fill(dataMap, writeSheet);
        excelWriter.finish();
    }
    catch (IOException e) {
        throw new BusinessException("熊猫统计报表导出失败!");
    }
}

上面这段代码看着不算短,但其实很简单,整体就做了几件事:

  • ①从数据库里将整张熊猫表的数据查询出来;
  • ②提前初始化各统计项的变量,并循环熊猫集合统计数据;
  • ③在循环内部分别统计模板所需的性别、身高、新增等熊数;
  • ④调用封装的通用方法,并将列表数据、统计数据填充并返回。

这就是整段代码逻辑的流程,最后调用下接口试试看:

熊猫统计导出效果

来看最终的结果图,不管是上面的各个统计项,还是中间的熊猫列表数据,又或者最下面的备注项,数据都正常填充了进去,达到了咱们所希望的效果。

四、总结

作为EasyExcel系列的封篇之作,看到这里也就走进了尾声,其实本文的内容并不难理解,这篇的目的也仅是为了知识全面性,确保大家在面对复杂报表导出需求时,也仍然能够游刃有余。

相较于EasyExcel的普通写入模式,填充模式能更好的满足各种复杂报表需求,该模式基于提前设计好的Excel模板来导出文件,对应的模板中可以包含复杂的格式、公式、图表,以及花哨的Excel样式和布局,而我们只需关注数据的填充,就能导出各种花哨的报表了。

填充模式尽管能满足复杂的报表场景,但对于有些特殊的情况它仍然无法处理,这时还得上原生的POI,因为原生POI的操作性更强。除了无法适用于某些特殊场景外,填充模式也并不适用于大数据场景!

普通写入模式可以以流式去写入excel数据,尽管面对的数据量级大的场景,只要优化妥当,处理起来可以说是毫不费力。反观填充模式却不太行,尤其是填充列表数据时,手动开启了forceNewRow=true选项,这会导致所有数据全放在内存处理,从而导致大量资源的消耗。

综上所述,什么时候选择普通写入模式,什么时候选择模板填充模式,又或者什么时候使用原生的POI,这要视场景而定,每种方式都有各自的优劣势,充分了解各自的特性后,结合具体的业务场景才能选出最合适的方案。

所有文章已开始陆续同步至微信公众号:竹子爱熊猫,想在手机上便捷阅读的小伙伴可搜索关注~