杰瑞科技汇

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

  1. EasyExcel: 阿里巴巴开源,API 简单,性能优秀,内存占用低,是目前推荐的首选。
  2. Apache POI: 老牌 Java 操作 Office 文件的库,功能强大,但在处理大文件时内存消耗较高。

本文将以 EasyExcel 为核心进行讲解,因为它更符合现代应用的需求。

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

整体步骤概览

  1. 环境准备: 创建项目,添加必要的依赖。
  2. 准备数据库和表: 创建目标数据表。
  3. 准备 Excel 文件: 创建一个示例 .xlsx 文件。
  4. 编写 Java 代码:
    • 定义一个与 Excel 行数据对应的 Java 实体类。
    • 创建一个监听器,用于在读取 Excel 时逐行处理数据。
    • 编写主程序,启动 Excel 读取,并通过监听器将数据写入数据库。
  5. 运行与验证: 执行代码,检查数据库中的数据。

第一步:环境准备

创建 Maven 项目

如果你使用的是 IDE (如 IntelliJ IDEA 或 Eclipse),可以直接创建一个 Maven 项目。

添加依赖

pom.xml 文件中添加以下依赖,你需要 EasyExcel、JDBC 驱动(以 MySQL 为例)以及一个数据库连接池(如 HikariCP,性能优秀)。

