📖目录
一、背景与概念
在Java开发中,处理 Excel
文件是一项常见的需求。EasyExcel
是阿里巴巴开源的一个高性能、易用的Excel读写库,它基于流式解析,具有低内存消耗的特点,非常适合处理大数据量的Excel文件。
本文将详细介绍如何在 Spring Boot
项目中使用 EasyExcel
实现Excel文件的上传,并对文件内容进行分批处理。
二、项目依赖配置
首先,我们需要在 pom.xml
文件中添加必要的依赖,主要包括 Spring Boot Web
和 EasyExcel
,同时还需要引入 Alibaba TransmittableThreadLocal(TTL)
来处理线程本地变量的传递。以下是具体的依赖配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
<!-- Alibaba TransmittableThreadLocal (TTL) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.3</version>
</dependency>
</dependencies>
上述配置中,spring-boot-starter-web
用于构建Web应用, easyexcel
是核心的Excel处理库, transmittable-thread-local
用于解决线程本地变量在异步线程中传递的问题
三、核心代码实现
1. Controller中上传MultipartFile并转换为File
在 Spring Boot
中,文件上传通常通过 实现。我们需要将其转换为 File
对象,以便后续处理。 MultipartFile
@RestController
@RequestMapping("/excel")
public class ExcelController {
@PostMapping("/upload")
public ResponseEntity<String> uploadExcel(@RequestParam("file") MultipartFile multipartFile) {
// 将 MultipartFile 转换为 File
File tempFile = FileConversionUtil.convertMultipartFileToFile(multipartFile);
// 使用 TtlRunnable + Runnable 异步处理
TtlRunnable.get(() -> {
try (InputStream inputStream = Files.newInputStream(tempFile.toPath())) {
// 使用 EasyExcel 读取文件
EasyExcel.read(inputStream, new ExcelDataListener()).sheet().doRead();
} catch (IOException e) {
throw new RuntimeException("文件读取失败", e);
}
}).run();
return ResponseEntity.ok("上传成功");
}
}
注:
FileConversionUtil.convertMultipartFileToFile
方法用于将 转换为临时File
对象。MultipartFile
- 使用
TtlRunnabl
包装任务,确保线程上下文信息(如TransmittableThreadLocal
)能够正确传递。- 在
try-with-resources
块中处理InputStream
,以避免资源泄漏。
2. 文件转换工具类
在 Spring Boot
中,文件上传通常使用 MultipartFile
对象来接收。为了方便后续处理,我们需要将 MultipartFile
转换为 File
对象。以下是 FileConversionUtil
类的实现:
public class FileConversionUtil {
/**
* 将 MultipartFile 转换为 File 对象
*/
public static File convertMultipartFileToFile(MultipartFile multipartFile) {
// 获取原始文件名
String originalFilename = multipartFile.getOriginalFilename();
// 从原始文件名中提取前缀和后缀
String prefix = extractPrefix(originalFilename);
String suffix = extractSuffix(originalFilename);
// 调用 FileUtil.multipartFile2File 方法生成临时文件
try {
return FileUtil.multipartFile2File(multipartFile, prefix, suffix);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 从文件名中提取前缀
*/
private static String extractPrefix(String filename) {
int lastIndex = filename.lastIndexOf('.');
if (lastIndex == -1) {
return filename; // 如果没有扩展名,整个文件名作为前缀
}
return filename.substring(0, lastIndex);
}
/**
* 从文件名中提取后缀
*/
private static String extractSuffix(String filename) {
int lastIndex = filename.lastIndexOf('.');
if (lastIndex == -1) {
return ""; // 如果没有扩展名,返回空字符串
}
return filename.substring(lastIndex);
}
}
此工具类提供了 convertMultipartFileToFile
方法,用于将 MultipartFile
转换为 File
对象。在转换过程中,会调用 FileUtil
类的 multipartFile2File
方法来生成临时文件。
3. 文件工具类
FileUtil
类负责将 MultipartFile
转换为临时文件,具体实现如下:
public class FileUtil {
/**
* 将 MultipartFile 转换为临时文件
*/
public static File multipartFile2File(MultipartFile multipartFile, String prefix, String suffix) throws IOException {
// 创建临时文件
File tempFile = File.createTempFile(prefix, suffix);
// 获取输入流
try (InputStream inputStream = multipartFile.getInputStream();
FileOutputStream outputStream = new FileOutputStream(tempFile)) {
// 将输入流写入临时文件
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
return tempFile;
}
}
该方法通过创建临时文件,并将 MultipartFile
的输入流写入临时文件,实现了文件的转换。
4. Excel数据监听器
ExcelDataListener
类继承自 AnalysisEventListener
,用于处理Excel数据的解析。它采用了分批处理的策略,每解析一定数量的行就会进行一次处理,避免内存溢出。
public class ExcelDataListener extends AnalysisEventListener<Map<Integer, String>> {
// 用于存储表头的信息
private Map<Integer, String> headMap;
private static final int BATCH_COUNT = 50;
// 用于存储值
private List<Map<Integer, String>> cacheDataList = new ArrayList<>(BATCH_COUNT);
// 解析每一行数据
@Override
public void invoke(Map<Integer, String> rowData, AnalysisContext context) {
cacheDataList.add(rowData);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cacheDataList.size() >= BATCH_COUNT) {
readData();
// 存储完成清理 list
cacheDataList.clear();
// 重新清空 list,方便内存回收
cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
// 表头解析(表头信息通常用于映射列名和业务字段)
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
this.headMap = headMap;
System.out.println("表头信息: " + this.headMap);
}
// 解析完成回调(会在所有数据解析完成后调用,可以用于统计总行数或执行其他收尾操作。)
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 最后一轮执行
readData();
// 获取总行数(含表头)
Integer rowNumber = context.readSheetHolder().getApproximateTotalRowNumber();
System.out.println("Excel解析完成,总共处理数据行数:" + rowNumber);
}
// 读取数据,进行业务处理
private void readData() {
System.out.println("开始处理数据数,size:" + cacheDataList.size());
for (Map<Integer, String> rowData : cacheDataList) {
System.out.println("解析行数据: " + rowData);
}
}
}
5. 运行
浏览器打开链接:localhost:8080/index.html,选择对应的 Excel
文件上传
6. 执行结果
表头信息: {0=第一列, 1=第二列, 2=第三列}
开始处理数据数,size:50
解析行数据: {0=1, 1=10, 2=100}
(省略...)
解析行数据: {0=50, 1=500, 2=5000}
开始处理数据数,size:50
解析行数据: {0=51, 1=510, 2=5100}
(省略...)
解析行数据: {0=100, 1=1000, 2=10000}
开始处理数据数,size:1
解析行数据: {0=101, 1=1010, 2=10100}
Excel解析完成,总共处理数据行数:102
7. 分批次处理与扩展性
EasyExcel
支持分批次处理数据,非常适合处理大文件。以下是其优势:
7.1 分批次处理
EasyExcel
内部实现了流式读取,不会一次性加载整个文件到内存中,而是逐行读取并处理。这种方式可以有效减少内存占用。
7.2 扩展性
在 invoke
方法中,您可以对每一行的数据进行自定义处理,例如写入数据库、调用外部接口等。这种设计使得代码易于扩展。
注:
- 可以通过
context.readRowHolder().getRowIndex()
获取当前行的索引,便于调试和日志记录。
四、总结
本文介绍了如何在 Spring Boot
项目中使用 EasyExcel
处理 Excel 文件上传和解析,主要包括以下内容:
- Maven 依赖配置:引入
EasyExcel
和TransmittableThreadLocal
- MultipartFile 转换为 File:通过工具类实现文件转换。
- TtlRunnable + Runnable 异步处理:确保线程上下文信息正确传递。
- ExcelListener 实现数据解析:包含
invokeHeadMap
、invoke
和doAfterAllAnalysed
等方法。 - 分批次处理与扩展性:支持高效处理大数据量文件,并提供灵活的业务扩展能力。
注:
- 在生产环境中,请务必注意文件大小限制和上传安全性,避免恶意文件攻击。
如果您对本文有任何疑问或建议,欢迎在评论区留言!希望这篇文章对您有所帮助!
希望这篇博客能满足您的需求!如果有任何需要调整的地方,请随时告诉我!
五、福利资源
完整的代码示例已上传至附件,可在Intellij IDEA中导入并直接运行 免费下载