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<>();@Overridepublic void invoke(T data, AnalysisContext context) {dataList.add(data);}@Overridepublic 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 {@Autowiredprivate 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> {@Overridepublic 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 類自動生成表頭。
- 高效導入:逐行解析,內存占用低。
- 類型安全:通過泛型和注解保證數據一致性。
- 靈活擴展:支持自定義樣式、轉換器、校驗邏輯。