在企业级应用开发场景中,数据的高效流转与处理始终是核心需求之一。Excel 作为最常用的数据载体,如何快速、稳定地将其内容导入系统成为开发人员的高频任务。EasyExcel 凭借其轻量级架构、低内存占用和简洁的 API 设计,完美解决了传统 POI 框架在大数据量处理时内存溢出等痛点;而 Spring Boot 以 “约定优于配置” 的理念,极大简化了 Java 项目的搭建与部署流程。当 EasyExcel 与 Spring Boot 强强联合,不仅能实现基础数据的快速导入,更以标准化、模块化的开发模式,降低代码耦合度,提升项目的可维护性与扩展性。无论是财务报表的批量录入,还是基础档案的系统迁移,这套技术组合都能以优雅高效的方式完成数据交互,成为企业数字化转型中不可或缺的技术利器。
第一步 导入easyexcel依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
第二步 将基础数据domain类添加easyexcel注解
@ExcelIgnoreUnannotated
@ExcelIgnoreUnannotated
是 EasyExcel 提供的一个类级别注解,用来决定是否忽略那些没有被其他 EasyExcel 注解(如 @ExcelProperty
)标注的字段。这个注解可以帮助开发者更好地控制数据模型与 Excel 表格之间的映射关系。简单来讲就是忽略没有@ExcelProperty注解的字段,添加在类上
@ExcelProperty
ExcelProperty
注解用于匹配excel
和实体类字段之间的关系。注意value值需要与导入excel表的表头字段对应
例如 @ExcelIgnoreUnannotated @Data @AutoMapper(target = MdItem.class, reverseConvertGenerate = false) public class MdItemBo implements Serializable { @Serial private static final long serialVersionUID = 1L; /** * 产品物料ID */ @NotNull(message = "产品物料ID不能为空", groups = {EditGroup.class}) private Long itemId; /** * 产品物料编码 */ @ExcelProperty(value = "产品物料编码") @NotBlank(message = "产品物料编码不能为空", groups = {AddGroup.class, EditGroup.class}) private String itemCode; /** * 产品物料名称 */ @ExcelProperty(value = "产品物料名称") @NotBlank(message = "产品物料名称不能为空", groups = {AddGroup.class, EditGroup.class}) private String itemName;
第三步 建一个类去继承AnalysisEventListener。这个接口就easyexcel的公共监听器,实现里面三个方法,泛型使用刚定义的实体类,记得这个类要加@service注解给spring管理
@Service @Slf4j public class MdItemListener extends AnalysisEventListener<MdItemBo> { @Autowired private MdItemMapper mdItemMapper; private List<MdItem> listData = new ArrayList<>(); @Autowired private IMdUnitService mdUnitService; @Autowired private IMdBizCategoryService mdBizCategoryService; @Override public void invoke(MdItemBo mdItemBo, AnalysisContext analysisContext) { log.info("解析到一条数据:{}", mdItemBo); MdItem mdItem = MapstructUtils.convert(mdItemBo, MdItem.class); LambdaQueryWrapper<MdItem> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(MdItem::getItemCode, mdItem.getItemCode()); boolean notUnique = mdItemMapper.exists(wrapper); if (notUnique) { throw new RuntimeException("物料编码不能重复:" + mdItemBo.getItemCode()); } String unitName = mdItemBo.getUnitName(); String itemTypeName = mdItemBo.getItemTypeName(); MdUnit unit = mdUnitService.lambdaQuery().eq(MdUnit::getUnitName, unitName).one(); mdItem.setUnitId(unit.getUnitId()); MdBizCategory mdBizCategory = mdBizCategoryService.lambdaQuery().eq(MdBizCategory::getCategoryName, itemTypeName).one(); mdItem.setItemTypeId(mdBizCategory.getCategoryId()); listData.add(mdItem); } @Override @Transactional(rollbackFor = Exception.class) public void doAfterAllAnalysed(AnalysisContext analysisContext) { mdItemMapper.insertBatch(listData); listData.clear(); log.info("所有数据解析完成!"); } @Override public void onException(Exception exception, AnalysisContext context) { // 如果是某一个单元格的转换异常 能获取到具体行号 // 如果要获取头的信息 配合doAfterAllAnalysedHeadMap使用 if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception; log.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex()+1, excelDataConvertException.getColumnIndex()+1,exception); } throw new RuntimeException("导入失败:" + exception.getMessage(), exception); } }
invoke方法
easyexcel每解析一条数据就会调用一次,参数就是读取到的实体类,
可以在方法里面加上业务逻辑,如唯一性判断
doAfterAllAnalysed方法
excel所有行数据解析完会执行该方法
onException方法:异常处理方法
最后一步 后端就处理完了,写一个接口,调用read方法完成
@Autowired private MdItemListener listener;@PostMapping("/import") public R<Void> importData(@RequestParam("file") MultipartFile file) throws IOException { try { // 读取Excel并自动填充到监听器 EasyExcel.read(file.getInputStream(), MdItemBo.class,listener ) .sheet("物料产品") .headRowNumber(1) // 跳过表头行 .doRead(); return R.ok(); } catch (Exception e) { // 捕获异常并返回失败响应 return R.fail(e.getMessage()); } } }
注意点 listener要使用依赖注入方式
前端只要上传文件到这个接口就完成了