核心思路
在 Java Web 应用中导入 Excel 的核心流程如下:

- 前端页面:提供一个文件上传的表单,让用户选择本地的 Excel 文件。
- 后端接收:服务器端的 Servlet 或 Controller 接收前端上传的文件。
- 解析 Excel:使用专门的库(如 Apache POI 或 EasyExcel)来读取 Excel 文件的内容。
- 数据处理:将读取到的数据(通常是字符串或数字)转换成你业务需要的 Java 对象(Entity/DTO)。
- 业务处理:将转换后的数据列表保存到数据库,或进行其他业务逻辑操作。
- 响应结果:向前端返回一个操作结果,例如成功、失败、失败原因等。
技术选型
选择合适的库是成功的一半,目前最主流的两个库是:
| 特性 | Apache POI | EasyExcel (阿里巴巴) |
|---|---|---|
| 简介 | 老牌、功能强大的 Java API,用于操作 Microsoft Office 格式文件。 | 阿里巴巴开源,基于 POI 封装,解决了 POI 内存消耗大的问题。 |
| 优点 | 功能极其全面,支持所有 Office 格式。 | 内存占用极低,性能好,适合处理大文件,API 更简洁。 |
| 缺点 | 内存消耗巨大,处理大文件时容易导致 OOM (Out of Memory)。 | 相对 POI,功能上可能稍弱(但已覆盖绝大多数场景)。 |
| 适用场景 | 文件较小,功能要求复杂。 | 强烈推荐,特别是生产环境和处理大文件时。 |
对于新项目,强烈推荐使用 EasyExcel,本教程将以 EasyExcel 为主进行讲解,最后也会给出 POI 的示例。
详细实现步骤 (以 EasyExcel 为例)
我们将创建一个标准的 Maven 项目,实现一个用户信息导入功能。
步骤 1:添加 Maven 依赖
在你的 pom.xml 文件中添加 EasyExcel 和 Web 相关的依赖。

