我们将使用 Apache POI 来读取 Excel 文件,使用 JDBC 来连接和操作数据库,这是最经典和通用的组合。

第一步:环境准备
-
Java Development Kit (JDK): 确保你已经安装了 JDK 8 或更高版本。
-
Excel 文件 (示例): 假设我们有一个名为
users.xlsx的 Excel 文件,内容如下:
| id | name | age | create_time | |
|---|---|---|---|---|
| 1 | 张三 | zhangsan@example.com | 25 | 2025-01-15 10:30:00 |
| 2 | 李四 | lisi@example.com | 30 | 2025-02-20 14:15:00 |
| 3 | 王五 | wangwu@example.com | 28 | 2025-03-10 09:45:00 |
* **注意:**
* 第一行是**标题行 (Header Row)**,用于映射数据库字段。
* 日期列的格式要确保能被程序正确解析。
* 建议将此文件放在项目的 `src/main/resources` 目录下,方便作为资源文件访问。
-
数据库 (示例): 我们使用 MySQL 数据库,创建一个对应的表
t_user。CREATE DATABASE IF NOT EXISTS `demo_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE `demo_db`; CREATE TABLE `t_user` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '用户ID', `name` VARCHAR(50) NOT NULL COMMENT '姓名', `email` VARCHAR(100) NOT NULL COMMENT '邮箱', `age` INT COMMENT '年龄', `create_time` DATETIME COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_email` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-
添加 Maven 依赖: 在你的
pom.xml文件中添加以下依赖。
(图片来源网络,侵删)<dependencies> <!-- Apache POI: 用于操作 Office 文件 --> <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> <!-- MySQL JDBC Driver: 用于连接 MySQL 数据库 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!-- Lombok: 简化 Java 代码 (可选,但推荐) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency> </dependencies>
第二步:核心代码实现
我们将创建一个工具类 ExcelImportUtils,它负责读取 Excel 并将数据插入数据库。
数据库连接工具类 (DBUtil.java)
创建一个工具类来管理数据库连接,避免代码重复。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
// 数据库连接信息 (请根据你的实际情况修改)
private static final String URL = "jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8";
private static final String USER = "root"; // 你的数据库用户名
private static final String PASSWORD = "your_password"; // 你的数据库密码
static {
try {
// 加载 MySQL 驱动
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("Failed to load MySQL driver!");
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASSWORD);
}
public static void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Excel 导入工具类 (ExcelImportUtils.java)
这是核心逻辑所在,我们将分步骤实现它。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class ExcelImportUtils {
/**
* 从 Excel 文件导入数据到数据库
* @param inputStream Excel 文件的输入流
* @param sheetName 要读取的工作表名称 (如果为 null,则读取第一个)
* @param startRow 数据开始的行号 (从 0 开始,标题行不算)
* @return 成功导入的记录数
*/
public static int importFromExcel(InputStream inputStream, String sheetName, int startRow) {
Connection conn = null;
int successCount = 0;
try {
// 1. 获取数据库连接
conn = DBUtil.getConnection();
// 关闭自动提交,开启事务
conn.setAutoCommit(false);
// 2. 使用 POI 读取 Excel 文件
Workbook workbook = new XSSFWorkbook(inputStream);
Sheet sheet = (sheetName != null && !sheetName.isEmpty()) ? workbook.getSheet(sheetName) : workbook.getSheetAt(0);
// 3. 获取标题行,用于构建 SQL
Row headerRow = sheet.getRow(0);
if (headerRow == null) {
throw new RuntimeException("Excel 文件没有标题行!");
}
// 4. 构建动态 SQL 语句 (更健壮的方式)
// 这里我们使用一个通用的插入语句,并处理日期格式
String sql = "INSERT INTO t_user (name, email, age, create_time) VALUES (?, ?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
// 5. 遍历数据行
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = startRow; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) {
continue; // 跳过空行
}
try {
// 读取单元格数据并处理
String name = getCellValueAsString(row.getCell(1)); // name 在第二列
String email = getCellValueAsString(row.getCell(2)); // email 在第三列
Integer age = getCellValueAsInteger(row.getCell(3)); // age 在第四列
// 处理日期单元格
Date createTime = null;
Cell dateCell = row.getCell(4); // create_time 在第五列
if (dateCell != null) {
if (dateCell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(dateCell)) {
createTime = dateCell.getDateCellValue();
} else if (dateCell.getCellType() == CellType.STRING) {
// 尝试将字符串解析为日期
createTime = dateFormat.parse(dateCell.getStringCellValue());
}
}
// 设置 PreparedStatement 参数
pstmt.setString(1, name);
pstmt.setString(2, email);
pstmt.setObject(3, age); // 使用 setObject 处理 Integer 可能为 null 的情况
pstmt.setTimestamp(4, createTime != null ? new java.sql.Timestamp(createTime.getTime()) : null);
// 添加到批处理
pstmt.addBatch();
// 每 1000 条提交一次批处理,提高性能
if (i % 1000 == 0) {
successCount += pstmt.executeBatch();
}
} catch (Exception e) {
System.err.println("处理第 " + (i + 1) + " 行时出错: " + e.getMessage());
// 可以选择记录错误行号和内容,然后继续处理下一行
}
}
// 6. 执行批处理并提交事务
successCount += pstmt.executeBatch();
conn.commit(); // 提交事务
System.out.println("成功导入 " + successCount + " 条数据!");
} catch (Exception e) {
e.printStackTrace();
if (conn != null) {
try {
conn.rollback(); // 出错时回滚事务
System.err.println("导入失败,已回滚事务!");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true); // 恢复自动提交模式
DBUtil.closeConnection(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return successCount;
}
/**
* 获取单元格的字符串值
*/
private static String getCellValueAsString(Cell cell) {
if (cell == null) {
return "";
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue().trim();
case NUMERIC:
// 如果是整数,返回整数形式;否则返回浮点数形式
if (cell.getNumericCellValue() % 1 == 0) {
return String.valueOf((long) cell.getNumericCellValue());
} else {
return String.valueOf(cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
return cell.getCellFormula();
default:
return "";
}
}
/**
* 获取单元格的整数值
*/
private static Integer getCellValueAsInteger(Cell cell) {
if (cell == null) {
return null;
}
switch (cell.getCellType()) {
case NUMERIC:
return (int) cell.getNumericCellValue();
case STRING:
try {
return Integer.parseInt(cell.getStringCellValue().trim());
} catch (NumberFormatException e) {
return null; // 无法解析则返回 null
}
default:
return null;
}
}
}
测试主类 (Main.java)
创建一个主类来调用我们的导入工具。
import java.io.InputStream;
public class Main {
public static void main(String[] args) {
// 从 resources 目录下加载 Excel 文件
// "users.xlsx" 文件应放在 src/main/resources 目录下
InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("users.xlsx");
if (inputStream == null) {
System.err.println("错误: 在 resources 目录下未找到 users.xlsx 文件!");
return;
}
// 调用导入方法
// sheetName: "Sheet1" (根据你的Excel文件工作表名称修改)
// startRow: 1 (表示从标题行的下一行开始读取数据)
ExcelImportUtils.importFromExcel(inputStream, "Sheet1", 1);
}
}
第三步:运行与验证
- 放置文件: 将你的
users.xlsx文件复制到项目的src/main/resources目录下。 - 检查配置: 确保
DBUtil.java中的数据库 URL、用户名和密码是正确的。 - 运行: 运行
Main类的main方法。 - 查看结果:
- 控制台会打印出导入的记录数,
成功导入 3 条数据! - 连接到你的 MySQL 数据库,查询
t_user表,检查数据是否已成功导入。
- 控制台会打印出导入的记录数,
第四步:高级技巧与最佳实践
上面的代码已经是一个很好的基础,但在生产环境中,你还需要考虑更多。
性能优化:批处理
代码中已经使用了 PreparedStatement 的 addBatch() 和 executeBatch(),这是批量插入数据的关键,能极大地减少与数据库的交互次数,提升性能。
数据验证与错误处理
- 校验数据格式: 在读取每一行数据时,可以添加校验逻辑,检查
email是否合法,age是否在合理范围内。 - 错误回滚: 我们使用了事务 (
conn.setAutoCommit(false)和conn.commit()/conn.rollback()),如果某一行数据导入失败(如邮箱重复导致唯一约束冲突),整个事务会回滚,确保数据的一致性。 - 记录错误日志: 将出错的行号、内容和具体错误信息记录到日志文件或数据库中,方便后续排查。
内存优化 (处理大文件)
Apache POI 在处理 .xlsx 文件时,默认会将整个文件加载到内存中,Excel 文件非常大(例如几百MB),可能会导致内存溢出 (OutOfMemoryError)。
解决方案:使用 SAX 模式 (Event API)
POI 提供了基于 SAX 的解析方式,它不会一次性加载整个文件,而是逐行读取,内存占用非常小。
你需要引入额外的依赖:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.3</version>
</dependency>
然后使用 XSSFReader 和 SheetContentsHandler 接口来实现,这比 DOM 模式(我们上面用的)要复杂,但对于大文件是必须的。
动态 SQL (更灵活的方案)
上面的 SQL 语句是写死的,Excel 文件的列是动态变化的,或者需要适配不同的表,就需要动态构建 SQL。
// ... 在读取标题行后 ...
StringBuilder sqlBuilder = new StringBuilder("INSERT INTO t_user (");
StringBuilder placeholdersBuilder = new StringBuilder(") VALUES (");
List<String> headers = new ArrayList<>();
for (Cell cell : headerRow) {
if (cell != null) {
String headerName = getCellValueAsString(cell);
// 可以在这里做映射,Excel 的 "姓名" -> 数据库的 "name"
// headers.add(headerName);
headers.add(headerName); // 假设 Excel 的列名和数据库列名一致
}
}
for (int i = 0; i < headers.size(); i++) {
sqlBuilder.append(headers.get(i));
placeholdersBuilder.append("?");
if (i < headers.size() - 1) {
sqlBuilder.append(", ");
placeholdersBuilder.append(", ");
}
}
sqlBuilder.append(placeholdersBuilder.toString()).append(")");
String finalSql = sqlBuilder.toString();
System.out.println("生成的SQL: " + finalSql);
// 然后使用这个 finalSql 来创建 PreparedStatement
// 注意:列的顺序必须和 Excel 的列顺序一致!
使用更现代的框架
对于企业级应用,可以考虑使用现成的框架来简化工作:
- EasyExcel: 阿里巴巴开源的,专门为解决 POI 内存问题而生,性能好,API 简洁。
- JPA/Hibernate + Spring Batch: 在 Spring 生态中,可以使用 Spring Batch 来处理批量导入任务,它提供了强大的任务调度、重试、跳过和事务管理功能。
从 Excel 导入数据到数据库是一个综合性的任务,涉及文件解析、数据库操作、事务管理等多个方面。
- 基础实现: 使用 Apache POI 读取 Excel,JDBC 操作数据库,并使用 事务 保证数据一致性。
- 核心优化: 使用 批处理 提升性能。
- 健壮性增强: 增加数据校验和完善的错误处理。
- 进阶考虑: 对于大文件,采用 SAX 模式;对于灵活性需求,构建动态 SQL;对于大型项目,选择成熟的框架。
希望这份详细的指南能帮助你顺利完成 Java 从 Excel 导入数据库的开发任务!
