使用easypoi模板导出遇见的bug

发布时间 2023-08-01 10:08:16作者: 卧龙戏公瑾

一、前言

easypoi是为java提供的一款excel导入导出的工具包。使用easypoi,能极大的简化我们excel导入导出的操作;但是在使用过程中,也发现了一些bug,在这里做一些相关记录。

二、问题

我这里发现的问题主要是easypoi提供的模板导出功能。

1.前期准备

为了模拟问题的出现,我们需要新建一个Springboot项目,然后引入相关依赖,如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-base</artifactId>
            <version>4.1.3</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

我是用的easypoi版本为4.1.3。
接着,我们再创建两个实体类,如下:

package com.example.demo.entity;
import lombok.Data;

@Data
public class Student {
    private Integer id;
    private String stuName;
}
package com.example.demo.entity;
import lombok.Data;
import java.util.List;
@Data
public class Teacher {
    private Integer id;
    private String name;
    List<Student> stuList;
}

两个实体类,一个Teacher类一个Student类,Teacher与Student的关系是一对多的关系。

最后,我们在resources目录下的templates文件夹下,新增一个空白的名字为导出模板.xlsx的文件作为我们的导出模板,如下:
新建的excel导出模板

1.循环嵌套某些单元格值为空的情况

首先,修改我们的导出模板.xlsx如下:
导出模板

导出模板语法在这里我就不多说了,详情请参考easypoi模板语法

接着,编写我们的测试方法。

    @Test
    void contextLoads() throws Exception{
        /**
         * 生成需要导出的数据
         */
        List<Teacher> teacherList =  new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Teacher t = new Teacher();
            t.setId(i);
            t.setName("教师_" + i);
            List<Student> studentList = new ArrayList<>();
            for (int j = 0; j < 3; j++) {
                Student student = new Student();
                student.setId(j);
                student.setStuName("学生_" + i + "_" + j);
                studentList.add(student);
            }
            t.setStuList(studentList);
            teacherList.add(t);
        }

        /**
         * 加载excel模板
         */
        TemplateExportParams params = new TemplateExportParams(
                "templates/导出模板.xlsx", true);
        Map<String, Object> map = new HashMap<String, Object>();
        /**
         * 这个map的key需要和模板里便利的值保持一直
         */
        map.put("list", teacherList);
        
        Workbook book = ExcelExportUtil.exportExcel(params, map);
        File savefile = new File("D:/home/excel/");
        if (!savefile.exists()) {
            savefile.mkdirs();
        }
        FileOutputStream fos = new FileOutputStream("D:/home/excel/exportTemp.xlsx");
        book.write(fos);
        fos.close();
    }

注意:模板里遍历的列表名及对应属性需要和代码中的属性保持一致!

注意

执行我们的测试方法,最终生成的excel如下:

问题

从上图中不难发现,教师0应该也有三个学生,但是最终只显示出一个,其他的两个直接为空白!

2.问题解决

数据丢了两行,我们怎么解决呢?解决方法很简单,只要将模板里面的$fe替换为fe就可以了,修改之后的模板如下:
修改之后的模板

这里补充说明一下,$fefe的区别。
fe:在处理数据时,会在插入数据的下方生成一个空行
$fe:会在插入数据的下方保留原有数据

这里其实是easypoi的一个bug,在处理一对多数据的时候,使用$fe会有空行的问题。

再次运行我们的测试程序,得到的结果如下:
修改之后的结果

我们不难发现,原先为空的数据现在全部都展示了。

3.新的问题

解决了上述问题,又有新的问题产生了,如果我们的要求就是需要在列表下方保留一列,比如加一个合计列,这时我们该怎么处理呢?
在这里我说一下我的解决方案。
通过Workbook book = ExcelExportUtil.exportExcel(params, map);方法返回的Workbook对象,获取到sheet对象,在通过sheet对象获取excel中数据的行数,在获取到的行数中插入我们的代码。修改测试类中代码如下:

    @Test
    void contextLoads() throws Exception{
        /**
         * 生成需要导出的数据
         */
        List<Teacher> teacherList =  new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Teacher t = new Teacher();
            t.setId(i);
            t.setName("教师_" + i);
            List<Student> studentList = new ArrayList<>();
            for (int j = 0; j < 3; j++) {
                Student student = new Student();
                student.setId(j);
                student.setStuName("学生_" + i + "_" + j);
                studentList.add(student);
            }
            t.setStuList(studentList);
            teacherList.add(t);
        }

        /**
         * 加载excel模板
         */
        TemplateExportParams params = new TemplateExportParams(
                "templates/导出模板.xlsx", true);
        Map<String, Object> map = new HashMap<String, Object>();
        /**
         * 这个map的key需要和模板里便利的值保持一直
         */
        map.put("list", teacherList);

        Workbook book = ExcelExportUtil.exportExcel(params, map);

        /**
         * 1.首先获取sheet
         * 2.通过获取到的sheet获取excel的函数
         * 3.在指定行创建单元格并设置值
         */
        Sheet sheetAt = book.getSheetAt(0);

        /**********************添加合计行的代码开始************************************/
        // 设置单元格样式
        CellStyle cellStyle = book.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.CENTER);//设置水平居中
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//设置垂直居中

        int physicalNumberOfRows = sheetAt.getPhysicalNumberOfRows();
        Row row = sheetAt.createRow(physicalNumberOfRows);
        Cell cell01 = row.createCell(0);
        cell01.setCellStyle(cellStyle);
        cell01.setCellValue("合计");
        /***********************添加合计行的代码结束*******************************/


        File savefile = new File("D:/home/excel/");
        if (!savefile.exists()) {
            savefile.mkdirs();
        }
        FileOutputStream fos = new FileOutputStream("D:/home/excel/exportTemp.xlsx");
        book.write(fos);
        fos.close();

    }

运行我们的测试程序,结果如下:
添加合计之后的结果

可以发现我们的合计列加上了。
这是我自己想到的解决方案,如果大家有更好的解决方案,欢迎大家在评论区评论。

三、总结

在使用easypoi的模板导出功能时,如果导出的数据包含一对多的情形,此时使用$fe是由bug的,可能会产生空行的问题,此时建议使用fe替换$fe。如果列表下面还需要保留数据,需要我们通过Workbook对象,手动插入相关数据。