EasyExcel使用与详细说明
EasyExcel工具类(一)
简介: EasyExcel使用与详细说明,EasyExcel工具类
1.Apache POI
Apache POI是Apache软件基金会的开源函式库,提供跨平台的Java API实现Microsoft Office格式档案读写。但是存在如下一些问题:
1.1 学习使用成本较高
对POI有过深入了解的才知道原来POI还有SAX模式(Dom解析模式)。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。
想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。
POI的SAX模式的API可以一定程度的解决一些内存溢出的问题,但是POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大,一个3M的Excel用POI的SAX解析,依然需要100M左右内存。
1.2 POI的内存消耗较大
大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。
总体上来说,简单写法重度依赖内存,复杂写法学习成本高。
1.3 特点
- 功能强大
- 代码书写冗余繁杂
- 读写大文件耗费内存较大,容易OOM
2. 初识EasyExcel
2.1 重写了POI对07版Excel的解析
- EasyExcel重写了POI对07版Excel的解析,可以把内存消耗从100M左右降低到10M以内,并且再大的Excel不会出现内存溢出,03版仍依赖POI的SAX模式。
- 下图为64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点)

- 在上层做了模型转换的封装,让使用者更加简单方便
2.2 特点
- 在数据模型层面进行了封装,使用简单
- 重写了07版本的Excel的解析代码,降低内存消耗,能有效避免OOM
- 只能操作Excel
- 不能读取图片
3.快速入门
3.1 导入依赖坐标
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
<!-- lombok 优雅编程 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
3.2 最简单的读
3.2.1 需求、准备工作
/**
* 需求:单实体导入
* 导入Excel学员信息到系统。
* 包含如下列:姓名、出生日期、性别
* 模板:逐浪教育学员信息表.xls文件
*/
3.2.2 编写导出数据的实体
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 学生实体类
* lombok:通过一个插件 + 一个依赖 ,就可以在编译的时候自动帮助生成实体类常用方法
*
* @author 角刀牛
* @create 2023-02-26 14:56
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
/**
* 学生姓名
*/
private String name;
/**
* 学生出生日期
*/
private Date birthday;
/**
* 学生性别
*/
private String gender;
/**
* id
*/
private String id;
}
3.2.3 读取Excel的监听器,用于处理读取产生的数据
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.fox.easyexcel.domain.Student;
/**
* 读取文档的监听器类
*
* @author 角刀牛
* @create 2023-02-26 15:10
*/
public class StudentListener extends AnalysisEventListener<Student> {
/**
* 读监听器,每读一行内容,都会调用一次invoke,在invoke可以操作使用读取到的数据
*
* @param student 每次读取到的数据封装的对象
* @param analysisContext
*/
public void invoke(Student student, AnalysisContext analysisContext) {
System.out.println(student);
}
/**
* 读取完整个文档之后,调用的方法
*
* @param analysisContext
*/
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// todo
}
}
3.2.4 读取Excel文件
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import com.fox.easyexcel.domain.Student;
import com.fox.easyexcel.listener.StudentListener;
import org.junit.Test;
/**
* @author 角刀牛
* @create 2023-02-26 15:03
*/
public class ExcelTest {
/**
* 工作簿:一个excel文件就是一个工作簿
* 工作表:一个工作簿可以有多个工作表(sheet)
*/
@Test
public void test01() {
/*
1.获得一个工作簿对象
构建一个读的工作簿对象,参数说明:
- pathName:要读的文件的路径
- head:文件中每一行数据要存储到的实体的类型的class
- readListener:读监听器,每读一行内容,都会调用一次该对象的invoke,在invoke可以操作使用读取到的数据
*/
ExcelReaderBuilder readWorkBook = EasyExcel.read("逐浪教育学员信息表.xlsx", Student.class, new StudentListener());
// 2.获得一个工作表对象,默认读取第一个工作表
ExcelReaderSheetBuilder sheet = readWorkBook.sheet();
// 3.读取工作表中的内容
sheet.doRead();
}
}
3.3 最简单的写
3.3.1 需求、准备工作
/**
* 需求:单实体导出
* 导出多个学生对象到Excel表格
* 包含如下列:姓名、出生日期、性别
* 模板详见:逐浪教育学员信息表.xlsx
*/
3.3.2 编写导出数据的实体
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 学生实体类
* lombok:通过一个插件 + 一个依赖 ,就可以在编译的时候自动帮助生成实体类常用方法
* 注解 @ContentRowHeight():内容的行高
* 注解 @HeadRowHeight:表头的行高
*
* @author 角刀牛
* @create 2023-02-26 14:56
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
/**
* 学生姓名
*/
@ExcelProperty("学生姓名")
@ColumnWidth(20)
private String name;
/**
* 学生出生日期
*/
@ExcelProperty("出生日期")
@ColumnWidth(20)
private Date birthday;
/**
* index 从0开始
* 学生性别
*/
@ExcelProperty(value = "学生性别", index = 1)
private String gender;
/**
* id
*/
@ExcelIgnore
private String id;
}
3.3.3 准备数据并写入到文件
@Test
public void test02() {
/*
1.构建一个写的工作簿对象
- pathName:要写入的文件路径
- head:封装写入的数据的实体类型
*/
ExcelWriterBuilder writeWorkBook = EasyExcel.write("逐浪教育学员信息表.xlsx", Student.class);
// 2.获取工作表对象,默认是第一个工作表
ExcelWriterSheetBuilder sheet = writeWorkBook.sheet();
// 3.生成十个测试对象
ArrayList<Student> students = new ArrayList<Student>();
for (int i = 1; i <= 10; i++) {
students.add(new Student("逐浪者-" + i, new Date(), "男", null));
}
// 4.将数据写入工作表
sheet.doWrite(students);
}
3.4 文件上传和下载
基于SpringMVC的文件上传和下载
3.4.1 导入依赖
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
<!-- lombok 优雅编程 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.3</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
3.4.2 文件上传
🍀 编写excel中每一行对应的实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
/**
* 学生姓名
*/
private String name;
/**
* 学生性别
*/
private String gender;
/**
* 学生出生日期
*/
private Date birthday;
/**
* id
*/
private String id;
}
🍀 回调监听器StudentReadListener
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.fox.easyexcel.domain.Student;
import com.fox.easyexcel.service.StudentService;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
/**
* 读取文档的监听器类
*
* @author 角刀牛
* @create 2023-02-26 15:10
*/
@Component
@Scope("prototype") //作者要求每次读取都要使用新的Listener
public class StudentListener extends AnalysisEventListener<Student> {
@Resource
private StudentService studentService;
public static final ThreadLocal<ArrayList<Student>> threadLocal = new ThreadLocal<ArrayList<Student>>();
/**
* 读监听器,每读一行内容,都会调用一次invoke,在invoke可以操作使用读取到的数据
*
* @param student 每次读取到的数据封装的对象
* @param analysisContext
*/
public void invoke(Student student, AnalysisContext analysisContext) {
ArrayList<Student> students = threadLocal.get();
if (students == null) {
threadLocal.set(new ArrayList<Student>());
students = threadLocal.get();
}
students.add(student);
if (students.size() == 5) {
studentService.save(students);
students.clear();
}
}
/**
* 读取完整个文档之后,调用的方法
*
* @param analysisContext
*/
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// todo
}
}
🍀 业务代码接口StudentService和实现类StudentServiceImpl
import com.fox.easyexcel.domain.Student;
import java.util.ArrayList;
/**
* @author 角刀牛
* @create 2023-02-26 16:27
*/
public interface StudentService {
/**
* 保存学生信息
*
* @param students 信息列表
*/
void save(ArrayList<Student> students);
}
import com.fox.easyexcel.domain.Student;
import com.fox.easyexcel.service.StudentService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
/**
* @author 角刀牛
* @create 2023-02-26 16:27
*/
@Service
public class StudentServiceImpl implements StudentService {
@Override
public void save(ArrayList<Student> students) {
System.out.println("save to database = " + students);
}
}
🍀 读取上传的Excel文件
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import com.fox.easyexcel.domain.Student;
import com.fox.easyexcel.listener.StudentListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
/**
* @author 角刀牛
* @create 2023-02-26 16:29
*/
@RestController
@RequestMapping("/student")
@Slf4j
public class StudentController {
@Resource
private StudentListener studentListener;
@PostMapping("/read")
public String readExcel(MultipartFile uploadExcel) {
try {
// 1.获取工作簿
ExcelReaderBuilder readWorkBook = EasyExcel.read(uploadExcel.getInputStream(), Student.class, studentListener);
// 2.获取工作表
ExcelReaderSheetBuilder sheet = readWorkBook.sheet();
// 3.读取数据
sheet.doRead();
// 4.释放线程
StudentListener.threadLocal.remove();
return "success";
} catch (Exception e) {
log.error("读取文件失败:{}", e.getMessage());
return "fail";
}
}
}
🍀 ApiFox测试

