杰瑞科技汇

Java如何实现Excel数据导入数据库?

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

Java如何实现Excel数据导入数据库?-图1
(图片来源网络,侵删)

第一步:环境准备

  1. Java Development Kit (JDK): 确保你已经安装了 JDK 8 或更高版本。

  2. Excel 文件 (示例): 假设我们有一个名为 users.xlsx 的 Excel 文件,内容如下:

id name email 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` 目录下,方便作为资源文件访问。
  1. 数据库 (示例): 我们使用 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='用户表';
  2. 添加 Maven 依赖: 在你的 pom.xml 文件中添加以下依赖。

    Java如何实现Excel数据导入数据库?-图2
    (图片来源网络,侵删)
    <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);
    }
}

第三步:运行与验证

  1. 放置文件: 将你的 users.xlsx 文件复制到项目的 src/main/resources 目录下。
  2. 检查配置: 确保 DBUtil.java 中的数据库 URL、用户名和密码是正确的。
  3. 运行: 运行 Main 类的 main 方法。
  4. 查看结果:
    • 控制台会打印出导入的记录数,成功导入 3 条数据!
    • 连接到你的 MySQL 数据库,查询 t_user 表,检查数据是否已成功导入。

第四步:高级技巧与最佳实践

上面的代码已经是一个很好的基础,但在生产环境中,你还需要考虑更多。

性能优化:批处理

代码中已经使用了 PreparedStatementaddBatch()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>

然后使用 XSSFReaderSheetContentsHandler 接口来实现,这比 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 导入数据到数据库是一个综合性的任务,涉及文件解析、数据库操作、事务管理等多个方面。

  1. 基础实现: 使用 Apache POI 读取 Excel,JDBC 操作数据库,并使用 事务 保证数据一致性。
  2. 核心优化: 使用 批处理 提升性能。
  3. 健壮性增强: 增加数据校验和完善的错误处理
  4. 进阶考虑: 对于大文件,采用 SAX 模式;对于灵活性需求,构建动态 SQL;对于大型项目,选择成熟的框架

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

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