<dependencies>
    <!-- EasyExcel -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>3.3.2</version> <!-- 请使用最新版本 -->
    </dependency>
    <!-- MySQL JDBC Driver -->
    <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 (可选,用于简化实体类代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.28</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

第二步:准备数据库和表

假设我们要导入的是用户信息,创建一个 user 表。

CREATE DATABASE IF NOT EXISTS demo_db;
USE demo_db;
CREATE TABLE `user` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(100) NOT NULL COMMENT '姓名',
  `gender` VARCHAR(10) DEFAULT NULL COMMENT '性别',
  `age` INT DEFAULT NULL COMMENT '年龄',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

第三步:准备 Excel 文件

创建一个名为 users.xlsx 的 Excel 文件,内容如下。第一行必须是表头

Java如何高效导入Excel数据到数据库?-图2
(图片来源网络,侵删)
姓名 性别 年龄
张三 25
李四 30
王五 28
赵六 35

第四步:编写 Java 代码

我们将代码分为几个部分,结构清晰。

实体类

创建一个 User.java 类,其字段名需要与 Excel 的表头列名完全一致(不区分大小写,但推荐保持一致),EasyExcel 会自动根据列名进行映射。

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
@Data // Lombok 注解,自动生成 getter, setter, toString 等
public class User {
    // ExcelProperty 的 value 值必须与 Excel 表头列名一致
    @ExcelProperty("姓名")
    private String name;
    @ExcelProperty("性别")
    private String gender;
    @ExcelProperty("年龄")
    private Integer age;
    // 数据库中的创建时间,Excel 文件中没有,我们可以在代码中设置
    private Date createTime;
}

数据库连接工具类

创建一个 JdbcUtils.java 用于管理数据库连接。

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class JdbcUtils {
    private static HikariDataSource dataSource;
    static {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8");
        config.setUsername("root"); // 你的数据库用户名
        config.setPassword("your_password"); // 你的数据库密码
        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
        // 连接池配置 (可选)
        config.setMaximumPoolSize(10);
        config.setMinimumIdle(5);
        config.setConnectionTimeout(30000); // 30 seconds
        config.setIdleTimeout(600000); // 10 minutes
        config.setMaxLifetime(1800000); // 30 minutes
        dataSource = new HikariDataSource(config);
    }
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    public static void closeConnection(Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

Excel 监听器

这是整个流程的核心,监听器会在 EasyExcel 读取每一行数据时被调用,我们在这里实现数据库的写入逻辑。

Java如何高效导入Excel数据到数据库?-图3
(图片来源网络,侵删)
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Date;
// 有参构造函数中传入 Connection,确保每次导入都使用同一个连接
public class UserExcelListener extends AnalysisEventListener<User> {
    private static final Logger LOGGER = LoggerFactory.getLogger(UserExcelListener.class);
    private final Connection connection;
    // 使用 PreparedStatement 批量插入,提高性能
    private final PreparedStatement preparedStatement;
    // 每批处理的数量
    private static final int BATCH_SIZE = 100;
    // 用于暂存数据的列表
    private List<User> cachedDataList = new ArrayList<>(BATCH_SIZE);
    public UserExcelListener(Connection connection) throws SQLException {
        this.connection = connection;
        // 关闭自动提交,开启事务
        connection.setAutoCommit(false);
        // 准备 SQL 语句
        String sql = "INSERT INTO user (name, gender, age, create_time) VALUES (?, ?, ?, ?)";
        this.preparedStatement = connection.prepareStatement(sql);
    }
    /**
     * 这个方法会逐行调用
     */
    @Override
    public void invoke(User user, AnalysisContext context) {
        LOGGER.info("解析到一条数据: {}", user);
        // 设置创建时间
        user.setCreateTime(new Date());
        cachedDataList.add(user);
        // 达到 BATCH_SIZE 时,触发批量存储
        if (cachedDataList.size() >= BATCH_SIZE) {
            saveData();
            cachedDataList.clear();
        }
    }
    /**
     * 所有数据解析完成后调用
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 确保剩余的数据也被保存
        if (!cachedDataList.isEmpty()) {
            saveData();
        }
        LOGGER.info("所有数据解析完成!");
        try {
            // 提交事务
            connection.commit();
        } catch (SQLException e) {
            try {
                // 发生异常,回滚事务
                connection.rollback();
                LOGGER.error("事务回滚", e);
            } catch (SQLException ex) {
                LOGGER.error("回滚失败", ex);
            }
        } finally {
            JdbcUtils.closeConnection(connection);
        }
    }
    /**
     * 批量保存数据
     */
    private void saveData() {
        try {
            for (User user : cachedDataList) {
                preparedStatement.setString(1, user.getName());
                preparedStatement.setString(2, user.getGender());
                preparedStatement.setInt(3, user.getAge());
                preparedStatement.setTimestamp(4, new java.sql.Timestamp(user.getCreateTime().getTime()));
                preparedStatement.addBatch();
            }
            // 执行批量更新
            int[] results = preparedStatement.executeBatch();
            LOGGER.info("成功插入 {} 条数据", results.length);
        } catch (SQLException e) {
            LOGGER.error("批量保存数据失败", e);
            // 如果批量插入失败,可以尝试单条插入或直接抛出异常
            // 这里简单处理,打印日志
        }
    }
}

主程序

编写 Main.java 来启动整个导入过程。

import com.alibaba.excel.EasyExcel;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
public class Main {
    public static void main(String[] args) {
        String fileName = "D:/path/to/your/users.xlsx"; // 替换成你的 Excel 文件路径
        // 1. 获取数据库连接
        Connection connection = null;
        try {
            connection = JdbcUtils.getConnection();
            System.out.println("数据库连接成功!");
            // 2. 构建 Excel 监听器,并将连接传入
            UserExcelListener listener = new UserExcelListener(connection);
            // 3. 读取 Excel 文件
            // EasyExcel.read(fileName, User.class, listener).sheet().doRead();
            // 如果文件在 resources 目录下,可以这样读取
            InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("users.xlsx");
            if (inputStream == null) {
                System.err.println("未找到 Excel 文件,请检查路径是否正确!");
                return;
            }
            EasyExcel.read(inputStream, User.class, listener).sheet().doRead();
            System.out.println("Excel 导入数据库完成!");
        } catch (SQLException e) {
            System.err.println("数据库操作失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

第五步:运行与验证

  1. 确保路径正确: 修改 Main.javafileName 的值为你的 users.xlsx 的绝对路径,或者将 users.xlsx 文件放到 src/main/resources 目录下,并使用 getResourceAsStream 方式读取(如示例代码所示)。
  2. 配置数据库信息: 修改 JdbcUtils.java 中的数据库用户名、密码等信息。
  3. 运行: 运行 Main.javamain 方法。
  4. 验证: 查看你的 MySQL 数据库 demo_db 中的 user 表,数据应该已经被成功导入。

你还会在控制台看到类似以下的日志输出:

数据库连接成功!
解析到一条数据: User(name=张三, gender=男, age=25, createTime=...)
解析到一条数据: User(name=李四, gender=女, age=30, createTime=...)
...
成功插入 100 条数据
所有数据解析完成!
Excel 导入数据库完成!

最佳实践与注意事项

  1. 大文件处理: EasyExcel 采用 SAX 模式(事件驱动)读取,不会一次性将整个 Excel 文件加载到内存,因此可以处理非常大的文件(如几百兆甚至上 G),而 Apache POI 的 XSSF 模式是 DOM 模式,会将文件全部读入内存,容易导致 OOM(内存溢出)。
  2. 事务管理: 在监听器中,我们手动管理了事务(connection.setAutoCommit(false)connection.commit()/rollback()),这非常重要,可以保证一批次的数据要么全部成功,要么全部失败,保证数据一致性。
  3. 批量插入: 使用 PreparedStatementaddBatch()executeBatch() 方法,可以极大地提升数据库写入性能,减少网络 I/O 次数。
  4. 数据校验: 在实际应用中,你可能需要对 Excel 中的数据进行校验(如非空、格式、长度等),可以在 invoke 方法中增加校验逻辑,如果数据不合法,可以跳过该行或记录错误信息。
  5. 错误处理: 增加更完善的异常处理和日志记录,以便在导入失败时能够快速定位问题。
  6. 性能优化:
    • 调整 BATCH_SIZE: BATCH_SIZE 是一个重要的性能调优参数,你可以根据数据量和服务器性能进行调整,通常在 100 到 1000 之间。
    • 使用连接池: 我们已经使用了 HikariCP,这是目前性能最好的连接池之一。
  7. 复杂表头: Excel 表头有多行或复杂的合并单元格,EasyExcel 也支持,需要使用 Head 相关的注解和配置,相对复杂一些。
分享:
扫描分享到社交APP
上一篇
下一篇