杰瑞科技汇

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

  • Excel 文件读取: Apache POI - 这是 Java 操作 Office 文件(如 .xlsx, .xls)的事实标准。
  • 数据库连接: JDBC - Java 数据库连接,是 Java 访问数据库的基础 API。
  • 数据库: MySQL - 作为示例数据库,但此方法同样适用于 Oracle, SQL Server, PostgreSQL 等。

第一步:环境准备

创建 Java 项目

创建一个标准的 Java Maven 项目,Maven 可以帮助我们非常方便地管理依赖。

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

添加 Maven 依赖

在你的 pom.xml 文件中,添加以下依赖:

<dependencies>
    <!-- Apache POI 用于读取 Excel 文件 -->
    <!-- 注意:poi 和 poi-ooxml 需要同时引入,poi-ooxml 支持 .xlsx 格式 -->
    <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 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    <!-- (可选) Lombok 简化 Java Bean 代码 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

准备数据库和表

在 MySQL 数据库中创建一个目标表,我们假设要导入一个学生信息表。

创建数据库:

CREATE DATABASE IF NOT EXISTS demo_db;
USE demo_db;

创建学生表: 我们假设 Excel 文件中有 id, name, age, email, enrollment_date 这几列。

Java Excel导入数据库,如何高效实现?-图2
(图片来源网络,侵删)
CREATE TABLE IF NOT EXISTS `students` (
  `id` INT PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(100) NOT NULL,
  `age` INT,
  `email` VARCHAR(150) UNIQUE,
  `enrollment_date` DATE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

第二步:准备 Excel 文件

在你的项目中创建一个 students.xlsx 文件,内容如下,并放在 src/main/resources 目录下(方便读取)。

id name age email enrollment_date
1 张三 20 zhangsan@example.com 2025-09-01
2 李四 21 lisi@example.com 2025-09-01
3 王五 22 wangwu@example.com 2025-09-02
4 赵六 23 zhaoliu@example.com 2025-09-03

第三步:编写 Java 代码

我们将代码分为几个部分,使其结构清晰、易于维护。

数据库连接工具类 (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"; // 你的数据库密码
    // 获取数据库连接
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USER, PASSWORD);
    }
    // 关闭资源
    public static void close(Connection conn) {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

实体类 (Student.java)

创建一个与数据库表结构对应的 Java Bean。

Java Excel导入数据库,如何高效实现?-图3
(图片来源网络,侵删)
import lombok.Data;
import java.time.LocalDate;
@Data // Lombok 注解,自动生成 getter, setter, toString 等
public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String email;
    private LocalDate enrollmentDate;
}