EasyExcel工具类(二)
3.4.3 文件下载
🍀 编写实体类并创建对象以便写入表格
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 学生实体类
* lombok:通过一个插件 + 一个依赖 ,就可以在编译的时候自动帮助生成实体类常用方法
* 注解 @ContentRowHeight():内容的行高
* 注解 @HeadRowHeight:表头的行高
*
* @author 角刀牛
* @create 2023-02-26 14:56
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
/**
* id
*/
@ExcelIgnore
private String id;
/**
* 学生姓名
*/
@ExcelProperty("学生姓名")
private String name;
/**
* 学生性别
*/
@ExcelProperty("学生性别")
private String gender;
/**
* 学生出生日期
*/
@ExcelProperty("学生出生日期")
private Date birthday;
}
🍀 将数据写入到响应体实现下载
/**
* 文件下载
* 1. 编写实体类并创建对象以便写入表格
* 2. 设置响应参数:文件的ContentType和文件名,同时设置编码避免乱码
* 3. 直接写,内部会调用finish方法自动关闭OutputStream
*/
@GetMapping("/download")
public void download(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 防止中文乱码:import java.net.URLEncoder
String fileName = URLEncoder.encode("测试", "UTF-8");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName + ".xlsx");
// 创建文件簿
ExcelWriterBuilder workBook = EasyExcel.write(response.getOutputStream(), Student.class);
// 创建一个文件表
ExcelWriterSheetBuilder sheet = workBook.sheet("模板");
// 生成测试数据
ArrayList<Student> students = new ArrayList<Student>();
for (int i = 1; i <= 10; i++) {
students.add(new Student(null, "逐浪者-" + i, "男", new Date()));
}
// 写入,并且会关闭流
sheet.doWrite(students);
}
🍀 浏览器测试
💻 访问:http://localhost:8080/student/download


