在企业级应用开发中,数据导出是一个常见的需求。当面临百万级数据导出时,传统的数据导出方式往往会出现性能瓶颈,导致内存溢出、导出速度慢等问题。Spring Boot 框架提供了灵活的开发环境,而 EasyExcel 是一款基于 Java 的高性能 Excel 操作库,能够高效地处理百万级数据导出。本文将详细介绍如何在 Spring Boot 项目中集成 EasyExcel,实现轻松搞定百万级数据导出。
一、环境搭建
在开始之前,我们需要搭建一个 Spring Boot 项目。如果你还没有搭建过 Spring Boot 项目,可以通过 Spring Initializr (https://start.spring.io/) 快速生成一个项目模板。然后,在项目的pom.xml文件中添加 EasyExcel 的依赖。
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- EasyExcel Starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
</dependencies>
接下来,我们需要配置 Excel 文件的路径和文件名。在application.yml文件中添加以下配置:
excel:
path: ./excel
filename-prefix: data_
headers:
- id
- name
- age
- department
这里,我们配置了 Excel 文件的存储路径为项目根目录下的excel文件夹,文件名前缀为data_,表头信息包括id、name、age和department。
二、创建 Excel 模型类
为了将数据写入 Excel 文件,我们需要创建一个 Excel 模型类。该类用于表示 Excel 文件中的数据结构,并使用@ExcelProperty注解标记每个字段。
import com.alibaba.excel.annotation.ExcelProperty;
public class DataModel {
@ExcelProperty("ID")
private Long id;
@ExcelProperty("姓名")
private String name;
@ExcelProperty("年龄")
private Integer age;
@ExcelProperty("部门")
private String department;
// getter 和 setter 方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
}
在上面的代码中,我们创建了一个DataModel类,包含了id、name、age和department四个字段。每个字段都使用@ExcelProperty注解标记了对应的表头名称。这样,当我们将数据写入 Excel 文件时,字段值会按照注解中的名称写入对应的列。
三、编写数据导出服务类
数据导出服务类是实现数据导出的核心部分。我们需要从数据库中查询数据,并使用 EasyExcel 将数据写入 Excel 文件。
import com.alibaba.excel.EasyExcel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ExportService {
@Value("${excel.path}")
private String excelPath;
@Value("${excel.filename-prefix}")
private String filenamePrefix;
public void exportData(List<DataModel> dataModels) {
// 生成文件名
String fileName = excelPath + "/" + filenamePrefix + System.currentTimeMillis() + ".xlsx";
// 使用 EasyExcel 写入数据
EasyExcel.write(fileName, DataModel.class).sheet("Sheet1").doWrite(dataModels);
}
}
在上面的代码中,我们创建了一个ExportService类,用于实现数据导出功能。该类注入了 Excel 文件的存储路径和文件名前缀,并提供了一个exportData方法。在exportData方法中,我们生成了一个唯一的文件名,然后使用 EasyExcel 的write方法将数据写入 Excel 文件。write方法的参数包括文件名、数据模型类和工作表名称,最后通过doWrite方法将数据写入文件。
四、创建控制器类
为了方便用户调用数据导出功能,我们需要创建一个控制器类,暴露 REST API 接口。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class ExportController {
@Autowired
private ExportService exportService;
@GetMapping("/export")
public String exportData() {
// 模拟从数据库中查询数据
List<DataModel> dataModels = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
DataModel dataModel = new DataModel();
dataModel.setId((long) i);
dataModel.setName("User" + i);
dataModel.setAge(30);
dataModel.setDepartment("Department" + i % 10);
dataModels.add(dataModel);
}
// 调用数据导出服务
exportService.exportData(dataModels);
return "Data exported successfully!";
}
}
在上面的代码中,我们创建了一个ExportController类,用于处理数据导出的请求。该类注入了ExportService类,并提供了一个/export的 GET 请求接口。在exportData方法中,我们模拟了从数据库中查询数据的过程,生成了一个包含 1000000 条数据的列表。然后,调用exportService的数据导出功能,将数据写入 Excel 文件。
五、性能优化
在处理百万级数据导出时,性能优化是关键。EasyExcel 采用流式读写的方式,避免了大量数据对内存的占用。我们可以通过以下方式进行性能优化:
1. 使用异步导出
对于大规模数据导出,可以使用异步处理的方式,避免主线程被阻塞。Spring Boot 提供了@Async注解,可以方便地实现异步导出。
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ExportService {
@Value("${excel.path}")
private String excelPath;
@Value("${excel.filename-prefix}")
private String filenamePrefix;
@Async
public void exportData(List<DataModel> dataModels) {
// 生成文件名
String fileName = excelPath + "/" + filenamePrefix + System.currentTimeMillis() + ".xlsx";
// 使用 EasyExcel 写入数据
EasyExcel.write(fileName, DataModel.class).sheet("Sheet1").doWrite(dataModels);
}
}
在上面的代码中,我们在exportData方法上添加了@Async注解,将其标记为异步方法。这样,当用户调用数据导出接口时,数据导出将在后台线程中执行,不会阻塞主线程。
2. 分页查询数据
如果数据量非常大,一次性从数据库中查询所有数据可能会导致内存不足。可以通过分页查询的方式,分批次加载数据,并使用 EasyExcel 将数据分批次写入 Excel 文件。
import com.alibaba.excel.EasyExcel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ExportService {
@Value("${excel.path}")
private String excelPath;
@Value("${excel.filename-prefix}")
private String filenamePrefix;
public void exportDataByPage() {
// 生成文件名
String fileName = excelPath + "/" + filenamePrefix + System.currentTimeMillis() + ".xlsx";
// 使用 EasyExcel 写入数据
EasyExcel.write(fileName, DataModel.class).sheet("Sheet1").doWrite(data -> {
// 分页查询数据
for (int pageNum = 1; ; pageNum++) {
List<DataModel> dataModels = queryDataByPage(pageNum, 1000);
if (dataModels.isEmpty()) {
break;
}
data.write(dataModels);
}
});
}
private List<DataModel> queryDataByPage(int pageNum, int pageSize) {
// 模拟分页查询数据
List<DataModel> dataModels = new ArrayList<>();
int start = (pageNum - 1) * pageSize;
for (int i = start; i < start + pageSize; i++) {
DataModel dataModel = new DataModel();
dataModel.setId((long) i);
dataModel.setName("User" + i);
dataModel.setAge(30);
dataModel.setDepartment("Department" + i % 10);
dataModels.add(dataModel);
}
return dataModels;
}
}
在上面的代码中,我们使用了分页查询的方式,每次查询 1000 条数据,并将数据分批次写入 Excel 文件。这样可以减少内存的占用,并提高数据导出的性能。
六、测试与验证
在完成数据导出功能的开发后,需要进行测试与验证。我们可以通过以下步骤进行测试:
1. 准备测试数据
首先,我们需要准备一些测试数据。可以使用数据生成工具,或者手动创建一些数据。例如,可以创建一个包含 1000000 条记录的数据库表。
2. 调用数据导出接口
启动 Spring Boot 应用程序,并访问/export接口,触发数据导出功能。
3. 检查导出结果
检查生成的 Excel 文件是否包含所有数据,并且数据格式是否正确。可以使用 Excel 软件打开文件,查看数据是否按照预期排列。
4. 测试性能
可以使用性能测试工具,如 JMeter 或 Gatling,对数据导出接口进行测试,记录导出时间、内存占用、CPU 使用率等指标,评估数据导出功能的性能表现。