SpringBoot整合EasyExcel实现高效读写与分类导入实战

Chloe
Chloe
发布时间:2026-10-16| 5人浏览过
原创

在使用Java处理Excel文件时,Apache POI等经典框架在应对大并发或海量数据时容易出现内存溢出(OOM)。为解决这一痛点,本文基于SpringBoot框架演示如何使用阿里开源的EasyExcel进行高效的数据读写。文章详细讲解了核心依赖引入、实体类注解映射、基础读写代码实现,并通过一个两级分类数据导入的实际案例,深入剖析了利用监听器进行逐行解析与去重保存的最佳实践。

为什么选择EasyExcel

Java生态中常见的Excel处理框架如Apache POI和JXL,虽然功能强大,但在处理大规模数据时存在显著的缺点:它们倾向于将整个文件加载到内存中,极易引发Out Of Memory (OOM)错误或触发频繁的Full GC,影响系统稳定性。

相比之下,EasyExcel由阿里巴巴开源,其核心优势在于极低内存占用。它采用“一行一读取”的流式解析模式,将每一行的解析结果通过观察者模式通知给使用者,而非一次性加载全部数据。这使得即使在处理数十万行数据时,也能保持稳定的内存消耗。

基础功能:创建实体与读写测试

使用EasyExcel的第一步是建立Java对象与Excel列的对应关系,随后即可进行简单的写入或读取操作。

第1步:定义数据实体类

通过 @ExcelProperty 注解指定字段对应的Excel表头名称及列索引(Index),这是读写操作的基础映射依据。

/**
 * DemoData.java
 */
@Data
public class DemoData {
    // index = 0 表示第一列
    @ExcelProperty(value = "学生编号",index = 0)
    private Integer sno;

    // index = 1 表示第二列
    @ExcelProperty(value = "学生姓名",index = 1)
    private String sname;
}

注意:如果省略index,EasyExcel会默认按字面声明顺序匹配(第一个字段为0)。显式设置index能提高容错率,防止因列顺序调整导致的错位。

第2步:执行写Excel操作

通过 EasyExcel.write() 方法,传入目标文件路径和数据类Class,配合Sheet名和数据源即可完成写入。该方法会自动构建表头并逐行写入数据。

public class TestWriteExcel {
    public static void main(String[] args) {
        String fileName = "D:\\write.xlsx";
        // 写入操作:指定路径、实体类、Sheet名称及数据列表
        EasyExcel.write(fileName, DemoData.class).sheet("学生列表").doWrite(getData());
    }

    private static List<DemoData> getData() {
        List<DemoData> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setSno(i);
            data.setSname("lucy" + i);
            list.add(data);
        }
        return list;
    }
}

第3步:执行读Excel操作

读取比写入稍复杂,需要实现一个监听器 AnalysisEventListener 来处理每行数据的回调逻辑。

public class ExcelListener extends AnalysisEventListener<DemoData> {
    /**
     * 逐行调用,处理每一行解析出的数据
     */
    @Override
    public void invoke(DemoData data, AnalysisContext analysisContext) {
        System.out.println("****" + data);
    }