3.5 自定义单元格样式
EasyExcel支持调整行高、列宽、背景色、字体大小等内容,但是控制方式与使用原生POI无异,比较繁琐,不建议使用。
但是可以使用模板填充的方式,向预设样式的表格中直接写入数据,写入数据的时候会保持原有样式。
4.填充
4.1 填充一组数据
🍀 准备模板
Excel表格中用{} 来表示包裹要填充的变量,如果单元格文本中本来就有{、}左右大括号,需要在括号前面使用斜杠转义\{、\}。
代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。

🍀 封装数据
编写封装填充数据的类或选用Map
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 使用实体类封装填充数据
* 实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
*
* @author 角刀牛
* @create 2023-02-26 17:32
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FillData {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
}
🍀 填充
准备数据并填充到文件
/**
* 单组数据填充:封装类的方式
*/
@Test
public void test03() {
// 1.准备模板
String templatePath = FillData.class.getResource("/fill_data_template1.xlsx").getPath();
// 2.创建一个工作簿对象
ExcelWriterBuilder writeWorkBook = EasyExcel.write("excel填充-单组数据.xlsx", FillData.class)
.withTemplate(templatePath);
// 3.创建工作表对象
ExcelWriterSheetBuilder sheet1 = writeWorkBook.sheet("sheet1");
// 4.准备数据
FillData fillData = new FillData("逐浪者", 19);
// 5.填充数据,并关闭流
sheet1.doFill(fillData);
}
/**
* 单组数据填充:map的方式
*/
@Test
public void test04() {
// 1.准备模板
String templatePath = FillData.class.getResource("/fill_data_template1.xlsx").getPath();
// 2.创建一个工作簿对象
ExcelWriterBuilder writeWorkBook = EasyExcel.write("excel填充-单组数据.xlsx", FillData.class)
.withTemplate(templatePath);
// 3.创建工作表对象
ExcelWriterSheetBuilder sheet1 = writeWorkBook.sheet("sheet1");
// 4.准备数据:使用Map数据填充
HashMap<String, Object> mapFillData = new HashMap<>();
mapFillData.put("name", "角刀牛");
mapFillData.put("age", 18);
// 5.填充数据,并关闭流
sheet1.doFill(mapFillData);
}
🍀 测试效果

