技术选型
要实现这个功能,我们通常需要以下几个核心库:

-
Excel 解析库:
- Apache POI: 最经典、功能最强大的 Java 操作 Office 文件(如 .xls, .xlsx)的库,它是事实上的标准。
- EasyExcel: 阿里巴巴开源的库,它基于 POI,但做了大量优化,内存占用极低,性能更好,特别适合处理大文件(几十上百万行)。强烈推荐使用 EasyExcel。
-
数据库连接:
- JDBC: Java 数据库连接,是 Java 访问数据库的标准 API。
- 连接池 (HikariCP, Druid): 在实际应用中,必须使用连接池来管理数据库连接,以提高性能和稳定性,HikariCP 是目前性能最高的连接池之一。
-
构建工具:
- Maven 或 Gradle: 用于管理项目依赖。
环境准备与项目搭建
这里我们以 Maven 和 EasyExcel 为例进行演示。

1 创建 Maven 项目
在你的 IDE(如 IntelliJ IDEA 或 Eclipse)中创建一个新的 Maven 项目。
2 添加依赖 (pom.xml)
在 pom.xml 文件中添加以下依赖:
<dependencies>
<!-- EasyExcel 核心依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version> <!-- 请使用最新版本 -->
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version> <!-- 请根据你的 MySQL 版本选择 -->
</dependency>
<!-- HikariCP 连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version> <!-- 请使用最新版本 -->
</dependency>
<!-- Lombok (简化 Java 代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
数据库准备
假设我们要导入一张用户信息表。
1 创建数据库和表
-- 创建数据库 CREATE DATABASE IF NOT EXISTS excel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 使用数据库 USE excel_db; -- 创建用户表 CREATE TABLE IF NOT EXISTS `user_info` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` VARCHAR(50) NOT NULL COMMENT '姓名', `age` INT NULL COMMENT '年龄', `gender` VARCHAR(10) NULL COMMENT '性别', `email` VARCHAR(100) NULL COMMENT '邮箱', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ) COMMENT = '用户信息表';
创建 Excel 文件
在 src/main/resources 目录下创建一个名为 user_info_template.xlsx 的 Excel 文件。

注意:
- 第一行必须是表头。
- 表头的名称需要和 Java 实体类的字段名对应(或通过注解映射)。
| 姓名 | 年龄 | 性别 | 邮箱 |
|---|---|---|---|
| 张三 | 25 | 男 | zhangsan@example.com |
| 李四 | 30 | 女 | lisi@example.com |
| 王五 | 28 | 男 | wangwu@example.com |
Java 代码实现
我们将代码分为几个部分:实体类、数据库配置、Excel 读取逻辑和主程序。
1 创建实体类 (UserInfo.java)
这个类用于映射 Excel 中的一行数据。
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
@Data // Lombok 注解,自动生成 getter, setter, toString 等
public class UserInfo {
// ExcelProperty 的 value 值需要和 Excel 表头完全一致
// index 可以指定列的顺序,但通常不推荐,依赖顺序容易出错
@ExcelProperty("姓名")
private String name;
@ExcelProperty("年龄")
private Integer age;
@ExcelProperty("性别")
private String gender;
@ExcelProperty("邮箱")
private String email;
// 数据库中需要,但 Excel 中没有的字段
private Date createTime;
}
2 数据库配置 (DatabaseConfig.java)
我们将数据库连接信息提取出来,方便管理。
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DatabaseConfig {
private static final String JDBC_URL = "jdbc:mysql://localhost:3306/excel_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai";
private static final String USERNAME = "root"; // 你的数据库用户名
private static final String PASSWORD = "your_password"; // 你的数据库密码
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(JDBC_URL);
config.setUsername(USERNAME);
config.setPassword(PASSWORD);
// 连接池配置
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
config.setMaximumPoolSize(10); // 最大连接数
config.setMinimumIdle(5); // 最小空闲连接数
config.setConnectionTimeout(30000); // 连接超时时间
dataSource = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
3 Excel 读取与数据库写入逻辑 (ExcelImportService.java)
这是核心业务逻辑,使用 EasyExcel 的监听器模式。
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.extern.slf4j.Slf4j;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class ExcelImportService {
// 定义批量插入的大小,可以提升性能
private static final int BATCH_SIZE = 1000;
public void importExcel(String filePath) {
// 1. 准备一个临时列表,用于存放读取到的数据
List<UserInfo> dataList = new ArrayList<>(BATCH_SIZE);
// 2. 创建数据库连接
try (Connection connection = DatabaseConfig.getConnection()) {
// 3. 创建 PreparedStatement,SQL 语句中用 ? 作为占位符
String sql = "INSERT INTO user_info (name, age, gender, email, create_time) VALUES (?, ?, ?, ?, ?)";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
// 4. 使用 EasyExcel 读取 Excel 文件
EasyExcel.read(filePath, UserInfo.class, new AnalysisEventListener<UserInfo>() {
// 每解析完一行数据,就会调用这个方法
@Override
public void invoke(UserInfo data, AnalysisContext context) {
dataList.add(data);
// 当数据量达到 BATCH_SIZE 时,执行批量插入
if (dataList.size() >= BATCH_SIZE) {
saveData(ps, dataList);
// 清空列表,准备下一批数据
dataList.clear();
}
}
// 所有数据解析完成后,会调用这个方法
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 处理剩余不足 BATCH_SIZE 的数据
if (!dataList.isEmpty()) {
saveData(ps, dataList);
}
log.info("所有数据解析完成!");
}
}).sheet().doRead(); // 读取第一个 sheet
}
} catch (Exception e) {
log.error("导入 Excel 失败", e);
throw new RuntimeException("导入失败", e);
}
}
/**
* 批量保存数据到数据库
* @param ps PreparedStatement 对象
* @param dataList 数据列表
* @throws SQLException
*/
private void saveData(PreparedStatement ps, List<UserInfo> dataList) throws SQLException {
log.info("开始保存 {} 条数据到数据库...", dataList.size());
for (UserInfo data : dataList) {
// 设置 SQL 语句中的参数
ps.setString(1, data.getName());
ps.setInt(2, data.getAge());
ps.setString(3, data.getGender());
ps.setString(4, data.getEmail());
ps.setTimestamp(5, new java.sql.Timestamp(System.currentTimeMillis())); // 设置当前时间作为创建时间
// 将 SQL 语句添加到批处理中
ps.addBatch();
}
// 执行批处理
ps.executeBatch();
log.info("成功保存 {} 条数据!", dataList.size());
}
}
4 主程序 (Main.java)
我们编写一个主程序来调用服务。
import java.io.InputStream;
public class Main {
public static void main(String[] args) {
// Excel 文件路径,这里从 resources 目录读取
String fileName = Main.class.getClassLoader().getResource("user_info_template.xlsx").getFile();
ExcelImportService importService = new ExcelImportService();
importService.importExcel(fileName);
}
}
运行与验证
- 确保 MySQL 服务已启动,
excel_db数据库和user_info表已创建。 - 检查
DatabaseConfig.java中的数据库连接信息(URL, 用户名, 密码)是否正确。 - 运行
Main.java。 - 查看控制台日志,你会看到类似 "开始保存 1000 条数据..." 的输出。
- 查询数据库,验证数据是否已成功导入。
-- 查询 user_info 表 SELECT * FROM user_info;
你应该能看到 Excel 中的数据已经成功插入到数据库中。
高级与优化(非常重要)
处理大文件(内存优化)
EasyExcel 的核心优势就是处理大文件,上面的监听器模式本身就是为处理大文件设计的,它不会一次性将所有数据加载到内存中,而是逐行读取,处理完一行就丢弃,内存占用非常低。
关键点:
- 必须使用
AnalysisEventListener,而不是EasyExcel.read().doReadAll()。 - 在
invoke方法中处理数据,而不是将所有数据存入一个List中再统一处理(除非你的业务逻辑确实需要)。
错误处理与数据回滚
在批量导入时,如果某条数据因为格式错误(如年龄是字符串)导致插入失败,我们希望跳过这条错误数据,继续处理后续数据,而不是整个导入任务失败。
优化后的 saveData 方法:
// 在 ExcelImportService.java 中
private void saveData(PreparedStatement ps, List<UserInfo> dataList) {
log.info("开始保存 {} 条数据到数据库...", dataList.size());
try {
for (UserInfo data : dataList) {
try {
ps.setString(1, data.getName());
ps.setInt(2, data.getAge());
ps.setString(3, data.getGender());
ps.setString(4, data.getEmail());
ps.setTimestamp(5, new java.sql.Timestamp(System.currentTimeMillis()));
ps.addBatch();
} catch (SQLException e) {
// 捕获单条数据插入时的异常(如类型不匹配),记录日志并跳过
log.error("数据格式错误,已跳过: {}", data, e);
// 继续处理下一条
continue;
}
}
ps.executeBatch();
log.info("成功保存 {} 条数据!", dataList.size());
} catch (SQLException e) {
log.error("批处理执行失败", e);
// 可以在这里选择是否回滚,但通常批处理是自动提交的,所以手动回滚需要额外配置
// connection.rollback();
}
}
事务管理
对于要求“要么全部成功,要么全部失败”的业务场景,就需要使用数据库事务。
// 在 ExcelImportService.java 的 importExcel 方法中
public void importExcelWithTransaction(String filePath) {
Connection connection = null;
try {
connection = DatabaseConfig.getConnection();
// 关闭自动提交,开启事务
connection.setAutoCommit(false);
String sql = "INSERT INTO user_info (name, age, gender, email, create_time) VALUES (?, ?, ?, ?, ?)";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
List<UserInfo> dataList = new ArrayList<>(BATCH_SIZE);
EasyExcel.read(filePath, UserInfo.class, new AnalysisEventListener<UserInfo>() {
@Override
public void invoke(UserInfo data, AnalysisContext context) {
dataList.add(data);
if (dataList.size() >= BATCH_SIZE) {
saveDataWithTransaction(ps, connection, dataList);
dataList.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if (!dataList.isEmpty()) {
saveDataWithTransaction(ps, connection, dataList);
}
// 所有数据成功处理,提交事务
connection.commit();
log.info("所有数据解析并保存完成,事务已提交!");
}
}).sheet().doRead();
}
} catch (Exception e) {
log.error("导入 Excel 失败,准备回滚", e);
if (connection != null) {
try {
// 发生异常,回滚事务
connection.rollback();
log.info("事务已回滚!");
} catch (SQLException ex) {
log.error("回滚事务失败", ex);
}
}
throw new RuntimeException("导入失败,已回滚", e);
} finally {
if (connection != null) {
try {
// 恢复自动提交
connection.setAutoCommit(true);
connection.close();
} catch (SQLException e) {
log.error("关闭连接失败", e);
}
}
}
}
private void saveDataWithTransaction(PreparedStatement ps, Connection connection, List<UserInfo> dataList) throws SQLException {
log.info("事务模式下,开始保存 {} 条数据...", dataList.size());
for (UserInfo data : dataList) {
ps.setString(1, data.getName());
ps.setInt(2, data.getAge());
ps.setString(3, data.getGender());
ps.setString(4, data.getEmail());
ps.setTimestamp(5, new java.sql.Timestamp(System.currentTimeMillis()));
ps.addBatch();
}
ps.executeBatch();
}
| 场景 | 推荐方案 | 关键点 |
|---|---|---|
| 小文件导入 | EasyExcel + JDBC | 简单直接,使用 doReadAll() 或监听器均可。 |
| 大文件导入 | EasyExcel (监听器模式) + JDBC | 必须使用 AnalysisEventListener,逐行读取,内存友好。 |
| 高性能批量插入 | EasyExcel + JDBC + 批处理 (addBatch/executeBatch) |
显著减少数据库 I/O 次数,大幅提升速度。 |
| 容错性要求高 | EasyExcel + JDBC + 异常捕获 | 在 try-catch 中处理单条数据错误,避免整个任务中断。 |
| 数据一致性要求高 | EasyExcel + JDBC + 数据库事务 | 使用 connection.setAutoCommit(false) 和 commit()/rollback()。 |
通过以上步骤和优化,你就可以构建一个健壮、高效、可扩展的 Java Excel 导入数据库功能。