    /**
     * 解析表头信息
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头:"+headMap);
    }

    /**
     * 所有数据解析完成后触发
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 清理资源或统计逻辑
    }
}

在测试类中调用读取接口:

public class TestReadExcel {
    public static void main(String[] args) {
        String fileName = "D:\\write.xlsx";
        // 读取操作:指定路径、实体类、监听器实例
        EasyExcel.read(fileName, DemoData.class, new ExcelListener())
                .sheet()
                .doRead();
    }
}

进阶实战:SpringBoot中批量导入分类数据

在实际业务中,往往需要将Excel中的层级数据(如一级分类、二级分类)存入数据库。以下展示如何在SpringBoot环境下,通过上传Excel文件批量添加两层级的课程分类。

准备工作:引入依赖与建表

POM依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.5</version>
</dependency>

数据库设计:创建一个简单的课程科目表 edu_subject,包含主键、标题、父ID(parent_id用于关联层级)、排序和时间戳字段。

CREATE TABLE `edu_subject` (
  `id` char(19) NOT NULL COMMENT '课程类别ID',
  `title` varchar(10) NOT NULL COMMENT '类别名称',
  `parent_id` char(19) NOT NULL DEFAULT '0' COMMENT '父ID',
  `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程科目';

编写Controller接收上传文件

使用SpringMVC的 MultipartFile 接收前端上传的文件流,直接传递给Service层处理。

@RestController
public class SubjectController {
    @Autowired
    private EduSubjectService subjectService;

    @PostMapping("addSubject")
    public R addSubject(MultipartFile file){
        // 调用Service层进行读取和保存
        subjectService.saveSubject(file, subjectService);
        return R.ok();
    }
}

Service层:启动解析流程

在服务实现中,从 MultipartFile 获取输入流,调用 EasyExcel.read,并将实现了业务逻辑的自定义监听器注入进去。

@Override
public void saveSubject(MultipartFile file, EduSubjectService subjectService) {
    try {
        InputStream in = file.getInputStream();
        // 传入输入流、数据类型、以及注入了Service的监听器
        EasyExcel.read(in, SubjectData.class, new SubjectExcelListener(subjectService)).sheet().doRead();
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException("Excel导入失败");
    }
}

核心监听器:逻辑处理与去重保存

这是最关键的部分。在 invoke 方法中,我们根据模板格式(例如第一列为一级分类名,第二列为二级分类名)解析数据,并在数据库中执行“不存在则插入”的逻辑,避免重复添加。

@Component
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {

    private EduSubjectService subjectService;

    public SubjectExcelListener(EduSubjectService subjectService) {
        this.subjectService = subjectService;
    }

    /**
     * 每次读取一行数据都会触发此方法
     */
    @Override
    public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
        if (subjectData == null) return;

        String oneName = subjectData.getOneSubjectName();
        String twoName = subjectData.getTwoSubjectName();

        // 1. 处理一级分类:检查是否存在
        LambdaQueryWrapper<EduSubject> oneWrapper = new LambdaQueryWrapper<>();
        oneWrapper.eq(EduSubject::getTitle, oneName)
                  .eq(EduSubject::getParentId, "0");
        EduSubject existOne = subjectService.getOne(oneWrapper);

        String parentId;
        if (existOne == null) {
            // 新建一级分类,父ID设为0
            existOne = new EduSubject();
            existOne.setParentId("0");
            existOne.setTitle(oneName);
            subjectService.save(existOne);
        }
        parentId = existOne.getId();

        // 2. 处理二级分类:检查在当前一级下是否已存在
        LambdaQueryWrapper<EduSubject> twoWrapper = new LambdaQueryWrapper<>();
        twoWrapper.eq(EduSubject::getTitle, twoName)
                  .eq(EduSubject::getParentId, parentId);
        EduSubject existTwo = subjectService.getOne(twoWrapper);

        if (existTwo == null) {
            // 新建二级分类,父ID设为刚才的一级ID
            existTwo = new EduSubject();
            existTwo.setParentId(parentId);
            existTwo.setTitle(twoName);
            subjectService.save(existTwo);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 解析结束后的清理工作
    }
}

最终效果验证

按照以下Excel模板准备数据(两列分别为一级和二级分类名):

上传至接口后,系统会根据逻辑生成层级关系并存入数据库,控制台输出如下响应:

查看数据库记录,已成功生成带有父子关联的完整分类树:

总结

通过上述步骤,我们完成了SpringBoot与EasyExcel的深度整合。从简单的实体映射到复杂的层级数据解析与持久化,EasyExcel凭借其低内存占用的特性,完美解决了传统方式处理大批量Excel导入时的性能瓶颈。以上就是SpringBoot整合EasyExcel实现高效读写与分类导入的详细内容,更多关于SpringBoot开发技巧的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。