4.2 填充多组数据
🍀 准备模板
Excel表格中用{.} 来表示包裹要填充的变量,如果单元格文本中本来就有{、}左右大括号,需要在括号前面使用斜杠转义\{ 、\}。
代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。

🍀 封装数据
编写封装填充数据的类或选用Map
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 使用实体类封装填充数据
* 实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
*
* @author 角刀牛
* @create 2023-02-26 17:32
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FillData {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
}
🍀 填充
准备数据并填充到文件
/**
* 多组数据填充:封装类的方式
*/
@Test
public void test05() {
// 1.准备模板
String templatePath = FillData.class.getResource("/fill_data_template2.xlsx").getPath();
// 2.创建一个工作簿对象
ExcelWriterBuilder writeWorkBook = EasyExcel.write("excel填充-多组数据.xlsx", FillData.class)
.withTemplate(templatePath);
// 3.创建工作表对象
ExcelWriterSheetBuilder sheet1 = writeWorkBook.sheet("sheet1");
// 4.准备数据
ArrayList<FillData> data = new ArrayList<>();
for (int i = 15; i <= 30; i++) {
data.add(new FillData("逐浪者-" + i, i));
}
// 5.填充数据,并关闭流
sheet1.doFill(data);
}
🍀 测试效果

4.3 组合填充
🍀 准备模板
即有多组数据填充,又有单一数据填充,为了避免两者数据出现冲突覆盖的情况,在多组填充时需要通过FillConfig对象设置换行。

🍀 封装数据
编写封装填充数据的类或选用Map
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 使用实体类封装填充数据
* 实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
*
* @author 角刀牛
* @create 2023-02-26 17:32
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FillData {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
}
🍀 填充
准备数据并填充到文件
/**
* 组合数据填充
*/
@Test
public void test06() {
// 1.准备模板
String templatePath = FillData.class.getResource("/fill_data_template3.xlsx").getPath();
// 2.创建一个工作簿对象
ExcelWriter workBook = EasyExcel.write("excel填充-组合数据.xlsx", FillData.class)
.withTemplate(templatePath).build();
// 3.创建工作表对象
WriteSheet sheet = EasyExcel.writerSheet().build();
// 组合填充时,因为多组填充的数据量不确定,需要在多组填充完之后另起一行
FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
// 4.准备数据
ArrayList<FillData> data = new ArrayList<>();
for (int i = 15; i <= 30; i++) {
data.add(new FillData("逐浪者-" + i, i));
}
HashMap<String, String> dateAndTotal = new HashMap<>();
dateAndTotal.put("date", "2023-02-26");
dateAndTotal.put("total", "10000");
// 5.填充数据,会一并关闭流
// sheet1.doFill(data);
// 多组填充
workBook.fill(data, fillConfig, sheet);
// 单组填充
workBook.fill(dateAndTotal, sheet);
// 关闭流
workBook.finish();
}
🍀 测试效果

4.4 水平填充
🍀 准备模板
水平填充和多组填充模板一样,不一样的地方在于,填充时需要通过FillConfig对象设置水平填充。

