跳到主要内容Spring Boot + EasyExcel 实现 Excel 导入导出 | 极客日志Javajava
Spring Boot + EasyExcel 实现 Excel 导入导出
本文介绍基于 Spring Boot 和 EasyExcel 实现 Excel 文件导入导出的完整流程。涵盖依赖配置、实体类注解设计、自定义监听器处理批量导入、Controller 层导出接口实现以及事务回滚等优化方案。通过实际代码示例,展示如何从数据库读取数据导出为 Excel,或将 Excel 数据批量写入数据库,解决内存溢出、数据校验及乱码等常见问题。
Spring Boot + EasyExcel 实现 Excel 导入导出
在 Spring Boot 项目中,Excel 导入导出是文件操作的核心落地场景——无论是日常办公的数据批量录入、系统数据备份,还是开发管理工具,Excel 导入导出都是不可或缺的功能。本文聚焦整合 EasyExcel 与 Spring Boot,从依赖引入、实体类注解设计,到批量导入数据库、数据导出为 Excel 文件,贴合实际开发场景,代码可直接复用。
一、前置准备:明确实战场景与环境搭建
本次实战以「用户数据管理」为核心场景(适配常见业务需求),实现两个核心功能:① 上传 Excel 文件,批量导入用户数据到数据库;② 查询数据库用户数据,导出为 Excel 文件。环境基于 Spring Boot + EasyExcel + MyBatis-Plus + MySQL,简化复杂配置,聚焦核心功能落地。
1.1 核心依赖引入
重点引入 EasyExcel 核心依赖(轻量高效,避免 POI 的内存溢出问题),搭配 MyBatis-Plus 操作数据库,无需额外引入过多组件:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
1.2 核心配置(application.yml)
配置数据库连接、EasyExcel 相关优化(可选)、文件上传限制,集中管理,便于后续部署修改:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_easyexcel?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: 123456
servlet:
multipart:
enabled: true
max-file-size: 50MB
max-request-size: 100MB
mybatis-plus:
mapper-locations: classpath:mybatis/mapper/**/*.xml
type-aliases-package: com.example.demo.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
easyexcel:
reader:
batch-size: 100
head-row-number: 1
writer:
auto-close-stream: true
1.3 数据库表设计(用户表)
设计简单的用户表,用于存储 Excel 导入的数据、提供导出数据源,字段与 Excel 表头一一对应,适配数据管理需求:
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户主键',
`username` varchar(50) NOT NULL COMMENT '用户名(唯一)',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号',
`role` varchar(20) NOT NULL COMMENT '用户角色(admin/普通用户)',
`status` tinyint(1) DEFAULT 1 COMMENT '状态(1:正常,0:禁用)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';
二、核心实战一:实体类注解设计(EasyExcel 核心)
EasyExcel 的核心优势的是'注解驱动',无需手动解析 Excel 单元格,通过注解将实体类与 Excel 表头绑定,同时关联数据库实体,实现'Excel 数据→实体类→数据库'的无缝映射,是导入导出的基础。
我们需要创建两个关联实体(可复用一个,按需区分):① 数据库实体(SysUser):映射数据库表;② Excel 解析实体(ExcelUserDTO):映射 Excel 表头,通过 EasyExcel 注解配置。
2.1 数据库实体(SysUser)
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_user")
public class SysUser {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String phone;
private String role;
private Integer status = 1;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
2.2 Excel 解析实体(ExcelUserDTO)
核心是 EasyExcel 注解,通过@ExcelProperty 绑定 Excel 表头,明确列索引和表头名称,同时可添加数据校验、格式转换注解,适配 Excel 导入导出需求:
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.NumberFormat;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class ExcelUserDTO {
@ExcelIgnore
private Long id;
@ExcelProperty(value = "用户名", index = 0)
@NotBlank(message = "用户名不能为空")
private String username;
@ExcelProperty(value = "手机号", index = 1)
@NumberFormat("#")
private String phone;
@ExcelProperty(value = "角色", index = 2)
@NotBlank(message = "角色不能为空")
private String role;
@ExcelIgnore
private Integer status;
@ExcelIgnore
private LocalDateTime createTime;
}
- Excel 表头名称必须与@ExcelProperty 的 value 完全一致(大小写、空格都要匹配),否则无法解析;
- 无需导入/导出的字段,添加@ExcelIgnore 注解,避免冗余;
- 导入时的数据校验(如@NotBlank),可提前拦截非法数据,减少数据库异常。
三、核心实战二:Excel 批量导入数据库(核心功能)
Excel 导入的核心逻辑:前端上传 Excel 文件 → 后端通过 EasyExcel 监听器解析 Excel 数据 → 数据校验 → 批量存入数据库,全程无冗余代码,提升效率。
3.1 自定义 EasyExcel 监听器(核心)
EasyExcel 采用'监听器模式'解析 Excel,自定义监听器处理每一行解析后的数据,实现数据校验、批量入库(避免频繁操作数据库,提升性能):
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.example.demo.dto.ExcelUserDTO;
import com.example.demo.entity.SysUser;
import com.example.demo.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class ExcelUserImportListener extends AnalysisEventListener<ExcelUserDTO> {
private static final int BATCH_SIZE = 100;
private List<ExcelUserDTO> userList = new ArrayList<>();
private final SysUserService sysUserService;
private final Validator validator;
public ExcelUserImportListener(SysUserService sysUserService) {
this.sysUserService = sysUserService;
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
this.validator = factory.getValidator();
}
@Override
public void invoke(ExcelUserDTO data, AnalysisContext context) {
log.info("解析 Excel 数据:{}", data);
var violations = validator.validate(data);
if (!violations.isEmpty()) {
throw new RuntimeException("第" + (context.readRowHolder().getRowIndex() + 1) + "行数据非法:" + violations.iterator().next().getMessage());
}
userList.add(data);
if (userList.size() >= BATCH_SIZE) {
batchInsert();
userList.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if (!CollectionUtils.isEmpty(userList)) {
batchInsert();
userList.clear();
}
log.info("Excel 数据解析完成,已全部导入数据库");
}
private void batchInsert() {
List<SysUser> sysUsers = userList.stream().map(dto -> {
SysUser sysUser = new SysUser();
BeanUtils.copyProperties(dto, sysUser);
return sysUser;
}).toList();
sysUserService.saveBatch(sysUsers);
log.info("批量插入{}条用户数据到数据库", sysUsers.size());
}
}
3.2 Mapper 与 Service 层实现(批量入库)
基于 MyBatis-Plus 简化 CRUD 操作,无需手动编写 SQL,聚焦核心导入功能:
3.2.1 SysUserMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {}
3.2.2 SysUserService
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.SysUser;
import com.example.demo.mapper.SysUserMapper;
import org.springframework.stereotype.Service;
@Service
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
}
3.3 导入接口设计(Controller)
提供 Excel 上传接口,接收前端传入的 Excel 文件,调用监听器解析并批量入库,返回导入结果(成功条数、失败原因),满足前端调用需求:
import com.alibaba.excel.EasyExcel;
import com.example.demo.dto.ExcelUserDTO;
import com.example.demo.listener.ExcelUserImportListener;
import com.example.demo.service.SysUserService;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/excel")
public class ExcelImportExportController {
@Autowired
private SysUserService sysUserService;
@PostMapping("/import")
public ResponseEntity<Map<String, Object>> importExcel(@RequestParam("file") MultipartFile file) {
Map<String, Object> result = new HashMap<>();
try {
if (file.isEmpty()) {
result.put("code", 400);
result.put("message", "请选择要导入的 Excel 文件");
return ResponseEntity.badRequest().body(result);
}
String suffix = FilenameUtils.getExtension(file.getOriginalFilename()).toLowerCase();
if (!"xlsx".equals(suffix) && !"xls".equals(suffix)) {
result.put("code", 400);
result.put("message", "文件格式不合法,仅支持.xlsx、.xls 格式");
return ResponseEntity.badRequest().body(result);
}
ExcelUserImportListener listener = new ExcelUserImportListener(sysUserService);
EasyExcel.read(file.getInputStream(), ExcelUserDTO.class, listener).sheet()
.headRowNumber(1)
.doRead();
result.put("code", 200);
result.put("message", "Excel 导入成功");
return ResponseEntity.ok(result);
} catch (Exception e) {
result.put("code", 500);
result.put("message", "Excel 导入失败:" + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
}
3.4 导入测试(Postman)
{"code":200,"message":"Excel 导入成功"}
- 验证:查看数据库 sys_user 表,可看到 Excel 中的数据已批量导入,状态自动填充为 1,创建时间自动生成。
四、核心实战三:数据导出为 Excel 文件(核心功能)
Excel 导出的核心逻辑:查询数据库中的数据 → 转换为 Excel 解析实体(ExcelUserDTO) → 通过 EasyExcel 生成 Excel 文件 → 响应给前端,支持浏览器直接下载,满足日常使用需求。
4.1 导出接口设计(Controller)
无需额外编写监听器,直接调用 EasyExcel 的 write 方法,将数据库数据导出为 Excel,支持自定义 Excel 文件名、工作表名称,适配不同场景:
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.stream.Collectors;
@GetMapping("/export")
public void exportExcel(HttpServletResponse response) throws IOException {
try {
List<SysUser> sysUserList = sysUserService.list();
if (sysUserList.isEmpty()) {
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\":400,\"message\":\"暂无数据可导出\"}");
return;
}
List<ExcelUserDTO> excelUserList = sysUserList.stream().map(sysUser -> {
ExcelUserDTO dto = new ExcelUserDTO();
BeanUtils.copyProperties(sysUser, dto);
return dto;
}).collect(Collectors.toList());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("用户数据导出_" + System.currentTimeMillis(), "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), ExcelUserDTO.class).sheet("用户列表")
.doWrite(excelUserList);
} catch (Exception e) {
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\":500,\"message\":\"Excel 导出失败:" + e.getMessage() + "\"}");
}
}
4.2 导出测试(浏览器/Postman)
- 方式一(浏览器):直接访问接口地址:http://localhost:8080/excel/export,浏览器会自动下载 Excel 文件;
- 方式二(Postman):发送 GET 请求,接口地址同上,点击'Save Response' → 'Save to a file',保存 Excel 文件;
- 验证:打开下载的 Excel 文件,可看到数据库中的用户数据已完整导出,表头与 ExcelUserDTO 注解配置一致,无冗余字段。
- 响应头配置必须正确,否则浏览器可能无法识别 Excel 文件,导致下载后无法打开;
- 中文文件名需用 URLEncoder.encode 处理,避免中文乱码;
- 导出大数据量时,可使用 EasyExcel 的分批查询 + 分批写入,避免内存溢出。
五、优化建议(可选,提升体验)
为了提升系统专业性,补充 2 个实用优化点,可直接集成到代码中:
5.1 导入失败回滚(避免部分数据导入成功)
在 Service 层添加事务注解,当导入过程中出现异常时,自动回滚所有已插入的数据,保证数据一致性:
import org.springframework.transaction.annotation.Transactional;
@Service
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
@Transactional(rollbackFor = Exception.class)
public boolean saveBatch(List<SysUser> entityList) {
return super.saveBatch(entityList);
}
}
5.2 导出数据筛选(适配实际需求)
修改导出接口,支持按条件筛选导出(如按角色、状态筛选),让系统更灵活:
@GetMapping("/export/filter")
public void exportExcelByFilter(@RequestParam(required = false) String role, // 按角色筛选
@RequestParam(required = false) Integer status, // 按状态筛选
HttpServletResponse response) throws IOException {
try {
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
if (role != null && !role.isEmpty()) {
queryWrapper.eq("role", role);
}
if (status != null) {
queryWrapper.eq("status", status);
}
List<SysUser> sysUserList = sysUserService.list(queryWrapper);
} catch (Exception e) {
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\":500,\"message\":\"Excel 导出失败:" + e.getMessage() + "\"}");
}
}
六、常见问题与解决方案
解决方案:确保@ExcelProperty 的 value 与 Excel 表头完全一致(大小写、空格、特殊字符都要匹配),或通过 index 指定列索引(更稳妥)。
解决方案:① 调整监听器中的 BATCH_SIZE(如 100、200),减少单次内存占用;② 开启 EasyExcel 的分批读取,通过.headRowNumber(1).batchRead(100) 配置;③ 避免在监听器中存储过多数据。
解决方案:在 ExcelUserDTO 的 phone 字段添加@NumberFormat("#") 注解,或在 Excel 中将手机号列设置为'文本格式'。
解决方案:① 检查响应头配置是否正确(Content-Type 是否为 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet);② 确保 EasyExcel 的 write 方法正常执行,无异常中断;③ 避免在导出过程中关闭响应流。
Excel 导入时,提示'监听器中 Service 为 null'?
解决方案:EasyExcel 监听器不能被 Spring 自动注入(生命周期问题),需通过构造方法手动传入 Service,如 new ExcelUserImportListener(sysUserService)。
七、总结
本文承接文件上传的基础,完整落地了 Spring Boot + EasyExcel 的 Excel 导入导出核心功能,贴合开发需求,代码可直接复用,核心要点总结:
- 依赖引入:仅需引入 EasyExcel 核心依赖,搭配 MyBatis-Plus,简化开发,轻量高效;
- 实体设计:通过 EasyExcel 注解绑定 Excel 表头,关联数据库实体,实现'Excel→实体→数据库'无缝映射;
- 批量导入:自定义监听器处理解析、校验、批量入库,事务回滚保证数据一致性;
- 数据导出:配置响应头,直接将数据库数据导出为 Excel,支持浏览器下载和条件筛选。
掌握本文内容后,你可以直接将这些代码集成到自己的项目中,快速实现 Excel 导入导出核心功能,满足用户数据管理需求。后续可进一步扩展:模板下载(提供 Excel 导入模板)、导入导出进度显示、异常日志记录等,提升系统易用性。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online