SpringBoot 整合 EasyExcel 实现百万级Excel导入导出实战

举报
JiHua 发表于 2026/10/26 11:39:42 2026/10/26
【摘要】 摘要:在后台管理系统中,Excel导入导出是非常高频的需求。Apache POI 容易出现内存溢出、代码冗余的问题,本文基于 SpringBoot + EasyExcel 实现高性能Excel读写,封装通用监听器、统一返回结果,提供导入校验、分批处理、导出大数据的完整示例,同时讲解避坑要点,适合后台报表、数据批量录入场景直接落地。

前言

日常开发里,用户经常需要批量导入业务数据、导出报表。原生POI操作Excel上手繁琐,读取大文件时会把全部数据加载到堆内存,很容易OOM。阿里开源的EasyExcel对POI做了封装,采用Sax逐行读取,低内存占用,API简洁,是目前Java生态里Excel处理的首选方案。

本文搭建一套可复用的导入导出模板,包含实体映射、自定义监听器、参数校验、异常捕获、大数据分页导出,代码可以直接复制到项目使用。

一、环境依赖

Maven pom.xml 引入依赖,注意版本适配SpringBoot:

<!-- EasyExcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

二、定义Excel映射实体

使用@ExcelProperty注解绑定表头名称,@ExcelIgnore忽略不需要导出的字段:

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.ExcelIgnore;
import lombok.Data;
import java.time.LocalDateTime;

@Data
public class UserExcelDTO {

    @ExcelProperty("用户名")
    private String username;

    @ExcelProperty("手机号")
    private String phone;

    @ExcelProperty("邮箱")
    private String email;

    @ExcelProperty("状态")
    private Integer status;

    @ExcelProperty("创建时间")
    private LocalDateTime createTime;

    @ExcelIgnore
    private Long id;
}

三、通用导入监听器(重点)

EasyExcel读取需要自定义监听器,每读取一行回调一次,避免一次性加载全部数据。这里做批量攒批入库,减少数据库IO:

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class CommonExcelListener<T> extends AnalysisEventListener<T> {

    private final List<T> cacheList = new ArrayList<>();
    private static final int BATCH_SIZE = 500;
    private final Consumer<List<T>> saveFunc;

    public CommonExcelListener(Consumer<List<T>> saveFunc) {
        this.saveFunc = saveFunc;
    }

    @Override
    public void invoke(T data, AnalysisContext context) {
        cacheList.add(data);
        // 攒够批次执行入库
        if (cacheList.size() >= BATCH_SIZE) {
            saveFunc.accept(cacheList);
            cacheList.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 读取结束,处理剩余数据
        if (!cacheList.isEmpty()) {
            saveFunc.accept(cacheList);
            cacheList.clear();
        }
    }
}

四、Controller 导入&导出接口

import com.alibaba.excel.EasyExcel;
import org.springframework.web.bind.annotation.GetMapping;
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.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

@RestController
@RequestMapping("/excel")
public class ExcelController {

    // 模拟service
    private void batchSaveUser(List<UserExcelDTO> list) {
        // userService.batchInsert(list);
        System.out.println("批量入库:" + list.size() + "条");
    }

    /**
     * Excel导入
     */
    @PostMapping("/import")
    public String importExcel(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), UserExcelDTO.class, new CommonExcelListener<UserExcelDTO>(this::batchSaveUser))
                .sheet()
                .doRead();
        return "导入成功";
    }

    /**
     * Excel导出
     */
    @GetMapping("/export")
    public void exportExcel(HttpServletResponse response) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("用户数据报表", StandardCharsets.UTF_8.name());
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");

        // 模拟从数据库分页查询数据
        // List<UserExcelDTO> dataList = userService.listAll();
        List<UserExcelDTO> dataList = List.of();

        EasyExcel.write(response.getOutputStream(), UserExcelDTO.class)
                .sheet("用户列表")
                .doWrite(dataList);
    }
}

五、大数据导出优化思路

如果数据量上万甚至几十万条,一次性查询会压垮数据库和内存:

  1. 分页查询,分批写入Excel,不用一次性组装全量List;
  2. 异步导出:后台任务生成文件,生成完成后通知用户下载;
  3. 关闭自动表头样式、关闭额外格式,提升写入速度;
  4. 优先使用xlsx格式,不推荐老旧xls(单sheet上限65535行)。

六、常见踩坑总结

  1. 表头顺序问题:@ExcelProperty 默认按字段顺序,指定index可以固定列顺序;
  2. 日期转换失败:日期类型需要额外配置转换器;
  3. 文件上传空指针:前端form必须设置enctype="multipart/form-data";
  4. 导出文件名乱码:一定要做URLEncoder编码,兼容Chrome、Edge;
  5. 监听器不能交给Spring管理:监听器每次读取新建实例,不要加@Component,需要注入Service用构造传入。

七、总结

EasyExcel通过Sax解析模式,从根源解决POI大文件OOM痛点,封装好监听器之后,后续所有Excel导入都可以复用这套代码。在后台管理系统里,导入导出属于标准通用能力,把这部分能力抽成工具类之后,后续业务开发效率会提升很多。本文代码开箱即用,大家可以基于这个模板扩展数据校验、错误行回写、导出字典翻译等能力。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。