EasyExcel工作表操作
这一部分,我们结合具体的案例介绍 EasyExcel 是如何将数据写入到一个或多个工作表中。
一、一个实体类重复多次写入到一个工作表中
首先我们来看一个简单的案例:利用循环将数据写到一个工作表中。
案例:将员工信息重复 5 次写入到一个工作表中,并将下面的工作表名称设置成“测试工作表”。
实体类 Employee、工具类 EmployeeUtils 中一次性获取十条 Employee 数据的
getEmployeeList
方法也已经准备好:点我查看导出的 Excel 文件显示效果如下图所示:
以下是我的实现思路:
- 创建一个可以执行 5 次的循环:案例中要求我们重复 5 次写入到一个工作表中,这里我们使用 for 循环、while 循环或者 do-while 循环都可以。
- 在这个循环中,获取到数据列表,调用
EmployeeUtils.getEmployeeList
方法。 - 使用前面学过的链式调用,将获取到的数据列表作为参数传入。
以下是实现代码:
1 |
|
运行这段代码,我们发现它在 resources 目录下生成了 Excel 文件,但是 Excel 文件中只有 10 条数据,显然并不符合案例中的要求:
1.1 为什么链式调用没有达到效果
为什么我们的链式调用没有达到效果呢?
要想解答上述疑问,我们需要观察一下链式调用写法:
1 |
|
只凭英文语义我们也可以知道,真正将数据写入的是doWrite
方法,查看doWrite
方法源码我们可以看到如下逻辑:
1 |
|
结合我们的链式调用内容,我们传入的参数data
对应的是EmployeeUtils.getDataList
方法的列表数据。
源码中 2~4 行是判断excelWriter
是否为空,只要我们在使用链式调用方式,这个excelWriter
就不为空,即excelWriter != null
。接下来我们就通过源码来验证上述内容是否正确。
我们来查看一下链式调用的sheet
方法源码:
1 |
|
在调用sheet
方法时,源码中首先调用了build
方法,而build
方法中正好使用了new
的方式创建了 ExcelWriter 对象,因此上述的excelWriter != null
成立。
执行了上述判断以后,在doWrite
方法中使用excelWriter
依次调用了write
和finish
方法。其中write
方法的作用是将数据写入到 Excel 文件中,finish
方法是关闭文件资源流(毕竟写入到 Excel 操作属于文件 IO 操作),这一轮循环执行结束。
进行下一轮循环的时候,仍然执行上述的代码逻辑,数据再次写入 Excel 文件时,会覆盖掉上一次的 Excel 数据,以此类推,当循环结束时,Excel 文件中的内容是最后一次循环写入的数据。
1.2 解决方案
在循环中直接使用链式调用并不能达到效果,那么还有其他解决方案吗?
当然有!我们在前面学过 try-with-resources 方式实现将数据写入到 Excel 文件中,这种方式的特点是在保证整个 try 块代码没有出现异常时,在整个 try 块代码执行完成后,自动关闭文件资源流,在循环过程中不涉及到数据覆盖的操作。
以下是使用 try-with-resources 方式的实现思路:
- 在 try 小括号中创建 ExcelWriter 类型对象
excelWriter
:因为 ExcelWriter 类实现了 Closeable 接口,在 try 块执行完毕时,会自动调用close
方法关闭文件资源流。 - 在 try 块中编写可以执行 5 次的循环,在循环中设置工作表相关信息(例如:工作表的名称)。
- excelWriter 对象调用 write 方法,配置要写入的数据列表和工作表对象作为参数,执行数据写入操作。
- 如果整个 try 块执行过程中未出现异常,此时会自动关闭文件资源流。
以下是使用 try-with-resources 方式的代码实现:
1 |
|
这段代码运行后,在 resources 目录下生成了一个 Excel 文件,此时 Excel 文件中除去表头,一共有 50 行数据(共循环了 5 次,每次有 10 条数据写入到 Excel 文件):
新思路和原思路相比,新思路在执行循环的过程中文件资源流一致保持开启状态,直到整个 try 块执行完毕自动关闭文件资源流;而原思路是每次执行循环时开启文件资源流,循环结束后关闭本次文件资源流。
二、一个实体类重复多次写入到多个工作表中
案例:将上述的 Employee 列表数据重复写入到 5 张工作表中,效果如下图所示
已知 Employee 实体类和 EmployeeUtils 实体类已经准备好:点我查看
前面我们提到过,一个 Excel 文件称作一个工作簿(Workbook),在一个工作簿中可以包含多张工作表(Worksheet)。我们在前面向 Excel 中写数据时,一般是一个对象写入到一个工作表中。但有的时候,我们需要向多个工作表中插入数据。
EasyExcel 在处理多个工作表时,类似数组的索引,会将第一张要操作的工作表标记为 0,第二张要操作的工作表标记为 1,以此类推(如下图):
结合上述分析,我们在调用 EasyExcel 类的静态方法writerSheet
时,可以指定索引(sheetNo)和工作表名称(sheetName)。那么我们可以编写一个$[0,5)$的普通 for 循环,索引就是用 for 循环每一次迭代的数字,并为每一个工作表指定名称。示例代码如下:
1 |
|
运行这一段代码,发现生成的 Excel 文件中存在 5 个工作表,并且每一个工作表中存在表头和 10 行数据。
三、多个实体类写入到多个工作表中
案例:使用 EasyExcel 将学生数据和教师数据分别写入到一个 Excel 文件的两个工作表中,效果如下图所示:
已知学生实体类 Student、教师实体类 Teacher 代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 学生实体类
*
* @author iCode504
* @date 2024-05-10
*/
@Data
@NoArgsConstructor
@ToString
public class Student {
@ExcelProperty("学生id")
private String studentId;
@ExcelProperty("学生姓名")
private String studentName;
@ExcelProperty("年龄")
private Integer age;
@ExcelProperty("性别")
private String gender;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 教师实体类
*
* @author iCode504
* @date 2024-05-10
*/
@Data
@NoArgsConstructor
@ToString
public class Teacher {
@ExcelProperty("教师id")
private String teacherId;
@ExcelProperty("教师姓名")
private String teacherName;
@ExcelProperty("学科")
private String subject;
@ExcelProperty("年龄")
private Integer age;
}数据处理类 PersonUtils:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 人员工具类
*
* @author iCode504
* @date 2024-05-10
*/
public class PersonUtils {
public static final String[] SUBJECTS = {"语文", "数学", "英语", "计算机", "物理", "化学", "生物"};
/**
* 获取学生信息列表
*
* @return 学生信息列表:学生id从1开始,姓名默认以student开头,性别随机生成
* 年龄在12-18岁之间随机生成
*/
public static List<Student> getStudentInfoList() {
List<Student> list = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 100; i++) {
Student student = new Student();
student.setStudentId(String.valueOf(i + 1));
student.setStudentName("student" + (i + 1));
student.setGender(random.nextBoolean() ? "男" : "女");
student.setAge(random.nextInt(7) + 12);
list.add(student);
}
return list;
}
/**
* 获取教师信息列表
*
* @return 教师信息列表:教师id默认从1生成,姓名默认以teacher开头,年龄在30-59岁之间随机生成
* 学科随机生成
*/
public static List<Teacher> getTeacherInfoList() {
List<Teacher> list = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 10; i++) {
Teacher teacher = new Teacher();
teacher.setTeacherId(String.valueOf(i + 1));
teacher.setTeacherName("teacher" + (i + 1));
teacher.setAge(random.nextInt(30) + 30);
teacher.setSubject(SUBJECTS[random.nextInt(SUBJECTS.length)]);
list.add(teacher);
}
return list;
}
/**
* 生成Excel文件名称及所在路径,这里为了方便,我将最终生成的Excel放到了当前项目的resources目录下,以当前时间戳命名。方便后续使用
* 如有需要,请将第66行代码替换成你自己的路径
*/
public static String getFilePath() {
return "E:\\Code\\icode504-codespace\\easyexcel-study\\easyexcel-demo1\\src\\main\\resources\\"
+ System.currentTimeMillis() + ".xlsx";
}
}
由于我们需要将两个不相同的实体列表(学生信息列表PersonUtils.getStudentInfoList
和教师信息列表PersonUtils.getTeacherInfoList
)写入到两张数据表中,无法使用循环实现上述过程。以下是解决上述案例的思路:
- 使用
try-with-resources
方式创建。由于 ExcelWriter 类实现了 Closeable 接口,代码执行完成后会自动调用close
方法自动关闭资源; - 在
try
小括号中创建 ExcelWriter 类型对象excelWriter
:调用EasyExcel.write
方法时,只需要添加文件路径参数,不需要添加类的字节码参数(因为要写入的数据是两个实体类,write
方法默认只能接收一个类字节码参数)。参数配置完成后,调用build
方法创建excelWriter
对象; - 调用两次
EasyExcel.writeSheet
方法,第一个参数指定要写入哪一个数据表中(EasyExcel 处理数据表默认从 0 开始),第二个参数可以指定工作表的名称(后续更容易分辨)。参数配置完成后,调用build
方法创建writeSheet
对象; excelWriter
对象调用write
方法分别将学生和教师的数据列表分别写入 Excel 文件的两个数据表中。
以下是代码是上述思路的实现:
1 |
|
运行这段代码,生成的 Excel 文件确实有两张表:学生信息表、教师信息表,两张表里面确实有数据。但是有一点点小瑕疵:没有表头。
解决这个问题也很简单,只需要在writeSheet
方法后面再调用head
方法,并在这个方法中指定类字节码参数,代码如下:
1 |
|
此时两张工作表中就都存在表头信息了:
四、知识点总结
Easyexcel 工作表操作知识点总结如下图所示: