SpringBoot整合EasyExcel实现高效读写与分类导入实战
在使用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开发技巧的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









