目录
[一、 EasyExcel简介](#一、 EasyExcel简介)
[二、 EasyExcel快速上手](#二、 EasyExcel快速上手)
一、 EasyExcel简介
EasyExcel 是一个基于 Java 的简单、高效的 Excel 处理工具。它由阿里巴巴开源,主要解决了在 Java 应用中处理 Excel 文件时内存溢出的问题。
EasyExcel 和 Apache POI 都是 Java 生态中处理 Excel 文件的库,但它们在设计理念、性能、使用复杂度等方面有所不同。以下是 EasyExcel 相比于 Apache POI 的主要优势和一些差异:
性能:
-
内存占用:EasyExcel 是为低内存占用而设计的。在处理大型 Excel 文件时,EasyExcel 可以实现按行读取和写入,而不需要将整个文件加载到内存中,从而显著降低内存使用。而 Apache POI 在处理大型文件时,可能会因为将整个文件加载到内存而导致内存溢出。
-
处理速度:EasyExcel 对读取和写入操作进行了优化,通常在处理速度上比 Apache POI 更快。
使用复杂度:
-
API 设计:EasyExcel 提供了更为简洁的 API,使得读取和写入操作更加直观和方便。而 Apache POI 提供了丰富的 API,但这也使得它的使用相对复杂。
-
模型映射:EasyExcel 支持使用注解直接将 Excel 的列映射到 Java 实体的字段上,简化了数据转换的过程。Apache POI 需要手动处理每一行和每一列的数据。
官网链接:
EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 官网 (alibaba.com)
代码链接:
https://gitee.com/seniorGitee/easy-excel
二、 EasyExcel快速上手
引入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.2</version>
</dependency>
设置Excel相关注解
以员工实体类为例:
@ExcelIgnore设置忽略导出的字段信息
@ExcelProperty设置导出的字段名,导出的字段默认按照顺序从0开始排序,如果想指定导出字段的在第几列的话,可以设置对应的index,index的值:字段所在列数-1,类似数组下标索引
@DateTimeFormat定义日期格式
@ColumnWidth设置字段宽度
其实@ExcelProperty还有很多复杂的操作,具体请查看官网文档,这里只教会大家快速入门使用
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee implements Serializable {
/**
* 忽略这个字段
*/
@ExcelIgnore
private Long id;
@ExcelProperty("员工名")
private String name;
@ExcelProperty("员工年龄")
private Integer age;
@ExcelProperty(value = "员工工资",index = 3)
private BigDecimal salary;
@ColumnWidth(15)
@DateTimeFormat("yyyy年MM月dd日")
@ExcelProperty(value = "入职时间",index = 2)
private Date entryTime;
}
编写对应的监听类:
编写Employee对应的监听类EmployeeListener
注意:有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class EmployeeListener implements ReadListener<Employee> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<Employee> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
private IEmployeeService employeeService;
public EmployeeListener(IEmployeeService employeeService){
this.employeeService = employeeService;
}
//每一条数据解析都会进行调用
@Override
public void invoke(Employee employee, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JSON.toJSONString(employee));
cachedDataList.add(employee);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
//所有的数据解析完后进行调用
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
employeeService.save(cachedDataList);
log.info("存储数据库成功!");
}
}
简单写入数据:
注意:simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入(可以查看EasyExcel进阶操作:批量写入数据)
@SpringBootTest
class EasyExcelApplicationTests {
@Autowired
private IEmployeeService employeeService;
@Test
void simpleWrite() {
String fileName = ".\\src\\main\\resources\\static\\"+"simpleWrite" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName, Employee.class).sheet("模板").doWrite(employeeService.getData());
}
}
在resources/static中可以看到导出数据的Excel表格
简单读取数据:
读取数据有两种方式,分别是使用监听器和不使用监听器的方式进行读取数据
不需要使用监听器:
这里的fileName需要修改成读取Excel文件对应的路径
@SpringBootTest
@Slf4j
class EasyExcelApplicationTests {
@Autowired
private IEmployeeService employeeService;
@Test
void simpleRead(){
String fileName = ".\\src\\main\\resources\\static\\"+"simpleWrite1726145097517.xlsx";
// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
// 具体需要返回多少行可以在`PageReadListener`的构造函数设置
EasyExcel.read(fileName, Employee.class, new PageReadListener<Employee>(employeeList -> {
for (Employee employee : employeeList) {
log.info("读取到一条数据{}", JSON.toJSONString(employee));
}
})).sheet().doRead();
}
}
控制台输出的结果:
需要使用监听器:
这里的EmployListener对一个之前写的监听类,读取Excel文件的数据并将数据存储到数据库中
@SpringBootTest
@Slf4j
class EasyExcelApplicationTests {
@Autowired
private IEmployeeService employeeService;
@Test
void simpleReadByListener(){
String fileName = ".\\src\\main\\resources\\static\\"+"simpleWrite1726145097517.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, Employee.class, new EmployeeListener(employeeService)).sheet().doRead();
}
}
控制台输出的结果:
三、EasyExcel进阶操作
批量写入数据:
批量写入通一个sheet同一对象:
@SpringBootTest
@Slf4j
class EasyExcelApplicationTests {
@Autowired
private IEmployeeService employeeService;
@Test
void batchWriteOneSheep(){
// 方法1: 如果写到同一个sheet
String fileName = ".\\src\\main\\resources\\static\\"+"simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
try (ExcelWriter excelWriter = EasyExcel.write(fileName, Employee.class).build()) {
// 这里注意 如果同一个sheet只要创建一次
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
Long total = employeeService.total();
Integer pageSize = 10;
// 实际使用时根据数据库分页的总的页数来,这里批量插入,每次插入10条数据
for (int i = 1; i <= Math.ceil((double) total /pageSize); i++) {
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
PageBean pageBean = employeeService.pageEmployee(i,pageSize);
excelWriter.write(pageBean.getRows(), writeSheet);
}
}
}
}
批量写入不同的sheet同一对象:
@SpringBootTest
@Slf4j
class EasyExcelApplicationTests {
@Autowired
private IEmployeeService employeeService;
@Test
void batchWriteDifferentSheep(){
// 方法2: 如果写到不同的sheet 同一个对象
String fileName = ".\\src\\main\\resources\\static\\"+"simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 指定文件
try (ExcelWriter excelWriter = EasyExcel.write(fileName, Employee.class).build()) {
Long total = employeeService.total();
Integer pageSize = 10;
// 实际使用时根据数据库分页的总的页数来。这里最终会将数据写到多个sheet里面
for (int i = 1; i <= Math.ceil((double) total /pageSize); i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
WriteSheet writeSheet = EasyExcel.writerSheet(i, "员工表" + i).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
PageBean pageBean = employeeService.pageEmployee(i,pageSize);
excelWriter.write(pageBean.getRows(), writeSheet);
}
}
}
}
批量写入不同的sheet的不同对象:
@SpringBootTest
@Slf4j
class EasyExcelApplicationTests {
@Autowired
private IEmployeeService employeeService;
@Test
void batchWriteDifferentSheepAndObject(){
// 方法3 如果写到不同的sheet 不同的对象
String fileName = ".\\src\\main\\resources\\static\\"+"simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 指定文件
try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {
Long total = employeeService.total();
Integer pageSize = 10;
// 实际使用时根据数据库分页的总的页数来。这里最终会将数据写到多个sheet里面
for (int i = 1; i <= Math.ceil((double) total /pageSize); i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意Employee.class 可以每次都变,我这里为了方便 所以用的同一个class
// 实际上可以一直变
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模版表" + i).head(Employee.class).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
PageBean pageBean = employeeService.pageEmployee(i,pageSize);
excelWriter.write(pageBean.getRows(), writeSheet);
}
}
}
}
填充数据:
例如我们想将员工表填充到如下的Excel表格中
先对Excel表格模版填写占位符
执行填充Excel表格的代码
@SpringBootTest
@Slf4j
class EasyExcelApplicationTests {
@Autowired
private IEmployeeService employeeService;
@Test
void fillTemplateData(){
//需要填充模版
String templateFileName = ".\\src\\main\\resources\\static\\"+"fillTemplate.xlsx";
//模版填充后的文件
String fileName = ".\\src\\main\\resources\\static\\"+"fillTemplateComplete.xlsx";
// 这里 会填充到第一个sheet, 然后文件流会自动关闭
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(employeeService.getData());
}
}
四、实战EasyExcel(Excel表格的导入和导出)
web中进行Excel表格数据的导入和导出,以员工表为例:
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private IEmployeeService employeeService;
@PostMapping("/upload")
public void upload(MultipartFile file,HttpServletResponse response) throws IOException{
long begin = System.currentTimeMillis();
EasyExcel.read(file.getInputStream(), Employee.class, new EmployeeListener(employeeService)).sheet().doRead();
long end = System.currentTimeMillis();
response.setContentType("text/html;charset=utf-8");
response.getWriter().println("导出数据成功,耗时:"+(end-begin));
}
@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("导出的Excel数据", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), Employee.class).sheet("员工表").doWrite(employeeService.getData());
}
}