🍀 封装数据
编写封装填充数据的类或选用Map
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 使用实体类封装填充数据
* 实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
*
* @author 角刀牛
* @create 2023-02-26 17:32
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FillData {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
}
🍀 填充
准备数据并填充到文件
/**
* 水平填充
*/
@Test
public void test07() {
// 1.准备模板
String templatePath = FillData.class.getResource("/fill_data_template4.xlsx").getPath();
// 2.创建一个工作簿对象
ExcelWriter workBook = EasyExcel.write("excel填充-水平填充.xlsx", FillData.class)
.withTemplate(templatePath).build();
// 3.创建工作表对象
WriteSheet sheet = EasyExcel.writerSheet().build();
// 4.数据准备
ArrayList<FillData> data = new ArrayList<>();
for (int i = 15; i <= 30; i++) {
data.add(new FillData("逐浪者-" + i, i));
}
// 换行
FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
// 多组填充
workBook.fill(data, fillConfig, sheet);
// 关闭流
workBook.finish();
}
🍀 测试效果

4.5 注意事项
为了节省内存,所以没有采用把整个文档在内存中组织好之后再整体写入到文件的做法,而是采用的是一行一行写入的方式,不能实现删除和移动行,也不支持备注写入。多组数据写入的时候,如果需要新增行,只能在最后一行增加,不能在中间位置添加。
4.6 填充综合练习

/**
* 报表导出
*/
@Test
public void test08() {
// 1.准备模板
String templatePath = FillData.class.getResource("/report_template.xlsx").getPath();
// 2.创建一个工作簿对象
ExcelWriter workBook = EasyExcel.write("报表.xlsx", FillData.class)
.withTemplate(templatePath).build();
// 3.创建工作表对象
WriteSheet sheet = EasyExcel.writerSheet().build();
// ****** 准备数据 *******
// 日期
HashMap<String, String> dateMap = new HashMap<>();
dateMap.put("date", "2020-03-16");
// 总会员数
HashMap<String, String> totalCountMap = new HashMap<>();
dateMap.put("totalCount", "1000");
// 新增员数
HashMap<String, String> increaseCountMap = new HashMap<>();
dateMap.put("increaseCount", "100");
// 本周新增会员数
HashMap<String, String> increaseCountWeekMap = new HashMap<>();
dateMap.put("increaseCountWeek", "50");
// 本月新增会员数
HashMap<String, String> increaseCountMonthMap = new HashMap<>();
dateMap.put("increaseCountMonth", "100");
// 新增会员数据
List<Student> students = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
students.add(new Student(null, "逐浪者-" + i, "男", new Date()));
}
// **** 准备数据结束****
// 写入统计数据
workBook.fill(dateMap, sheet);
workBook.fill(totalCountMap, sheet);
workBook.fill(increaseCountMap, sheet);
workBook.fill(increaseCountWeekMap, sheet);
workBook.fill(increaseCountMonthMap, sheet);
// 写入新增会员
workBook.fill(students, sheet);
// 关闭流
workBook.finish();
}