Excel 导入核心逻辑 (ExcelImportService.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.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class ExcelImportService {
    public List<Student> parseExcel(InputStream inputStream) throws Exception {
        List<Student> studentList = new ArrayList<>();
        Workbook workbook = new XSSFWorkbook(inputStream); // 使用 XSSFWorkbook 处理 .xlsx 文件
        Sheet sheet = workbook.getSheetAt(0); // 获取第一个工作表
        // 假设第一行是标题,从第二行开始读取数据
        for (int i = 1; i <= sheet.getLastRowNum(); i++) {
            Row row = sheet.getRow(i);
            if (row == null) {
                continue; // 跳过空行
            }
            Student student = new Student();
            // 读取单元格数据并处理
            // 注意:getCell() 的第二个参数是 CellType,如果为空则返回 null
            student.setName(getCellValueAsString(row.getCell(1))); // name
            student.setAge(getCellValueAsInteger(row.getCell(2))); // age
            // 处理日期类型
            Cell dateCell = row.getCell(4);
            if (dateCell != null && dateCell.getCellType() == CellType.NUMERIC) {
                Date javaDate = dateCell.getDateCellValue();
                LocalDate localDate = javaDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                student.setEnrollmentDate(localDate);
            }
            studentList.add(student);
        }
        workbook.close();
        inputStream.close();
        return studentList;
    }
    public void importToDatabase(List<Student> studentList) {
        String sql = "INSERT INTO students (name, age, email, enrollment_date) VALUES (?, ?, ?, ?)";
        // 使用 try-with-resources 确保 Connection 和 PreparedStatement 自动关闭
        try (Connection conn = DBUtil.getConnection();
             // 使用 PreparedStatement 防止 SQL 注入,并且可以批量执行
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            conn.setAutoCommit(false); // 开启事务
            for (Student student : studentList) {
                pstmt.setString(1, student.getName());
                pstmt.setInt(2, student.getAge());
                pstmt.setString(3, student.getEmail());
                // 处理 LocalDate
                if (student.getEnrollmentDate() != null) {
                    pstmt.setDate(4, java.sql.Date.valueOf(student.getEnrollmentDate()));
                } else {
                    pstmt.setNull(4, java.sql.Types.DATE);
                }
                pstmt.addBatch(); // 添加到批量处理中
            }
            int[] results = pstmt.executeBatch(); // 执行批量插入
            conn.commit(); // 提交事务
            System.out.println("成功导入 " + results.length + " 条数据。");
        } catch (SQLException e) {
            System.err.println("数据库导入失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    // 辅助方法:将单元格内容转为字符串
    private String getCellValueAsString(Cell cell) {
        if (cell == null) {
            return null;
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue().trim();
            case NUMERIC:
                // 如果是整数,直接转为 long 再转字符串,避免科学计数法
                if (DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue().toString();
                } else {
                    return String.valueOf((long) cell.getNumericCellValue());
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                return cell.getCellFormula();
            default:
                return null;
        }
    }
    // 辅助方法:将单元格内容转为整数
    private Integer getCellValueAsInteger(Cell cell) {
        String strValue = getCellValueAsString(cell);
        if (strValue != null) {
            try {
                return Integer.parseInt(strValue);
            } catch (NumberFormatException e) {
                System.err.println("无法将 '" + strValue + "' 转换为整数。");
                return null;
            }
        }
        return null;
    }
}

主程序入口 (Main.java)

创建一个主类来调用上面的服务。

import java.io.InputStream;
public class Main {
    public static void main(String[] args) {
        // 1. 从 resources 目录下加载 Excel 文件
        // 使用 ClassLoader 加载资源,可以确保在 JAR 包中也能正确找到文件
        InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("students.xlsx");
        if (inputStream == null) {
            System.err.println("错误:在 resources 目录下未找到 students.xlsx 文件!");
            return;
        }
        ExcelImportService service = new ExcelImportService();
        try {
            // 2. 解析 Excel 文件,得到学生列表
            System.out.println("开始解析 Excel 文件...");
            List<Student> studentList = service.parseExcel(inputStream);
            System.out.println("解析完成,共读取 " + studentList.size() + " 条数据。");
            // 3. 将数据导入数据库
            System.out.println("开始导入数据库...");
            service.importToDatabase(studentList);
            System.out.println("导入完成!");
        } catch (Exception e) {
            System.err.println("处理过程中发生错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

第四步:运行和测试

  1. 确保数据库服务:你的 MySQL 服务必须正在运行。
  2. 修改配置:在 DBUtil.java 中填入你的数据库用户名和密码。
  3. 放置文件:将 students.xlsx 文件放入 src/main/resources 目录。
  4. 运行:运行 Main.javamain 方法。

预期输出:

开始解析 Excel 文件...
解析完成,共读取 4 条数据。
开始导入数据库...
成功导入 4 条数据。
导入完成!

数据库验证: 查询数据库表,可以看到数据已经成功导入。

SELECT * FROM students;

高级主题和最佳实践

错误处理与数据回滚

上面的代码已经包含了基本的 try-catch 和事务管理 (conn.setAutoCommit(false)conn.commit()),如果导入过程中某条数据出错,整个批次都会回滚,保证数据一致性。

性能优化:批量插入

我们使用了 PreparedStatementaddBatch()executeBatch() 方法,这比逐条执行 INSERT 语句要快得多,因为它减少了与数据库的交互次数,对于大量数据(上万行),性能提升非常明显。

大文件处理与内存优化

XSSFWorkbook 会将整个 Excel 文件加载到内存中,如果文件非常大(几百MB甚至上GB),可能会导致 OutOfMemoryError

解决方案:使用 SXSSFWorkbook (流式 API) SXSSFWorkbook 是 POI 提供的用于处理大数据量的 API,它会在写入磁盘时生成临时文件,从而将内存占用降到最低。

修改 ExcelImportService 中的解析部分:

// ... 其他代码不变
// Workbook workbook = new XSSFWorkbook(inputStream); // 旧方式
// 新方式:SXSSFWorkbook
SXSSFWorkbook workbook = new SXSSFWorkbook(new XSSFWorkbook(inputStream));
int windowSize = 100; // 内存中保留的行数,其余写入临时文件
workbook.setWindowSize(windowSize);
// ... 后续代码不变

注意:SXSSFWorkbook 主要用于 写入 Excel,对于 读取 大文件,POI 目前没有完美的流式 API,对于读取,可以考虑分块读取或者使用更专业的库,但对于大多数业务场景,现代机器的内存足以处理几十MB的 Excel 文件。

日志记录

使用 SLF4J + Logback 等日志框架代替 System.out.println,可以更好地管理日志级别、输出格式和文件。

更健壮的单元格类型处理

getCellValueAsString 方法已经处理了多种类型,但在实际业务中,你可能需要更严格的校验,

  • 年龄必须是正整数。
  • Email 必须符合格式。
  • 某些列不能为空。

你可以在 parseExcel 方法中增加校验逻辑,如果数据不合法,可以跳过该行或记录错误信息。

这个指南为你提供了一个从零开始、功能完整、结构清晰的 Java Excel 导入数据库的解决方案,你可以根据自己项目的具体需求进行修改和扩展。

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