<dependencies>
<!-- Spring Boot Starter Web (如果你用 Spring Boot) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- EasyExcel 核心依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version> <!-- 建议使用最新版本 -->
</dependency>
<!-- Lombok (简化 Java 代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
步骤 2:准备 Excel 模板和数据
假设我们要导入用户信息,Excel 文件 users.xlsx 内容如下:
| 姓名 | 年龄 | 邮箱 |
|---|---|---|
| 张三 | 25 | zhangsan@example.com |
| 李四 | 30 | lisi@example.com |
| 王五 | 28 | wangwu@example.com |
步骤 3:创建实体类
创建一个与 Excel 行数据对应的 Java 实体类,使用 EasyExcel 的注解来映射列。
User.java
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data // Lombok 自动生成 getter, setter, toString 等
public class User {
// value 对应 Excel 中的列名,index 对应列的索引(从0开始)
// 两者指定一个即可,推荐使用 value
@ExcelProperty("姓名")
private String name;
@ExcelProperty("年龄")
private Integer age;
@ExcelProperty("邮箱")
private String email;
}
步骤 4:创建 Controller 处理文件上传
这是核心部分,负责接收文件、调用解析器并返回结果。
ExcelImportController.java
import com.alibaba.excel.EasyExcel;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("/api/excel")
public class ExcelImportController {
/**
* 导入 Excel
* @param file 前端上传的文件
*/
@PostMapping("/import")
public String importExcel(@RequestParam("file") MultipartFile file) throws IOException {
// 1. 参数校验
if (file == null || file.isEmpty()) {
return "文件不能为空!";
}
// 2. 获取文件名
String originalFilename = file.getOriginalFilename();
if (!originalFilename.endsWith(".xlsx") && !originalFilename.endsWith(".xls")) {
return "文件格式不正确,请上传 Excel 文件!";
}
try {
// 3. 使用 EasyExcel 读取文件
// file.getInputStream() 获取文件输入流
// User.class 指定映射的实体类
// new PageReadListener() 监听器,每读取一行数据,就会回调一次
List<User> userList = EasyExcel.read(file.getInputStream(), User.class, new PageReadListener<User>(dataList -> {
// dataList 是每次读取到的数据列表(默认100条一批)
System.out.println("解析到一条数据: " + dataList.size());
// 在这里可以进行业务处理,比如批量保存到数据库
// userService.saveBatch(dataList);
})).sheet().doRead();
System.out.println("所有数据解析完成,总条数: " + userList.size());
return "成功导入 " + userList.size() + " 条数据!";
} catch (Exception e) {
e.printStackTrace();
return "导入失败: " + e.getMessage();
}
}
}
步骤 5:创建前端页面 (HTML + JavaScript)
创建一个简单的 HTML 页面用于上传文件。
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">Excel 导入示例</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.container { max-width: 600px; margin: auto; padding: 20px; border: 1px solid #ccc; border-radius: 5px; }
input[type="file"] { margin-bottom: 10px; }
button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background-color: #0056b3; }
#result { margin-top: 20px; padding: 10px; border: 1px solid #ccc; border-radius: 4px; }
</style>
</head>
<body>
<div class="container">
<h2>Excel 用户信息导入</h2>
<input type="file" id="fileInput" accept=".xlsx, .xls">
<button onclick="uploadFile()">上传并导入</button>
<div id="result"></div>
</div>
<script>
function uploadFile() {
const fileInput = document.getElementById('fileInput');
const resultDiv = document.getElementById('result');
if (fileInput.files.length === 0) {
resultDiv.innerHTML = '<p style="color: red;">请先选择一个文件!</p>';
return;
}
const file = fileInput.files[0];
const formData = new FormData();
formData.append('file', file);
resultDiv.innerHTML = '<p>正在上传...</p>';
fetch('/api/excel/import', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => {
resultDiv.innerHTML = `<p style="color: green;">${data}</p>`;
})
.catch(error => {
resultDiv.innerHTML = `<p style="color: red;">上传失败: ${error}</p>`;
});
}
</script>
</body>
</html>
步骤 6:配置 Spring Boot (如果使用)
在 application.properties 或 application.yml 中配置文件上传的大小限制,默认是 1MB,对于 Excel 文件来说太小了。
application.properties
# 配置单个文件上传大小 spring.servlet.multipart.max-file-size=10MB # 配置总请求大小(如果同时上传多个文件) spring.servlet.multipart.max-request-size=10MB
高级功能与最佳实践
复杂表头处理
Excel 表头是分层的,可以使用 Head 对象。
Excel 表头: | 用户信息 | | | | :--- | :--- | :--- | | 姓名 | 年龄 | 邮箱 |
实体类修改:
@Data
public class User {
// 第一列表头是 "用户信息",第二列表头是 "姓名"
@ExcelProperty({"用户信息", "姓名"})
private String name;
@ExcelProperty({"用户信息", "年龄"})
private Integer age;
@ExcelProperty({"用户信息", "邮箱"})
private String email;
}
数据校验与自定义转换
EasyExcel 支持集成 Spring Validation 进行数据校验。
实体类添加校验注解:
import javax.validation.constraints.*;
@Data
public class User {
@NotBlank(message = "姓名不能为空")
@ExcelProperty("姓名")
private String name;
@Min(value = 18, message = "年龄不能小于18岁")
@Max(value = 100, message = "年龄不能大于100岁")
@ExcelProperty("年龄")
private Integer age;
@Email(message = "邮箱格式不正确")
@ExcelProperty("邮箱")
private String email;
}
Controller 修改 (使用 @Validated):
@PostMapping("/import")
public String importExcel(@RequestParam("file") MultipartFile file, BindingResult result) {
// ... 前面的代码 ...
EasyExcel.read(..., new AnalysisEventListener<User>() {
@Override
public void invoke(User user, AnalysisContext context) {
// 这里可以手动校验
if (user.getName() == null || user.getName().trim().isEmpty()) {
// 可以将错误信息收集起来,最后统一返回
throw new RuntimeException("第 " + context.readRowHolder().getRowIndex() + " 行姓名不能为空");
}
// ... 业务处理 ...
}
// ... 其他方法 ...
});
}
错误信息收集与返回
实际应用中,我们希望知道具体哪一行、哪个字段出了错,而不是直接抛出异常中断。
修改后的监听器:
// 在 Controller 中
public String importExcel(@RequestParam("file") MultipartFile file) throws IOException {
// ...
List<String> errorMessages = new ArrayList<>();
EasyExcel.read(file.getInputStream(), User.class, new AnalysisEventListener<User>() {
// 每解析一行都会调用
@Override
public void invoke(User user, AnalysisContext context) {
// 模拟业务校验
if (user.getAge() != null && user.getAge() < 18) {
int rowNum = context.readRowHolder().getRowIndex() + 1; // Excel 行号从1开始
errorMessages.add("第 " + rowNum + " 行,年龄不能小于18岁");
return; // 跳过这一行,不继续处理
}
// ... 正常处理 ...
}
// 所有数据解析完成后调用
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 解析完成,可以在这里进行数据库批量保存等操作
if (errorMessages.isEmpty()) {
System.out.println("所有数据校验通过,准备入库...");
}
}
}).sheet().doRead();
if (!errorMessages.isEmpty()) {
return "导入失败,原因如下:<br>" + String.join("<br>", errorMessages);
}
return "导入成功!";
}
使用 Apache POI 示例
如果你因为某些原因必须使用 POI,以下是核心代码对比。
Maven 依赖
<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>
POI 解析代码 (事件模型 SAX)
为了不 OOM,POI 也推荐使用事件模型(SAX)来解析大文件,这和 EasyExcel 的监听器模式非常相似。
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFReader.SheetIterator;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class PoiExcelReader {
public static List<User> read(InputStream inputStream) throws Exception, InvalidFormatException {
List<User> userList = new ArrayList<>();
XSSFReader xssfReader = new XSSFReader(inputStream);
SharedStringsTable sst = xssfReader.getSharedStringsTable();
SheetIterator sheetIterator = (SheetIterator) xssfReader.getSheetsData();
if (sheetIterator.hasNext()) {
InputStream sheetStream = sheetIterator.next();
SheetHandler sheetHandler = new SheetHandler(sst, userList);
XMLReader parser = XMLReaderFactory.createXMLReader();
parser.setContentHandler(sheetHandler);
parser.parse(new InputSource(sheetStream));
}
return userList;
}
}
// 自定义的 SheetHandler
class SheetHandler implements ContentHandler {
// ... 省略了复杂的 SAX 解析逻辑 ...
// 需要自己处理 XML 事件,解析出单元格数据和字符串表,
// 然后映射到 User 对象中,代码量比 EasyExcel 多很多。
}
可以看到,POI 的原生实现非常繁琐,这也是为什么 EasyExcel 等封装库如此受欢迎的原因。
| 功能点 | EasyExcel (推荐) | Apache POI |
|---|---|---|
| 易用性 | 极高,注解 + 监听器模式,代码简洁。 | 较低,原生 API 复杂,大文件解析需要自己实现 SAX。 |
| 性能 | 优秀,内存占用低,流式读取。 | 一般,HSSF (.xls) 内存占用大,XSSF (.xlsx) 可以用 SAX 模式优化。 |
| 功能 | 覆盖 90%+ 常见需求,如复杂表头、数据校验等。 | 功能最全,支持所有 Office 特有格式。 |
| 学习成本 | 低 | 高 |
对于绝大多数 Java Web 项目,直接选择 EasyExcel 可以让你快速、高效、稳定地完成 Excel 导入功能。
