杰瑞科技汇

Java如何将Excel数据高效导入数据库?

  • 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 email 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();
            }
        }
    }
}

第三步:运行与验证

  1. 修改路径: 确保 ExcelImporter.java 中的 excelFilePath 变量指向你的 employees.xlsx 文件。
  2. 修改数据库连接: 确保 DBUtil.java 中的URL、用户名和密码正确。
  3. 运行: 运行 ExcelImportermain 方法。
  4. 验证: 检查你的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 (性能最好)、DruidC3P0

添加HikariCP依赖 (Maven):

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.0.1</version>
</dependency>

然后修改 DBUtil.java 来使用HikariCP管理连接。

使用更现代的框架

对于企业级应用,手动编写JDBC代码容易出错且维护成本高,可以考虑使用 MyBatisJPA (Hibernate) 等ORM框架,它们能更优雅地处理SQL映射、参数绑定和结果集。

错误处理与日志

不要使用 System.out.println,应该使用专业的日志框架,如 SLF4J + LogbackLog4j2,将所有错误信息(如哪一行数据出错、具体错误原因)记录到日志文件中,便于后续排查。

处理 .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导入数据库是一个经典任务,其核心流程是:

  1. 读取Excel: 使用Apache POI解析文件流,获取工作表、行和单元格数据。
  2. 建立数据库连接: 使用JDBC或连接池与数据库建立连接。
  3. 准备SQL: 使用 PreparedStatement 防止SQL注入,并利用其批处理功能提高性能。
  4. 数据转换与映射: 将Excel单元格数据(字符串、数字、日期等)转换为Java类型,再设置为 PreparedStatement 的参数。
  5. 执行与提交: 执行批处理操作,并提交事务以保证数据一致性。
  6. 资源清理: 在 finally 块中确保所有资源(文件流、连接、语句等)被正确关闭。

希望这份详细的指南能帮助你顺利完成Excel导入数据库的任务!

分享:
扫描分享到社交APP
上一篇
下一篇