Spring项目中使用EasyExcel实现Excel 多 Sheet 导入导出功能(完整版)
Excel 多 Sheet 导入导出功能完整实现指南
一、环境依赖
1. Maven 依赖
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
二、核心工具类封装
1. Excel 导出工具类
public class ExcelExportUtils {
/**
* 动态导出多 Sheet Excel(无需模板)
* @param sheetDataMap key: Sheet名称, value: 数据列表(需保证列表元素类型一致)
*/
public static void exportDynamicMultiSheet(
Map<String, List<Object>> sheetDataMap,
String filename,
HttpServletResponse response
) throws IOException {
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build()) {
WriteCellStyle headerStyle = createHeaderStyle();
for (Map.Entry<String, List<Object>> entry : sheetDataMap.entrySet()) {
String sheetName = entry.getKey();
List<Object> dataList = entry.getValue();
if (CollectionUtils.isEmpty(dataList)) {
throw new IllegalArgumentException("Sheet [" + sheetName + "] 数据不能为空");
}
Class<?> dtoClass = dataList.get(0).getClass();
WriteSheet writeSheet = EasyExcel.writerSheet(sheetName)
.head(dtoClass)
.headStyle(headerStyle)
.build();
excelWriter.write(dataList, writeSheet);
}
}
}
private static WriteCellStyle createHeaderStyle() {
WriteCellStyle style = new WriteCellStyle();
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setHorizontalAlignment(HorizontalAlignment.CENTER);
WriteFont font = new WriteFont();
font.setBold(true);
font.setFontHeightInPoints((short) 12);
style.setWriteFont(font);
return style;
}
}
2. Excel 导入工具类
public class ExcelImportUtils {
/**
* 导入多 Sheet Excel
* @param file 上传的文件
* @param sheetMap key: Sheet名称, value: 该Sheet对应的DTO类
*/
public static Map<String, List<?>> importMultiSheet(
MultipartFile file,
Map<String, Class<?>> sheetMap
) throws IOException {
Map<String, List<?>> resultMap = new LinkedHashMap<>();
try (ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build()) {
for (Map.Entry<String, Class<?>> entry : sheetMap.entrySet()) {
String sheetName = entry.getKey();
Class<?> dtoClass = entry.getValue();
GenericSheetListener<Object> listener = new GenericSheetListener<>();
ReadSheet readSheet = EasyExcel.readSheet(sheetName)
.head(dtoClass)
.registerReadListener(listener)
.build();
excelReader.read(readSheet);
resultMap.put(sheetName, listener.getDataList());
}
}
return resultMap;
}
/** 通用数据监听器 */
private static class GenericSheetListener<T> implements ReadListener<T> {
private final List<T> dataList = new ArrayList<>();
@Override
public void invoke(T data, AnalysisContext context) {
dataList.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 可添加日志或后处理逻辑
}
public List<T> getDataList() {
return dataList;
}
}
}
三、DTO 类定义
1. 用户信息 DTO(对应 Sheet1)
public class UserDTO {
@ExcelProperty(value = "姓名", index = 0)
private String name;
@ExcelProperty(value = "年龄", index = 1)
private Integer age;
// 必须有无参构造方法
public UserDTO() {}
// Getters & Setters
}
2. 产品信息 DTO(对应 Sheet2)
public class ProductDTO {
@ExcelProperty(value = "产品ID", index = 0)
private Long id;
@ExcelProperty(value = "产品名称", index = 1)
private String name;
// 必须有无参构造方法
public ProductDTO() {}
// Getters & Setters
}
四、Service 层实现
@Service
public class ExcelService {
/**
* 导出 Excel
*/
public void exportExcel(HttpServletResponse response) {
Map<String, List<Object>> sheetDataMap = new LinkedHashMap<>();
// Sheet1: 用户数据
List<Object> users = Arrays.asList(
new UserDTO("张三", 25),
new UserDTO("李四", 30)
);
sheetDataMap.put("用户信息", users);
// Sheet2: 产品数据
List<Object> products = Arrays.asList(
new ProductDTO(1L, "笔记本电脑"),
new ProductDTO(2L, "智能手机")
);
sheetDataMap.put("产品信息", products);
// 调用工具类
ExcelExportUtils.exportDynamicMultiSheet(sheetDataMap, "export_data.xlsx", response);
}
/**
* 导入 Excel
*/
public void importExcel(MultipartFile file) {
// 定义 Sheet 映射关系
Map<String, Class<?>> sheetMap = new LinkedHashMap<>();
sheetMap.put("用户信息", UserDTO.class);
sheetMap.put("产品信息", ProductDTO.class);
// 解析数据
Map<String, List<?>> dataMap = ExcelImportUtils.importMultiSheet(file, sheetMap);
// 处理业务逻辑
List<UserDTO> users = (List<UserDTO>) dataMap.get("用户信息");
List<ProductDTO> products = (List<ProductDTO>) dataMap.get("产品信息");
// 保存到数据库...
}
}
五、Controller 层实现
@RestController
@RequestMapping("/api/excel")
public class ExcelController {
@Autowired
private ExcelService excelService;
/**
* 导出 Excel
*/
@GetMapping("/export")
public void exportExcel(HttpServletResponse response) {
excelService.exportExcel(response);
}
/**
* 导入 Excel
*/
@PostMapping("/import")
public ResponseEntity<String> importExcel(@RequestParam("file") MultipartFile file) {
try {
excelService.importExcel(file);
return ResponseEntity.ok("文件导入成功");
} catch (Exception e) {
return ResponseEntity.status(500).body("导入失败: " + e.getMessage());
}
}
}
六、关键概念解释
1. 监听器(ReadListener
)
- 作用:
在 Excel 导入过程中,逐行读取数据并处理,避免一次性加载全量数据到内存。 - 核心方法:
invoke(T data, AnalysisContext context)
:每读取一行数据时触发。doAfterAllAnalysed(AnalysisContext context)
:当前 Sheet 解析完成后触发。
- 使用场景:
- 数据收集(存储到 List)。
- 数据校验(如字段非空检查)。
- 批量插入数据库(累积一定数量后批量操作)。
2. @ExcelProperty
注解
- 功能:定义 Java 字段与 Excel 列的映射关系。
- 参数:
value
:对应 Excel 列名。index
:指定列位置(从 0 开始)。
- 示例:
@ExcelProperty(value = "姓名", index = 0) private String name;
3. 样式配置(WriteCellStyle
)
- 用途:控制单元格样式(如字体、颜色、对齐方式)。
- 常用配置项:
setFillForegroundColor
: 背景色。setHorizontalAlignment
: 水平对齐。setWriteFont
: 字体配置(加粗、字号)。
七、常见问题
1. 类型转换异常
- 表现:
ClassCastException
。 - 原因:DTO 类字段类型与 Excel 单元格数据类型不匹配。
- 解决:使用
@DateTimeFormat
或自定义转换器。
2. 表头不匹配
- 表现:数据未正确映射到字段。
- 原因:
@ExcelProperty
的value
或index
配置错误。 - 解决:检查 DTO 类注解与 Excel 列名是否一致。
3. 内存溢出
- 表现:大文件解析时内存占用过高。
- 解决:确保使用监听器模式,避免一次性加载全部数据。
八、扩展功能
1. 自定义数据转换器
public class CustomConverter implements Converter<LocalDate> {
@Override
public LocalDate convertToJavaData(ReadConverterContext<?> context) {
return LocalDate.parse(context.getReadCellData().getStringValue(), DateTimeFormatter.ISO_DATE);
}
}
// 注册转换器
EasyExcel.read(inputStream)
.registerConverter(new CustomConverter())
.build();
2. 复杂表头合并
WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1")
.head(Collections.singletonList(Arrays.asList("主标题", "子标题1", "子标题2")))
.build();
九、总结
通过本方案可实现:
- 动态导出:无需模板,根据 DTO 类自动生成表头。
- 高效导入:逐行解析,内存占用低。
- 类型安全:通过泛型和注解保证数据一致性。
- 灵活扩展:支持自定义样式、转换器、校验逻辑。