Excel處理基礎知識
1.1 為什么需要在應用中處理Excel文件?
在企業應用開發中,Excel文件處理是一個非常常見的需求,主要用于以下場景:
數據導入:允許用戶通過Excel上傳批量數據到系統
數據導出:將系統數據導出為Excel供用戶下載分析
報表生成:生成復雜的報表并格式化為Excel
數據交換:作為不同系統間交換數據的媒介
批量數據處理:處理大量結構化數據
1.2 Java中的Excel處理庫介紹
Java中處理Excel文件的主要庫有以下幾種:
1.2.1 Apache POI
Apache POI是Java中使用最廣泛的Excel處理庫,提供了全面的API來創建、讀取和修改Office文檔。
優點:
功能全面,支持Excel所有功能
支持.xls (HSSF - Excel 97-2003)和.xlsx (XSSF - Excel 2007+)格式
社區活躍,文檔豐富
支持公式計算、圖表、合并單元格等高級功能
缺點:
API相對復雜
處理大文件時內存消耗大(尤其是XSSF)
1.2.2 EasyExcel
EasyExcel是阿里巴巴開源的Excel處理庫,基于POI,但做了大量優化。
優點:
內存占用低,使用SAX模式讀取,避免OOM
API簡單易用,注解驅動
讀寫速度快
適合處理大型Excel文件
缺點:
功能不如POI全面
靈活性相對較低
1.2.3 JExcel
JExcel是另一個處理Excel的Java庫。
優點:
API較簡單
速度較快
缺點:
僅支持舊版Excel (.xls)格式
不再積極維護
功能有限
1.2.4 Apache POI SXSSF
SXSSF是POI提供的一種流式處理模式,專為處理大型Excel文件設計。
優點:
大大降低內存占用
適合生成大型Excel文件
缺點:
僅支持寫入操作,不支持讀取
功能比XSSF受限
1.3 Spring Boot中集成Excel處理
Spring Boot本身不提供Excel處理功能,但可以輕松集成上述各種Excel處理庫。本指南將主要介紹:
如何在Spring Boot項目中集成Apache POI和EasyExcel
如何實現Excel導入導出的常見功能
如何處理常見問題和優化性能
Spring Boot中Excel處理完全指南:從基礎到高級實踐
在數據驅動的現代應用中,Excel處理能力是后端開發的必備技能。本文全面解析Spring Boot中Excel操作的12種核心技術方案。
一、基礎方案:Apache POI原生集成
1. 添加依賴
<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.3</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.3</version>
</dependency>
2. 基礎讀寫操作
// 寫入Excel
public void writeExcel(List<User> users, String filePath) throws IOException {try (Workbook workbook = new XSSFWorkbook()) {Sheet sheet = workbook.createSheet("用戶表");// 創建表頭Row headerRow = sheet.createRow(0);headerRow.createCell(0).setCellValue("ID");headerRow.createCell(1).setCellValue("姓名");headerRow.createCell(2).setCellValue("年齡");// 填充數據int rowNum = 1;for (User user : users) {Row row = sheet.createRow(rowNum++);row.createCell(0).setCellValue(user.getId());row.createCell(1).setCellValue(user.getName());row.createCell(2).setCellValue(user.getAge());}// 自動調整列寬for (int i = 0; i < 3; i++) {sheet.autoSizeColumn(i);}try (FileOutputStream fos = new FileOutputStream(filePath)) {workbook.write(fos);}}
}// 讀取Excel
public List<User> readExcel(String filePath) throws IOException {List<User> users = new ArrayList<>();try (Workbook workbook = new XSSFWorkbook(new File(filePath))) {Sheet sheet = workbook.getSheetAt(0);Iterator<Row> rowIterator = sheet.iterator();// 跳過表頭if (rowIterator.hasNext()) rowIterator.next();while (rowIterator.hasNext()) {Row row = rowIterator.next();User user = new User();user.setId((long) row.getCell(0).getNumericCellValue());user.setName(row.getCell(1).getStringCellValue());user.setAge((int) row.getCell(2).getNumericCellValue());users.add(user);}}return users;
}
二、高效方案:EasyExcel處理百萬級數據
1. 添加依賴
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.2</version>
</dependency>
2. 基于注解的實體映射
@Data
public class UserExcel {@ExcelProperty("ID")private Long id;@ExcelProperty("姓名")private String name;@ExcelProperty("年齡")private Integer age;@ExcelProperty(value = "創建時間", converter = LocalDateTimeConverter.class)private LocalDateTime createTime;
}// 自定義日期轉換器
public class LocalDateTimeConverter implements Converter<LocalDateTime> {@Overridepublic LocalDateTime convertToJavaData(ReadCellData<?> cellData) {return LocalDateTime.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));}
}
3. 百萬數據導出(避免OOM)
@GetMapping("/exportBigData")
public void exportBigData(HttpServletResponse response) {response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");String fileName = URLEncoder.encode("百萬用戶數據", "UTF-8");response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");EasyExcel.write(response.getOutputStream(), UserExcel.class).sheet("用戶數據").doWrite(() -> {// 分頁查詢數據(每次查詢10000條)int page = 0;while (true) {Pageable pageable = PageRequest.of(page, 10000);Page<User> userPage = userRepository.findAll(pageable);if (userPage.isEmpty()) break;// 轉換數據List<UserExcel> data = userPage.getContent().stream().map(user -> {UserExcel excel = new UserExcel();BeanUtils.copyProperties(user, excel);return excel;}).collect(Collectors.toList());page++;return data;}return null;});
}
三、Web集成方案:文件上傳下載
1. 文件上傳解析
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {if (file.isEmpty()) {return "請選擇文件";}try {List<User> users = EasyExcel.read(file.getInputStream()).head(User.class).sheet().doReadSync();userRepository.saveAll(users);return "導入成功,共處理數據: " + users.size() + "條";} catch (IOException e) {return "文件處理失敗: " + e.getMessage();}
}
2. 模板下載
@GetMapping("/downloadTemplate")
public void downloadTemplate(HttpServletResponse response) throws IOException {response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");String fileName = URLEncoder.encode("用戶導入模板", "UTF-8");response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");// 創建模板數據(帶下拉菜單)List<String> headers = Arrays.asList("ID", "姓名", "年齡", "部門");List<ExcelSelect> selects = Collections.singletonList(new ExcelSelect(3, Arrays.asList("研發部", "市場部", "財務部")));EasyExcel.write(response.getOutputStream()).head(headers).registerWriteHandler(new DropDownHandler(selects)) // 添加下拉菜單.sheet("模板").doWrite(Collections.emptyList());
}// 下拉菜單處理器
public class DropDownHandler implements SheetWriteHandler {private final List<ExcelSelect> selects;public DropDownHandler(List<ExcelSelect> selects) {this.selects = selects;}@Overridepublic void afterSheetCreate(WriteWorkbookContext context) {Sheet sheet = context.getWriteSheetHolder().getSheet();DataValidationHelper helper = sheet.getDataValidationHelper();for (ExcelSelect select : selects) {// 創建下拉數據源String range = "D2:D1048576"; // 示例:D列所有行CellRangeAddressList addressList = new CellRangeAddressList(1, 65535, select.getColIndex(), select.getColIndex());// 設置下拉選項DataValidationConstraint constraint = helper.createExplicitListConstraint(select.getOptions().toArray(new String[0]));DataValidation validation = helper.createValidation(constraint, addressList);// 應用驗證sheet.addValidationData(validation);}}
}
四、高級技巧:動態模板與復雜報表
1. 動態表頭生成
public void exportDynamicColumns(HttpServletResponse response, List<String> columns) {// 動態構建表頭List<List<String>> head = new ArrayList<>();for (String col : columns) {head.add(Collections.singletonList(col));}// 動態數據填充List<List<Object>> data = new ArrayList<>();for (User user : userRepository.findAll()) {List<Object> row = new ArrayList<>();for (String col : columns) {switch (col) {case "ID": row.add(user.getId()); break;case "姓名": row.add(user.getName()); break;case "部門": row.add(user.getDepartment()); break;// 更多字段處理...}}data.add(row);}// 導出EasyExcel.write(response.getOutputStream()).head(head).sheet("動態報表").doWrite(data);
}
2. 復雜報表合并單元格
public void exportComplexReport() {String fileName = "復雜報表.xlsx";try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {WriteSheet writeSheet = EasyExcel.writerSheet("部門統計").build();// 設置合并策略excelWriter.registerWriteHandler(new CellMergeStrategy(Arrays.asList(new MergeRange(0, 0, 0, 3), // 第一行合并4列new MergeRange(1, 1, 0, 0), // 第二行第一列new MergeRange(1, 1, 1, 3) // 第二行后三列合并)));// 構建表頭List<List<String>> head = Arrays.asList(Collections.singletonList("公司年度報表"),Arrays.asList("部門", "Q1", "Q2", "Q3", "Q4"));// 填充數據List<List<Object>> data = new ArrayList<>();data.add(Arrays.asList("研發部", 150, 180, 200, 220));data.add(Arrays.asList("市場部", 120, 150, 180, 210));excelWriter.write(head, writeSheet);excelWriter.write(data, writeSheet);}
}// 自定義合并策略
public class CellMergeStrategy implements RowWriteHandler {private final List<MergeRange> mergeRanges;@Overridepublic void afterRowDispose(WriteSheetContext context) {Sheet sheet = context.getWriteSheetHolder().getSheet();for (MergeRange range : mergeRanges) {sheet.addMergedRegion(new CellRangeAddress(range.getFirstRow(), range.getLastRow(),range.getFirstCol(), range.getLastCol()));}}
}
五、性能優化與問題解決方案
1. 內存溢出解決方案
// 使用SXSSFWorkbook處理大數據
try (Workbook workbook = new SXSSFWorkbook(100)) { // 保持100行在內存中Sheet sheet = workbook.createSheet();// 寫入數據for (int i = 0; i < 1000000; i++) {Row row = sheet.createRow(i);// 填充單元格...// 定期刷新磁盤if (i % 1000 == 0) {((SXSSFSheet) sheet).flushRows(100);}}
}
2. 常見問題處理方案
問題類型 | 解決方案 |
---|---|
日期格式不一致 | 自定義Converter統一處理日期格式 |
大文件導出超時 | 分頁查詢+異步導出,前端輪詢結果 |
特殊字符(emoji)亂碼 | 使用String sanitized = StringEscapeUtils.escapeHtml4(rawString) 處理 |
公式計算 | 使用FormulaEvaluator 預處理公式結果 |
樣式丟失 | 定義CellStyle 緩存池避免重復創建 |
下拉菜單動態數據源 | 使用DVConstraint.createFormulaListConstraint 引用隱藏Sheet數據 |
3. Excel操作最佳實踐
-
讀寫分離原則:
- 讀操作:使用EasyExcel的監聽器模式(
ReadListener
) - 寫操作:大數據用分頁寫入,小數據用全量寫入
- 讀操作:使用EasyExcel的監聽器模式(
-
資源管理三要素:
try (Workbook workbook = ...;InputStream is = ...) {// 操作邏輯 } // 自動關閉資源
-
樣式復用技巧:
private CellStyle createHeaderStyle(Workbook workbook) {CellStyle style = workbook.createCellStyle();Font font = workbook.createFont();font.setBold(true);style.setFont(font);style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());style.setFillPattern(FillPatternType.SOLID_FOREGROUND);return style; }
六、擴展方案:集成第三方庫
1. JExcelAPI(適合.xls格式)
<dependency><groupId>net.sourceforge.jexcelapi</groupId><artifactId>jxl</artifactId><version>2.6.12</version>
</dependency>
2. Apache POI + JXLS模板引擎
<dependency><groupId>org.jxls</groupId><artifactId>jxls-poi</artifactId><version>2.12.0</version>
</dependency>
<dependency><groupId>org.jxls</groupId><artifactId>jxls-jexcel</artifactId><version>1.0.9</version>
</dependency>
模板示例(template.xlsx):
| 姓名 | 年齡 | 部門 |
|------|------|----------|
| ${u.name} | ${u.age} | ${u.department} |
Java代碼:
public void exportWithTemplate(HttpServletResponse response) throws IOException {try (InputStream is = getClass().getResourceAsStream("/template.xlsx");OutputStream os = response.getOutputStream()) {Context context = new Context();context.putVar("users", userRepository.findAll());JxlsHelper.getInstance().processTemplate(is, os, context);}
}
最佳實踐總結:對于中小數據量(<10萬行)推薦使用EasyExcel+注解方案;大數據場景采用POI的SXSSF模式;需要復雜報表時選擇JXLS模板引擎。始終記住:在Spring Boot中處理Excel,資源關閉和內存管理是成敗關鍵!
擴展學習:
- POI官方文檔
- EasyExcel GitHub
- JXLS模板語法