【后端】Java中使用EasyExcel实现Excel文件上传与分批处理


一、背景与概念

在Java开发中,处理 Excel 文件是一项常见的需求。EasyExcel 是阿里巴巴开源的一个高性能、易用的Excel读写库,它基于流式解析,具有低内存消耗的特点,非常适合处理大数据量的Excel文件。
本文将详细介绍如何在 Spring Boot 项目中使用 EasyExcel 实现Excel文件的上传,并对文件内容进行分批处理。


二、项目依赖配置

首先,我们需要在 pom.xml 文件中添加必要的依赖,主要包括 Spring Boot WebEasyExcel ,同时还需要引入 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 文件上传和解析,主要包括以下内容:

  1. Maven 依赖配置:引入 EasyExcelTransmittableThreadLocal
  2. MultipartFile 转换为 File:通过工具类实现文件转换。
  3. TtlRunnable + Runnable 异步处理:确保线程上下文信息正确传递。
  4. ExcelListener 实现数据解析:包含 invokeHeadMapinvokedoAfterAllAnalysed 等方法。
  5. 分批次处理与扩展性:支持高效处理大数据量文件,并提供灵活的业务扩展能力。

注:

  • 在生产环境中,请务必注意文件大小限制和上传安全性,避免恶意文件攻击。

如果您对本文有任何疑问或建议,欢迎在评论区留言!希望这篇文章对您有所帮助!
希望这篇博客能满足您的需求!如果有任何需要调整的地方,请随时告诉我!

五、福利资源

完整的代码示例已上传至附件,可在Intellij IDEA中导入并直接运行 免费下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值