大家好,我是java秃兔。在日常开发中,Excel导入是高频需求,而多Sheet页导入更是常见场景——比如批量导入不同类型的数据(用户表、订单表)到系统,或解析包含多模块信息的Excel文件。今天就带大家从零开始,用Java实现多Sheet页Excel导入。要实现多Sheet页导入,核心是“遍历Sheet→读取每行数据→解析封装”,关键在于借助成熟的Excel解析工具简化操作(避免手动解析二进制文件)。
1. 工具选择:Apache POI
Java解析Excel主流工具是Apache POI,它支持.xls(Excel 97-2003,二进制格式)和.xlsx(Excel 2007+,XML格式)两种后缀,提供了对Sheet、Row、Cell的封装API,能快速操作Excel内容。
注意:.xls和.xlsx的POI API有差异,需分别处理(后文代码会兼容两种格式)。
2. 核心步骤
引入依赖:在项目中导入POI相关Jar包,Maven/Gradle均可配置。
创建文件流:读取目标Excel文件,生成输入流。
判断Excel版本:根据文件后缀或文件头信息,创建对应的工作簿(Workbook)对象(HSSFWorkbook对应.xls,XSSFWorkbook对应.xlsx)。
遍历所有Sheet:通过Workbook获取Sheet总数,循环遍历每个Sheet页。
读取Sheet数据:对每个Sheet,遍历有效行(跳过表头/空行),再遍历每行的有效单元格,获取单元格值。
数据封装与处理:将读取到的单元格值封装为Java实体类,或直接入库、业务处理。
关闭资源:关闭流和Workbook对象,避免资源泄露。
以下代码基于Spring Boot项目编写(普通Java项目可稍作修改),兼容.xls和.xlsx,包含实体类、工具类、测试接口,复制即可运行。
1. 引入Maven依赖
在pom.xml中添加POI依赖,同时引入commons-io简化流操作:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version></dependency><!-- 处理xlsx格式 --><dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version></dependency><!-- 简化流操作 --><dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version></dependency>
2. 实体类(对应Excel数据)
假设Excel有两个Sheet:“用户表”和“订单表”,分别对应以下两个实体类:
// 用户实体类(对应“用户表”Sheet)@Datapublic class User { private String username; // 用户名(对应Excel第一列) private Integer age; // 年龄(对应Excel第二列) private String phone; // 手机号(对应Excel第三列)}// 订单实体类(对应“订单表”Sheet)@Datapublic class Order { private String orderNo; // 订单号(对应Excel第一列) private BigDecimal amount; // 金额(对应Excel第二列) private String status; // 状态(对应Excel第三列)}// 封装多Sheet数据的结果类@Datapublic class ExcelImportResult { private List<User> userList; // 用户列表 private List<Order> orderList; // 订单列表}
注:@Data注解是Lombok提供的,可自动生成getter/setter,若不使用Lombok,手动编写即可。
3. 核心工具类(Excel解析逻辑)
封装Excel读取工具,包含版本判断、多Sheet遍历、单元格值解析等核心逻辑:
import org.apache.poi.hssf.usermodel.HSSFWorkbook;import org.apache.poi.ss.usermodel.*;import org.apache.poi.xssf.usermodel.XSSFWorkbook;import org.springframework.util.StringUtils;import org.apache.commons.io.FilenameUtils;import java.io.IOException;import java.io.InputStream;import java.math.BigDecimal;import java.util.ArrayList;import java.util.List;public class ExcelImportUtil { // 常量:表头行号(默认第一行为表头,索引从0开始) private static final int HEADER_ROW_NUM = 0; /** * 读取Excel多个Sheet页数据 * @param inputStream Excel文件输入流 * @param fileName Excel文件名(用于判断格式) * @return 封装好的多Sheet数据结果 * @throws IOException 流操作异常 */ public static ExcelImportResult importMultiSheet(InputStream inputStream, String fileName) throws IOException { ExcelImportResult result = new ExcelImportResult(); // 1. 创建工作簿(根据Excel格式选择对应的Workbook) Workbook workbook = getWorkbook(inputStream, fileName); try { // 2. 遍历所有Sheet页 int sheetCount = workbook.getNumberOfSheets(); for (int i = 0; i < sheetCount; i++) { Sheet sheet = workbook.getSheetAt(i); String sheetName = sheet.getSheetName(); // 3. 根据Sheet名称解析对应数据 switch (sheetName) { case "用户表": List<User> userList = parseUserSheet(sheet); result.setUserList(userList); break; case "订单表": List<Order> orderList = parseOrderSheet(sheet); result.setOrderList(orderList); break; default: // 忽略未知Sheet页 System.out.println("忽略未知Sheet页:" + sheetName); } } } finally { // 4. 关闭资源 if (workbook != null) { workbook.close(); } if (inputStream != null) { inputStream.close(); } } return result; } /** * 判断Excel格式,创建对应的Workbook */ private static Workbook getWorkbook(InputStream inputStream, String fileName) throws IOException { Workbook workbook = null; // 获取文件后缀 String extension = FilenameUtils.getExtension(fileName); if ("xls".equalsIgnoreCase(extension)) { // Excel 97-2003 workbook = new HSSFWorkbook(inputStream); } else if ("xlsx".equalsIgnoreCase(extension)) { // Excel 2007+ workbook = new XSSFWorkbook(inputStream); } else { throw new IllegalArgumentException("不支持的Excel格式:" + fileName); } return workbook; } /** * 解析“用户表”Sheet数据 */ private static List<User> parseUserSheet(Sheet sheet) { List<User> userList = new ArrayList<>(); // 获取最后一行索引(判断数据总行数) int lastRowNum = sheet.getLastRowNum(); // 跳过表头,从第二行开始读取(行索引从0开始) for (int rowNum = HEADER_ROW_NUM + 1; rowNum <= lastRowNum; rowNum++) { Row row = sheet.getRow(rowNum); if (row == null) { continue; // 跳过空行 } // 解析每行单元格数据 User user = new User(); user.setUsername(getCellValue(row.getCell(0))); // 第一列:用户名 // 第二列:年龄(转为Integer) String ageStr = getCellValue(row.getCell(1)); user.setAge(StringUtils.hasText(ageStr) ? Integer.parseInt(ageStr) : null); user.setPhone(getCellValue(row.getCell(2))); // 第三列:手机号 userList.add(user); } return userList; } /** * 解析“订单表”Sheet数据 */ private static List<Order> parseOrderSheet(Sheet sheet) { List<Order> orderList = new ArrayList<>(); int lastRowNum = sheet.getLastRowNum(); for (int rowNum = HEADER_ROW_NUM + 1; rowNum <= lastRowNum; rowNum++) { Row row = sheet.getRow(rowNum); if (row == null) { continue; } Order order = new Order(); order.setOrderNo(getCellValue(row.getCell(0))); // 第一列:订单号 // 第二列:金额(转为BigDecimal) String amountStr = getCellValue(row.getCell(1)); order.setAmount(StringUtils.hasText(amountStr) ? new BigDecimal(amountStr) : null); order.setStatus(getCellValue(row.getCell(2))); // 第三列:状态 orderList.add(order); } return orderList; } /** * 获取单元格的值(兼容不同数据类型) */ private static String getCellValue(Cell cell) { if (cell == null) { return ""; } String cellValue = ""; // 根据单元格类型解析值 switch (cell.getCellType()) { case STRING: // 字符串类型 cellValue = cell.getStringCellValue().trim(); break; case NUMERIC: // 数字类型(含整数、小数、日期) // 处理日期格式(避免转为数字) if (DateUtil.isCellDateFormatted(cell)) { cellValue = cell.getDateCellValue().toString(); } else { // 数字转为字符串,避免科学计数法 cellValue = String.valueOf(cell.getNumericCellValue()).trim(); // 去掉末尾的.0(如123.0转为123) if (cellValue.endsWith(".0")) { cellValue = cellValue.substring(0, cellValue.length() - 2); } } break; case BOOLEAN: // 布尔类型 cellValue = String.valueOf(cell.getBooleanCellValue()); break; case FORMULA: // 公式类型 try { // 公式计算结果 cellValue = String.valueOf(cell.getNumericCellValue()).trim(); } catch (Exception e) { // 公式结果为字符串时 cellValue = cell.getStringCellValue().trim(); } break; default: cellValue = ""; } return cellValue; }}
4. 测试接口(Spring Boot环境)
编写一个文件上传接口,接收Excel文件并调用工具类解析:
import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import java.io.IOException;@RestControllerpublic class ExcelImportController { @PostMapping("/import/excel") public String importExcel(@RequestParam("file") MultipartFile file) { // 校验文件 if (file.isEmpty()) { return "请选择Excel文件"; } try { // 调用工具类解析多Sheet数据 ExcelImportResult result = ExcelImportUtil.importMultiSheet(file.getInputStream(), file.getOriginalFilename()); // 后续业务处理:入库、校验等 System.out.println("解析到用户数:" + (result.getUserList() != null ? result.getUserList().size() : 0)); System.out.println("解析到订单数:" + (result.getOrderList() != null ? result.getOrderList().size() : 0)); return "导入成功!用户数:" + (result.getUserList() != null ? result.getUserList().size() : 0) + ",订单数:" + (result.getOrderList() != null ? result.getOrderList().size() : 0); } catch (IOException e) { e.printStackTrace(); return "导入失败:" + e.getMessage(); } catch (IllegalArgumentException e) { e.printStackTrace(); return "导入失败:" + e.getMessage(); } }}
1. 单元格类型兼容
Excel单元格有字符串、数字、日期、公式等多种类型,工具类中getCellValue方法已做兼容处理,尤其注意:
2. 表头与空行处理
默认跳过第一行表头,若Excel表头行数不同,可修改HEADER_ROW_NUM常量;同时跳过空行,避免解析无效数据。
3. 资源关闭
Workbook和InputStream必须关闭,否则会导致文件句柄泄露,工具类中通过finally块确保资源关闭。
4. 大文件处理
若Excel文件较大(超过10万行),Apache POI的普通API会占用大量内存,此时可使用POI的SXSSF(针对.xlsx)或EasyExcel(阿里开源,更轻量)替代,避免OOM。
动态Sheet解析:若Sheet名称不固定,可通过Sheet索引或Sheet内容特征(如表头字段)判断解析逻辑,而非硬编码Sheet名称。
数据校验:在解析实体类时,添加字段校验(如手机号格式、金额非负),不符合规则的数据记录并返回错误信息。
批量入库:解析完成后,使用MyBatis的批量插入API将数据入库,提升效率。