- Excel读取: Apache POI - 这是处理Office文档(如Excel)的事实标准。
- 数据库操作: JDBC (Java Database Connectivity) - Java官方提供的数据库连接标准。
- 数据库: MySQL - 作为示例数据库,但此方法同样适用于PostgreSQL, Oracle, SQL Server等。
第一步:环境准备
创建Java项目
在你的IDE(如IntelliJ IDEA或Eclipse)中创建一个新的Maven或Gradle项目。
添加依赖
我们需要添加Apache POI和MySQL驱动的依赖。
使用Maven (pom.xml):
<dependencies>
<!-- Apache POI for Excel processing -->
<!-- 注意:包含所有核心模块 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.5</version>
</dependency>
<!-- MySQL Connector/J -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
使用Gradle (build.gradle):
dependencies {
// Apache POI for Excel processing
implementation 'org.apache.poi:poi:5.2.5'
implementation 'org.apache.poi:poi-ooxml:5.2.5'
implementation 'org.apache.poi:poi-scratchpad:5.2.5'
// MySQL Connector/J
implementation 'com.mysql:mysql-connector-j:8.0.33'
}
准备数据库和Excel文件
数据库表结构: 假设我们要导入一个包含员工信息的Excel文件,我们在MySQL中创建一个对应的表。
CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;
CREATE TABLE IF NOT EXISTS employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
age INT,
email VARCHAR(100),
department VARCHAR(100),
hire_date DATE
);
Excel文件 (employees.xlsx):
创建一个Excel文件,内容如下。第一行必须是列名,并且列名需要与数据库表的列名对应(或稍后在代码中处理映射关系)。
| name | age | department | hire_date | |
|---|---|---|---|---|
| 张三 | 28 | zhangsan@email.com | 技术部 | 2025-01-15 |
| 李四 | 32 | lisi@email.com | 市场部 | 2025-05-20 |
| 王五 | 25 | wangwu@email.com | 人事部 | 2025-03-10 |
第二步:核心代码实现
我们将代码分为几个部分,使其结构清晰、易于维护。
数据库连接工具类 (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/test_db?useSSL=false&serverTimezone=UTC";
private static final String USER = "root"; // 你的数据库用户名
private static final String PASSWORD = "your_password"; // 你的数据库密码
public static Connection getConnection() {
Connection conn = null;
try {
// 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection(URL, USER, PASSWORD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return conn;
}
public static void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Excel导入主逻辑 (ExcelImporter.java)
这是核心类,负责读取Excel文件并插入数据。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
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 ExcelImporter {
public static void main(String[] args) {
// 1. 定义Excel文件路径
String excelFilePath = "path/to/your/employees.xlsx"; // <--- 修改为你的Excel文件路径
// 2. 定义数据库表名
String tableName = "employees";
// 3. 执行导入
importExcelToDatabase(excelFilePath, tableName);
}
public static void importExcelToDatabase(String excelFilePath, String tableName) {
Connection conn = null;
PreparedStatement pstmt = null;
FileInputStream fis = null;
Workbook workbook = null;
try {
// 1. 获取数据库连接
conn = DBUtil.getConnection();
conn.setAutoCommit(false); // 开启事务,确保数据一致性
// 2. 加载Excel文件
fis = new FileInputStream(new File(excelFilePath));
workbook = new XSSFWorkbook(fis); // 针对 .xlsx 文件
// 3. 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0);
// 4. 获取表头(第一行)
Row headerRow = sheet.getRow(0);
int columnCount = headerRow.getPhysicalNumberOfCells();
// 5. 准备SQL语句 (使用动态列名构建)
// 动态构建列名字符串,如 "name, age, email, department, hire_date"
StringBuilder columns = new StringBuilder();
for (int i = 0; i < columnCount; i++) {
columns.append(headerRow.getCell(i).getStringCellValue());
if (i < columnCount - 1) {
columns.append(", ");
}
}
// 动态构建占位符字符串,如 "(?, ?, ?, ?, ?)"
StringBuilder placeholders = new StringBuilder();
for (int i = 0; i < columnCount; i++) {
placeholders.append("?");
if (i < columnCount - 1) {
placeholders.append(", ");
}
}
String sql = "INSERT INTO " + tableName + " (" + columns.toString() + ") VALUES (" + placeholders.toString() + ")";
System.out.println("Generated SQL: " + sql);
// 6. 创建PreparedStatement
pstmt = conn.prepareStatement(sql);
// 7. 遍历数据行(从第二行开始)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 用于日期格式化
int successCount = 0;
int failCount = 0;
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) {
continue; // 跳过空行
}
try {
// 为PreparedStatement设置参数
for (int j = 0; j < columnCount; j++) {
Cell cell = row.getCell(j);
int columnIndex = j + 1; // JDBC列索引从1开始
if (cell == null) {
pstmt.setNull(columnIndex, java.sql.Types.VARCHAR);
} else {
switch (cell.getCellType()) {
case STRING:
pstmt.setString(columnIndex, cell.getStringCellValue());
break;
case NUMERIC:
// 处理日期
if (DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
pstmt.setDate(columnIndex, new java.sql.Date(date.getTime()));
} else {
// 处理数字
double numericValue = cell.getNumericCellValue();
// 如果是整数,则按整数处理,否则按小数处理
if (numericValue == (long) numericValue) {
pstmt.setLong(columnIndex, (long) numericValue);
} else {
pstmt.setDouble(columnIndex, numericValue);
}
}
break;
case BOOLEAN:
pstmt.setBoolean(columnIndex, cell.getBooleanCellValue());
break;
case FORMULA:
// 如果是公式,则计算公式的值
switch (cell.getCachedFormulaResultType()) {
case NUMERIC:
pstmt.setDouble(columnIndex, cell.getNumericCellValue());
break;
case STRING:
pstmt.setString(columnIndex, cell.getStringCellValue());
break;
default:
pstmt.setString(columnIndex, "");
}
break;
default:
pstmt.setString(columnIndex, "");
}
}
}
// 添加到批处理
pstmt.addBatch();
successCount++;
} catch (Exception e) {
failCount++;
System.err.println("Error processing row " + (i + 1) + ": " + e.getMessage());
// 如果某一行数据有问题,可以选择跳过或回滚整个事务
// 这里我们选择跳过这一行
}
}
// 8. 执行批处理
int[] results = pstmt.executeBatch();
System.out.println("Successfully inserted " + results.length + " rows.");
// 9. 提交事务
conn.commit();
System.out.println("数据导入成功!");
System.out.println("成功行数: " + successCount);
System.out.println("失败行数: " + failCount);
} catch (Exception e) {
e.printStackTrace();
try {
if (conn != null) {
conn.rollback(); // 发生异常时回滚事务
System.err.println("事务已回滚!");
}
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
// 10. 关闭资源
try {
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
if (fis != null) fis.close();
if (workbook != null) workbook.close();
} catch (IOException | SQLException e) {
e.printStackTrace();
}
}
}
}
第三步:运行与验证
- 修改路径: 确保
ExcelImporter.java中的excelFilePath变量指向你的employees.xlsx文件。 - 修改数据库连接: 确保
DBUtil.java中的URL、用户名和密码正确。 - 运行: 运行
ExcelImporter的main方法。 - 验证: 检查你的MySQL数据库
test_db中的employees表,数据是否已经成功导入。
第四步:高级优化与最佳实践
上面的代码已经可以工作,但在生产环境中,我们还需要考虑更多。
使用批处理
上面的代码已经使用了 addBatch() 和 executeBatch(),这对于大量数据导入至关重要,它将多个SQL语句打包一次性发送给数据库,极大地减少了网络I/O和数据库解析SQL的开销,性能提升可达几十甚至上百倍。
性能优化:调整批处理大小
对于非常大的Excel文件(例如几十万行),一次性将所有 addBatch() 放入内存可能会导致内存溢出(OOM),可以分批执行。
// 在循环中,每1000条执行一次批处理
if (i % 1000 == 0) {
pstmt.executeBatch();
System.out.println("Processed " + i + " rows...");
}
// 循环结束后,执行剩余的批处理
pstmt.executeBatch();
数据校验与清洗
- 空值处理: 代码中已经做了基本处理 (
pstmt.setNull(...))。 - 数据类型转换: 代码中处理了字符串、数字、布尔值和日期,但更健壮的实现应该捕获
SQLException,记录错误行号和具体原因,而不是简单地跳过。 - 格式校验:
email列可以检查是否包含 符号。
使用连接池
在高并发场景下,频繁地创建和销毁数据库连接是非常消耗资源的,应该使用数据库连接池,如 HikariCP (性能最好)、Druid 或 C3P0。
添加HikariCP依赖 (Maven):
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
然后修改 DBUtil.java 来使用HikariCP管理连接。
使用更现代的框架
对于企业级应用,手动编写JDBC代码容易出错且维护成本高,可以考虑使用 MyBatis 或 JPA (Hibernate) 等ORM框架,它们能更优雅地处理SQL映射、参数绑定和结果集。
错误处理与日志
不要使用 System.out.println,应该使用专业的日志框架,如 SLF4J + Logback 或 Log4j2,将所有错误信息(如哪一行数据出错、具体错误原因)记录到日志文件中,便于后续排查。
处理 .xls (旧版Excel) 文件
上面的代码使用 XSSFWorkbook 处理 .xlsx 文件,如果需要处理旧版的 .xls 文件,需要使用 HSSFWorkbook。
可以修改代码,根据文件扩展名来决定使用哪种 Workbook 实现:
Workbook workbook;
if (excelFilePath.endsWith(".xlsx")) {
workbook = new XSSFWorkbook(fis);
} else if (excelFilePath.endsWith(".xls")) {
workbook = new HSSFWorkbook(fis);
} else {
throw new IllegalArgumentException("不支持的文件类型,请上传 .xls 或 .xlsx 文件");
}
将Excel导入数据库是一个经典任务,其核心流程是:
- 读取Excel: 使用Apache POI解析文件流,获取工作表、行和单元格数据。
- 建立数据库连接: 使用JDBC或连接池与数据库建立连接。
- 准备SQL: 使用
PreparedStatement防止SQL注入,并利用其批处理功能提高性能。 - 数据转换与映射: 将Excel单元格数据(字符串、数字、日期等)转换为Java类型,再设置为
PreparedStatement的参数。 - 执行与提交: 执行批处理操作,并提交事务以保证数据一致性。
- 资源清理: 在
finally块中确保所有资源(文件流、连接、语句等)被正确关闭。
希望这份详细的指南能帮助你顺利完成Excel导入数据库的任务!