EasyExcel工具类(三)
5.常用API及注解
5.1 常用类
- EasyExcel:入口类,用于构建开始各种操作;
- ExcelReaderBuilder:构建出一个ReadWorkbook对象,即一个工作簿对象,对应的是一个Excel文件;
- ExcelWriterBuilder:构建出一个WriteWorkbook对象,即一个工作簿对象,对应的是一个Excel文件;
- ExcelReaderSheetBuilder:构建出一个ReadSheet对象,即一个工作表的对象,对应的Excel中的每个sheet,一个工作簿可以有多个工作表;
- ExcelWriterSheetBuilder:构建出一WriteSheet对象,即一个工作表的对象,对应的Excel中的每个sheet,一个工作簿可以有多个工作表;
- ReadListener:在每一行读取完毕后都会调用ReadListener来处理数据,我们可以把调用service的代码可以写在其
invoke方法内部; - WriteHandler:在每一个操作包括创建单元格、创建表格等都会调用WriteHandler来处理数据,对使用者透明不可见;
所有配置都是继承的。Workbook的配置会被Sheet继承。所以在用EasyExcel设置参数的时候,在EasyExcel…sheet()方法之前作用域是整个sheet,之后针对单个sheet。
5.2 读取时的注解
🍀 @ExcelProperty
使用位置:标准作用在成员变量上,吧实体类中属性和excel表中列关联起来。
可选属性:
| 属性名 | 含义 | 说明 |
| index | 对应Excel表中的列数 | 默认-1,建议指定时从0开始 |
| value | 对应Excel表中的列头 | |
| converter | 成员变量转换器 | 自定义转换器需要实Converter接口 |
使用效果:index属性可以指定当前字段对应excel中的哪一列,可以根据列名value去匹配,也可以不写。
如果不使用@ExcelProperty注解,成员变量从上到下的顺序,对应表格中从左到右的顺序;
**使用建议:**要么全部不写,要么全部用index,要么全部用名字去匹配,尽量不要三个混着用。
🍀 @ExcelIgnore
标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
🍀 @DateTimeFormat
标注在成员变量上,日期转换,代码中用String类型的成员变量去接收excel中日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat
/**
* 学生出生日期
*/
@ExcelProperty("出生日期")
@ColumnWidth(20)
@DateTimeFormat("yyyy-MM-dd")
private Date birthday;
🍀 @NumberFormat
标注在成员变量上,数字转换,代码中用String类型的成员变量去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat
🍀 @ExcelIgnoreUnannotated
标注在类上。
不标注该注解时,默认类中所有成员变量都会参与读写,无论是否在成员变量上加了@ExcelProperty 的注解。
标注该注解后,类中的成员变量如果没有标注 @ExcelProperty 注解将不会参与读写。
5.3 读取时通用参数
ReadWorkbook,ReadSheet 都会有的参数,如果为空,默认使用上级。
converter转换器,默认加载了很多转换器。也可以自定义。readListener监听器,在读取数据的过程中会不断的调用监听器。headRowNumber指定需要读表格的 列头行数。默认有一行头,也就是认为第二行开始起为数据。head与clazz二选一。读取文件头对应的列表,会根据列表匹配数据。建议使用class,就是文件中每一行数据对应的代码中的实体类型。clazz与head二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。autoTrim字符串、表头等数据自动trimpassword读的时候是否需要使用密码
5.4 ReadWorkbook(工作簿对象)参数
excelType当前excel的类型,读取时会自动判断,无需设置。inputStream与file二选一。建议使用file。file与inputStream二选一。读取文件的文件。autoCloseStream自动关闭流。readCache默认小于5M用 内存,超过5M会使用EhCache,不建议使用这个参数。useDefaultListener@since 2.1.4默认会加入ModelBuildEventListener来帮忙转换成传入class的对象,设置成false后将不会协助转换对象,自定义的监听器会接收到Map<Integer,CellData>对象,如果还想继续接听到class对象,请调用readListener方法,加入自定义的beforeListener、ModelBuildEventListener、 自定义的afterListener即可。
5.5 ReadSheet(工作表对象)参数
sheetNo需要读取Sheet的编号,建议使用这个来指定读取哪个SheetsheetName根据名字去匹配Sheet,excel 2003不支持根据名字去匹配
5.6 写入时的注解
5.6.1 @ExcelProperty
使用位置:标准作用在成员变量上
可选属性:
| 属性名 | 含义 | 说明 |
| index | 对应Excel表中的列数 | 默认-1,指定时建议从0开始 |
| value | 对应Excel表中的列头 | |
| converter | 成员变量转换器 | 自定义转换器需要实Converter接口 |
使用效果:
index指定写到第几列,如果不指定则根据成员变量位置排序;value指定写入的列头,如果不指定则使用成员变量的名字作为列头;
如果要设置复杂的头,可以为value指定多个值。
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 学生实体类
* lombok:通过一个插件 + 一个依赖 ,就可以在编译的时候自动帮助生成实体类常用方法
* 注解 @ContentRowHeight():内容的行高
* 注解 @HeadRowHeight:表头的行高
*
* @author 角刀牛
* @create 2023-02-26 14:56
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
/**
* 学生姓名
*/
@ExcelProperty(value = {"学员信息表", "学生姓名"})
@ColumnWidth(20)
private String name;
/**
* 学生出生日期
*/
@ExcelProperty(value = {"学员信息表", "出生日期"})
@ColumnWidth(20)
@DateTimeFormat("yyyy-MM-dd")
private Date birthday;
/**
* index 从0开始
* 学生性别
*/
@ExcelProperty(value = {"学员信息表", "学员性别"})
@ColumnWidth(20)
private String gender;
/**
* id
*/
@ExcelIgnore
private String id;
}

