概述
在企业级应用开发中,Excel 导入导出是常见需求。本文将详细介绍 EasyExcel 的使用方法,提供完善的代码示例,并对比其他主流解决方案。
一、EasyExcel 简介
EasyExcel 是阿里巴巴开源的一个基于 Java 的简单、省内存的读写 Excel 工具。相比传统 POI,它具有以下优势:
- 内存优化:采用逐行解析模式,极大减少内存占用
- API 简洁:注解驱动,开发效率高
- 功能丰富:支持复杂表头、样式定制、数据校验等
二、环境准备
2.1 Maven 依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.3</version>
</dependency>
<!-- Spring Boot Web 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
三、核心工具类封装
3.1 Excel 导入工具类
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.read.listener.ReadListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
/**
* Excel 导入工具类
*/
@Slf4j
public class ExcelImportUtil {
private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
/**
* 导入 Excel 数据(分批处理,适合大数据量)
*
* @param file 上传的文件
* @param clazz 数据模型类
* @param batchSize 每批处理的数据量
* @param batchConsumer 批次消费函数
* @param <T> 数据类型
*/
public static <T> void importExcelInBatches(MultipartFile file, Class<T> clazz,
int batchSize, Consumer<List<T>> batchConsumer) {
try {
EasyExcel.read(file.getInputStream(), clazz, new PageReadListener<T>(dataList -> {
// 数据校验
List<T> validData = validateData(dataList);
if (!validData.isEmpty()) {
batchConsumer.accept(validData);
}
}, batchSize)).sheet().doRead();
} catch (IOException e) {
throw new RuntimeException("读取 Excel 文件失败", e);
}
}
/**
* 导入 Excel 数据(返回全部数据,适合小数据量)
*/
public static <T> List<T> importExcel(MultipartFile file, Class<T> clazz, String sheetName) {
List<T> dataList = new ArrayList<>();
try {
EasyExcel.read(file.getInputStream(), clazz, new ReadListener<T>() {
@Override
public void invoke(T data, AnalysisContext context) {
// 单条数据校验
Set<ConstraintViolation<T>> violations = validator.validate(data);
if (violations.isEmpty()) {
dataList.add(data);
} else {
log.warn("数据校验失败: {}", violations);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
log.info("Excel 解析完成,共读取 {} 条数据", dataList.size());
}
}).sheet(sheetName).doRead();
} catch (IOException e) {
throw new RuntimeException("读取 Excel 文件失败", e);
}
return dataList;
}
/**
* 数据校验
*/
private static <T> List<T> validateData(List<T> dataList) {
List<T> validData = new ArrayList<>();
for (T data : dataList) {
Set<ConstraintViolation<T>> violations = validator.validate(data);
if (violations.isEmpty()) {
validData.add(data);
} else {
log.warn("数据校验失败,跳过该行数据: {}", violations);
}
}
return validData;
}
}
3.2 Excel 导出工具类
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* Excel 导出工具类
*/
public class ExcelExportUtil {
/**
* 导出 Excel 文件(基础版本)
*/
public static <T> void exportExcel(HttpServletResponse response, String fileName,
String sheetName, Class<T> clazz, List<T> data) {
try {
setupResponse(response, fileName);
EasyExcel.write(response.getOutputStream(), clazz)
.sheet(sheetName)
.doWrite(data);
} catch (IOException e) {
throw new RuntimeException("导出 Excel 文件失败", e);
}
}
/**
* 导出 Excel 文件(带样式版本)
*/
public static <T> void exportExcelWithStyle(HttpServletResponse response, String fileName,
String sheetName, Class<T> clazz, List<T> data) {
try {
setupResponse(response, fileName);
// 设置表头样式
WriteCellStyle headStyle = new WriteCellStyle();
headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 设置内容样式
WriteCellStyle contentStyle = new WriteCellStyle();
contentStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
HorizontalCellStyleStrategy styleStrategy =
new HorizontalCellStyleStrategy(headStyle, contentStyle);
EasyExcel.write(response.getOutputStream(), clazz)
.registerWriteHandler(styleStrategy)
.sheet(sheetName)
.doWrite(data);
} catch (IOException e) {
throw new RuntimeException("导出 Excel 文件失败", e);
}
}
/**
* 设置 HTTP 响应头
*/
private static void setupResponse(HttpServletResponse response, String fileName) {
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
.replaceAll("\+", "%20");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
ContentDisposition.attachment()
.filename(encodedFileName + ".xlsx")
.build()
.toString());
}
/**
* 大数据量导出(分页查询方式)
*/
public static <T> void exportLargeData(HttpServletResponse response, String fileName,
String sheetName, Class<T> clazz,
DataSupplier<T> dataSupplier) {
try {
setupResponse(response, fileName);
EasyExcel.write(response.getOutputStream(), clazz)
.sheet(sheetName)
.doWrite(() -> dataSupplier.getNextBatch());
} catch (IOException e) {
throw new RuntimeException("导出 Excel 文件失败", e);
}
}
/**
* 数据供应接口(用于大数据量导出)
*/
@FunctionalInterface
public interface DataSupplier<T> {
List<T> getNextBatch();
}
}
四、数据模型定义
4.1 基础数据模型
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Date;
/**
* 用户信息导入导出模型
*/
@Data
@HeadRowHeight(20) // 表头行高
@ContentRowHeight(15) // 内容行高
public class UserInfoModel {
@ExcelProperty(value = "用户ID", index = 0)
@ColumnWidth(15)
private Long userId;
@ExcelProperty(value = "用户名", index = 1)
@ColumnWidth(20)
@NotBlank(message = "用户名不能为空")
private String username;
@ExcelProperty(value = "邮箱", index = 2)
@ColumnWidth(25)
@NotBlank(message = "邮箱不能为空")
private String email;
@ExcelProperty(value = "手机号", index = 3)
@ColumnWidth(15)
private String phone;
@ExcelProperty(value = "年龄", index = 4)
@ColumnWidth(10)
@NotNull(message = "年龄不能为空")
private Integer age;
@ExcelProperty(value = "工资", index = 5)
@ColumnWidth(15)
@NotNull(message = "工资不能为空")
private BigDecimal salary;
@ExcelProperty(value = "入职日期", index = 6)
@ColumnWidth(15)
@DateTimeFormat("yyyy-MM-dd")
@NotNull(message = "入职日期不能为空")
private Date hireDate;
@ExcelProperty(value = "状态", index = 7)
@ColumnWidth(10)
private String status;
/**
* 自定义转换:状态显示转换
*/
public String getStatus() {
if ("1".equals(status)) {
return "在职";
} else if ("0".equals(status)) {
return "离职";
}
return status;
}
}
4.2 复杂表头模型
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ContentFontStyle;
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
import lombok.Data;
import org.apache.poi.ss.usermodel.IndexedColors;
import java.math.BigDecimal;
/**
* 销售报表模型(复杂表头示例)
*/
@Data
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND,
fillForegroundColor = 44) // 天蓝色背景
@HeadFontStyle(fontHeightInPoints = 11, bold = true)
public class SalesReportModel {
// 第一级表头
@ExcelProperty(value = {"销售报表", "产品信息", "产品ID"}, index = 0)
private String productId;
@ExcelProperty(value = {"销售报表", "产品信息", "产品名称"}, index = 1)
private String productName;
// 第二级表头合并
@ExcelProperty(value = {"销售报表", "销售数据", "第一季度"}, index = 2)
private BigDecimal q1Sales;
@ExcelProperty(value = {"销售报表", "销售数据", "第二季度"}, index = 3)
private BigDecimal q2Sales;
@ExcelProperty(value = {"销售报表", "销售数据", "第三季度"}, index = 4)
private BigDecimal q3Sales;
@ExcelProperty(value = {"销售报表", "销售数据", "第四季度"}, index = 5)
private BigDecimal q4Sales;
@ExcelProperty(value = {"销售报表", "销售数据", "年度总计"}, index = 6)
@ContentFontStyle(bold = true, color = 10) // 红色字体
private BigDecimal totalSales;
@ExcelProperty(value = {"销售报表", "评价", "客户评分"}, index = 7)
private BigDecimal customerRating;
}
五、业务层实现
5.1 导入服务实现
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 用户信息导入服务
*/
@Slf4j
@Service
public class UserImportService {
private final UserRepository userRepository;
public UserImportService(UserRepository userRepository) {
this.userRepository = userRepository;
}
/**
* 导入用户数据(小数据量)
*/
public ImportResult importUsers(MultipartFile file) {
try {
List<UserInfoModel> userList = ExcelImportUtil.importExcel(file,
UserInfoModel.class, "用户信息");
int successCount = 0;
int failureCount = 0;
for (UserInfoModel userModel : userList) {
try {
// 数据转换和保存
UserEntity userEntity = convertToEntity(userModel);
userRepository.save(userEntity);
successCount++;
} catch (Exception e) {
log.error("保存用户数据失败: {}", userModel.getUsername(), e);
failureCount++;
}
}
return new ImportResult(successCount, failureCount,
"导入完成,成功:" + successCount + "条,失败:" + failureCount + "条");
} catch (Exception e) {
throw new RuntimeException("导入用户数据失败", e);
}
}
/**
* 批量导入用户数据(大数据量)
*/
public ImportResult importUsersInBatches(MultipartFile file, int batchSize) {
ImportResult result = new ImportResult(0, 0, "");
ExcelImportUtil.importExcelInBatches(file, UserInfoModel.class, batchSize, batch -> {
try {
List<UserEntity> entities = batch.stream()
.map(this::convertToEntity)
.toList();
userRepository.saveAll(entities);
result.setSuccessCount(result.getSuccessCount() + batch.size());
} catch (Exception e) {
log.error("批量保存用户数据失败", e);
result.setFailureCount(result.getFailureCount() + batch.size());
}
});
result.setMessage("批量导入完成,成功:" + result.getSuccessCount() +
"条,失败:" + result.getFailureCount() + "条");
return result;
}
private UserEntity convertToEntity(UserInfoModel model) {
UserEntity entity = new UserEntity();
entity.setUsername(model.getUsername());
entity.setEmail(model.getEmail());
entity.setPhone(model.getPhone());
entity.setAge(model.getAge());
entity.setSalary(model.getSalary());
entity.setHireDate(model.getHireDate());
return entity;
}
/**
* 导入结果类
*/
@Data
public static class ImportResult {
private int successCount;
private int failureCount;
private String message;
public ImportResult(int successCount, int failureCount, String message) {
this.successCount = successCount;
this.failureCount = failureCount;
this.message = message;
}
}
}
5.2 导出服务实现
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 用户信息导出服务
*/
@Slf4j
@Service
public class UserExportService {
private final UserRepository userRepository;
public UserExportService(UserRepository userRepository) {
this.userRepository = userRepository;
}
/**
* 导出所有用户数据
*/
public void exportAllUsers(HttpServletResponse response) {
try {
List<UserInfoModel> userList = userRepository.findAll()
.stream()
.map(this::convertToModel)
.toList();
ExcelExportUtil.exportExcelWithStyle(response, "用户信息导出",
"用户信息", UserInfoModel.class, userList);
} catch (Exception e) {
throw new RuntimeException("导出用户数据失败", e);
}
}
/**
* 大数据量导出(分页方式)
*/
public void exportLargeUserData(HttpServletResponse response) {
ExcelExportUtil.exportLargeData(response, "用户大数据导出",
"用户信息", UserInfoModel.class, new DataSupplier());
}
/**
* 自定义数据供应器
*/
private class DataSupplier implements ExcelExportUtil.DataSupplier<UserInfoModel> {
private int page = 0;
private final int size = 1000; // 每页1000条
@Override
public List<UserInfoModel> getNextBatch() {
Page<UserEntity> pageData = userRepository.findAll(PageRequest.of(page, size));
List<UserInfoModel> batch = pageData.getContent()
.stream()
.map(UserExportService.this::convertToModel)
.toList();
page++;
return batch;
}
}
private UserInfoModel convertToModel(UserEntity entity) {
UserInfoModel model = new UserInfoModel();
model.setUserId(entity.getId());
model.setUsername(entity.getUsername());
model.setEmail(entity.getEmail());
model.setPhone(entity.getPhone());
model.setAge(entity.getAge());
model.setSalary(entity.getSalary());
model.setHireDate(entity.getHireDate());
return model;
}
}
六、控制器层
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
/**
* Excel 导入导出控制器
*/
@RestController
@RequestMapping("/api/excel")
@RequiredArgsConstructor
public class ExcelController {
private final UserImportService userImportService;
private final UserExportService userExportService;
/**
* 导入用户数据
*/
@PostMapping("/import/users")
public ApiResult importUsers(@RequestParam("file") MultipartFile file) {
try {
UserImportService.ImportResult result = userImportService.importUsers(file);
return ApiResult.success(result);
} catch (Exception e) {
return ApiResult.error("导入失败: " + e.getMessage());
}
}
/**
* 批量导入用户数据
*/
@PostMapping("/import/users-batch")
public ApiResult importUsersBatch(@RequestParam("file") MultipartFile file,
@RequestParam(value = "batchSize", defaultValue = "1000") int batchSize) {
try {
UserImportService.ImportResult result = userImportService.importUsersInBatches(file, batchSize);
return ApiResult.success(result);
} catch (Exception e) {
return ApiResult.error("批量导入失败: " + e.getMessage());
}
}
/**
* 导出用户数据
*/
@GetMapping("/export/users")
public void exportUsers(HttpServletResponse response) {
try {
userExportService.exportAllUsers(response);
} catch (Exception e) {
throw new RuntimeException("导出失败", e);
}
}
/**
* 统一API响应格式
*/
@Data
public static class ApiResult {
private boolean success;
private String message;
private Object data;
public static ApiResult success(Object data) {
ApiResult result = new ApiResult();
result.success = true;
result.message = "操作成功";
result.data = data;
return result;
}
public static ApiResult error(String message) {
ApiResult result = new ApiResult();
result.success = false;
result.message = message;
return result;
}
}
}
七、其他 Excel 处理方案对比
7.1 Apache POI
优点:
- 功能最全面,支持所有 Excel 特性
- 社区活跃,文档丰富
- 支持老版本 Excel (.xls)
缺点:
- 内存消耗大,处理大文件容易 OOM
- API 相对复杂,学习曲线陡峭
- 代码量较大
适用场景:
- 需要处理复杂 Excel 格式
- 需要支持 .xls 格式
- 项目已经深度使用 POI
7.2 Hutool Excel
优点:
- API 简单易用,学习成本低
- 集成方便,依赖少
- 功能实用,满足日常需求
缺点:
- 功能相对简单,复杂场景支持不足
- 性能不如 EasyExcel
- 社区相对较小
适用场景:
- 简单 Excel 操作需求
- 快速开发项目
- 小数据量处理
7.3 综合对比表格
|
特性 |
EasyExcel |
Apache POI |
Hutool Excel |
|
内存占用 |
低 |
高 |
中等 |
|
性能 |
高 |
中等 |
中等 |
|
API 简洁性 |
高 |
低 |
高 |
|
功能全面性 |
中等 |
高 |
中等 |
|
学习曲线 |
低 |
高 |
低 |
|
社区支持 |
中等 |
高 |
中等 |
|
文档完善度 |
中等 |
高 |
中等 |
八、最佳实践提议
8.1 导入优化提议
- 数据校验:在导入前进行数据格式和业务规则校验
- 分批处理:大数据量采用分批处理,避免内存溢出
- 异常处理:完善的异常处理和错误信息反馈
- 进度监控:大文件导入提供进度监控功能
8.2 导出优化提议
- 分页查询:大数据量导出使用分页查询方式
- 样式优化:合理设置表格样式,提升用户体验
- 文件压缩:超大文件思考压缩处理
- 异步导出:长时间操作采用异步导出方式
8.3 安全思考
- 文件类型验证:严格验证上传文件类型
- 大小限制:设置合理的文件大小限制
- SQL 注入防护:避免通过 Excel 导入导致 SQL 注入
- 敏感信息过滤:导出时过滤敏感信息
总结
EasyExcel 是一个优秀的 Excel 处理工具,特别适合处理大数据量和性能敏感的场景。通过本文的完整示例和最佳实践,开发者可以快速掌握 EasyExcel 的使用方法,并根据实际需求选择合适的 Excel 处理方案。
在实际项目中,提议根据具体需求选择合适的技术方案,并结合本文提供的工具类和最佳实践,构建稳定高效的 Excel 导入导出功能。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...