5.6.2 其他注解
基本和读取时一致
@ContentRowHeight():标注在类上或属性上,指定内容行高@HeadRowHeight():标注在类上或属性上,指定列头行高@ColumnWidth():标注在类上或属性上,指定列宽@ExcelIgnore:默认所有字段都会写入excel,这个注解会忽略这个字段DateTimeFormat:日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormatNumberFormat:数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormatExcelIgnoreUnannotated:默认不加ExcelProperty的注解的都会参与读写,加了不会参与
5.7 写入时通用参数
WriteWorkbook、WriteSheet都会有的参数,如果为空,默认使用上级。
converter转换器,默认加载了很多转换器。也可以自定义。writeHandler写的处理器。可以实现WorkbookWriteHandler,SheetWriteHandler,RowWriteHandler,CellWriteHandler,在写入excel的不同阶段会调用,对使用者透明不可见。relativeHeadRowIndex距离多少行后开始。也就是开头空几行needHead是否导出头head与clazz二选一。写入文件的头列表,建议使用class。clazz与head二选一。写入文件的头对应的class,也可以使用注解。autoTrim字符串、表头等数据自动trim
5.8 WriteWorkbook(工作簿对象)参数
excelType当前excel的类型,默认为xlsxoutputStream与file二选一。写入文件的流file与outputStream二选一。写入的文件templateInputStream模板的文件流templateFile模板文件autoCloseStream自动关闭流。password写的时候是否需要使用密码useDefaultStyle写的时候是否是使用默认头
5.9 WriteSheet(工作表对象)参数
sheetNo需要写入的编号。默认0sheetName需要些的Sheet名称,默认同sheetNo
6.easyexcel工具类
6.1 excel通用读取监听类
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
/**
* excel通用读取监听类
*
* @author 角刀牛
* @create 2023-02-26 15:10
*/
@Slf4j
@Getter
@NoArgsConstructor
public class ExcelListener<T> extends AnalysisEventListener<T> {
/**
* 自定义用于暂时存储data 可以通过实例获取该值
*/
private final List<T> dataList = new ArrayList<>();
/**
* 每解析一行都会回调invoke()方法
*
* @param data 每一行的数据
*/
@Override
public void invoke(T data, AnalysisContext context) {
dataList.add(data);
log.info("读取的一条信息:{}", data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
log.info("{}条数据,解析完成", dataList.size());
}
}
6.2 工具类
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.fox.easyexcel.listener.ExcelListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.Set;
/**
* @author 角刀牛
* @create 2023-02-26 21:50
*/
public class EasyExcelUtils {
private static final Log log = LogFactory.getLog(EasyExcelUtils.class);
/**
* 单sheet版本Excel读取
* 从Excel中读取文件,读取的文件是一个DTO类
*
* @param inputStream 文件流
* @param clazz 行数据类型
*/
public static <T> List<T> readExcelOneSheet(InputStream inputStream, final Class<?> clazz) {
// 1.创建监听类
ExcelListener<T> listener = new ExcelListener<>();
// 2.构建工作簿对象的输入流
ExcelReader excelReader = EasyExcel.read(inputStream, clazz, listener).build();
// 3.构建工作表对象的输入流,默认是第一张工作表
ReadSheet readSheet = EasyExcel.readSheet(0).build();
// 4.读取信息,每读取一行都会调用监听类的 invoke 方法
excelReader.read(readSheet);
// 5.关闭流,如果不关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
return listener.getDataList();
}
/**
* 多sheet版本Excel读取
*
* @param <T> 行数据的类型
* @param filePath 文件路径
* @param clazz 行数据的类型
* @return 所有信息
*/
public static <T> List<T> readExcelAllSheet(String filePath, final Class<?> clazz) {
ExcelListener<T> listener = new ExcelListener<>();
// 读取全部sheet
// 这里需要注意 ExcelListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写
EasyExcel.read(filePath, clazz, listener).doReadAll();
return listener.getDataList();
}
/**
* 网页上的下载导出,只有一个工作表
*
* @param fileName 文件名
* @param clazz 类的字节码文件,行数据的类型
* @param dataList 导出的数据
* @param sheetName 工作表名
* @param response 响应体
* @throws IOException 异常对象
*/
