杰瑞科技汇

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

  • Excel处理: Apache POI (支持 .xls.xlsx 格式)
  • 数据库连接: JDBC (以 MySQL 为例,其他数据库类似)
  • 构建工具: Maven (用于管理依赖)

第一步:环境准备与依赖引入

你需要一个Java项目,并添加必要的依赖。

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

创建Maven项目

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

添加pom.xml依赖

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

<dependencies>
    <!-- 1. Apache POI: 用于操作Excel文件 -->
    <!-- 注意:需要同时引入 poi, poi-ooxml 和 poi-scratchpad 来全面支持 .xls 和 .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>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>5.2.3</version>
    </dependency>
    <!-- 2. MySQL Connector/J: 用于连接MySQL数据库 -->
    <!-- 请根据你的数据库版本选择合适的版本 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    <!-- 3. (可选但推荐) SLF4J 日志门面和 Logback 实现 -->
    <!-- POI 5.x 版本开始强制要求 SLF4J -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
    </dependency>
</dependencies>

提示: poi-ooxml 依赖了 ooxml-lite,它内部使用 org.apache.xmlbeans 来处理 .xlsx 格式,如果你的项目里没有冲突,这是最简单的方式。


第二步:准备数据库和Excel文件

创建数据库和表

假设我们要导入一个包含用户信息的Excel文件,我们先在MySQL中创建对应的表。

Java如何将Excel数据高效导入数据库?-图2
(图片来源网络,侵删)
-- 创建数据库
CREATE DATABASE IF NOT EXISTS excel_import_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 使用数据库
USE excel_import_db;
-- 创建用户表
CREATE TABLE `user` (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` VARCHAR(50) NOT NULL COMMENT '姓名',
  `age` INT COMMENT '年龄',
  `email` VARCHAR(100) COMMENT '邮箱',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';

准备Excel文件

在项目根目录下创建一个 files 文件夹,并放入一个名为 users.xlsx 的Excel文件,内容如下:

姓名 年龄 邮箱
张三 25 zhangsan@example.com
李四 30 lisi@example.com
王五 28 wangwu@example.com
(空行)
赵六 35 zhaoliu@example.com

注意:

  • 第一行是标题行,我们将用它来映射数据库字段。
  • 包含空行,这是测试代码健壮性的好方法。
  • 编码: 确保Excel文件保存为 UTF-8 编码,以避免中文乱码。

第三步:Java代码实现

我们将分步骤编写代码,使其清晰易懂。

数据库配置工具类

创建一个 DBUtil 类来管理数据库连接,避免代码重复。

Java如何将Excel数据高效导入数据库?-图3
(图片来源网络,侵删)
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
    // 数据库连接信息
    private static final String URL = "jdbc:mysql://localhost:3306/excel_import_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();
            }
        }
    }
}

Excel导入核心逻辑

这是实现导入功能的核心类,我们将实现一个方法,接收Excel文件路径,然后逐行读取并插入数据库。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
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.Date;
public class ExcelImportService {
    public void importExcel(String excelFilePath) {
        Connection conn = null;
        FileInputStream fis = null;
        Workbook workbook = null;
        try {
            // 1. 获取数据库连接
            conn = DBUtil.getConnection();
            // 关闭自动提交,使用事务
            conn.setAutoCommit(false);
            // 2. 加载Excel文件
            fis = new FileInputStream(excelFilePath);
            // 根据文件版本选择不同的Workbook实现
            // .xlsx -> XSSFWorkbook, .xls -> HSSFWorkbook
            workbook = new XSSFWorkbook(fis);
            // 3. 获取第一个工作表 (Sheet)
            Sheet sheet = workbook.getSheetAt(0);
            // 4. 获取第一行作为标题行,用于获取列名
            Row headerRow = sheet.getRow(0);
            if (headerRow == null) {
                System.out.println("Excel文件为空或没有标题行!");
                return;
            }
            // 5. 准备SQL语句 (使用PreparedStatement防止SQL注入)
            // 我们使用 ? 占位符,后续会根据Excel列数动态设置
            String sql = "INSERT INTO user (name, age, email, create_time) VALUES (?, ?, ?, ?)";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            // 6. 遍历数据行 (从第二行开始,索引为1)
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                // 跳过空行
                if (row == null) {
                    System.out.println("第 " + (i + 1) + " 行是空行,已跳过。");
                    continue;
                }
                // 7. 获取单元格数据并处理
                // 姓名
                Cell nameCell = row.getCell(0);
                String name = getCellValueAsString(nameCell);
                if (name == null || name.trim().isEmpty()) {
                    System.out.println("第 " + (i + 1) + " 行姓名为空,已跳过。");
                    continue; // 如果关键字段为空,可以选择跳过或报错
                }
                // 年龄
                Cell ageCell = row.getCell(1);
                int age = 0;
                try {
                    age = (int) getCellValueAsDouble(ageCell);
                } catch (Exception e) {
                    System.out.println("第 " + (i + 1) + " 行年龄格式不正确,默认设为0。");
                }
                // 邮箱
                Cell emailCell = row.getCell(2);
                String email = getCellValueAsString(emailCell);
                // 设置创建时间
                String createTime = sdf.format(new Date());
                // 8. 设置PreparedStatement参数并执行
                pstmt.setString(1, name);
                pstmt.setInt(2, age);
                pstmt.setString(3, email);
                pstmt.setString(4, createTime);
                pstmt.addBatch(); // 添加到批量中
                System.out.println("正在处理第 " + (i + 1) + " 行: " + name);
            }
            // 9. 执行批量插入
            int[] result = pstmt.executeBatch();
            System.out.println("成功插入 " + result.length + " 条数据。");
            // 10. 提交事务
            conn.commit();
            System.out.println("数据导入成功!");
        } catch (Exception e) {
            // 发生异常时回滚事务
            if (conn != null) {
                try {
                    conn.rollback();
                    System.out.println("发生异常,事务已回滚。");
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            // 11. 关闭所有资源
            DBUtil.close(conn);
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (workbook != null) {
                try {
                    workbook.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * 将单元格的值转换为字符串
     * 处理了数字、字符串、布尔值、日期等类型
     */
    private String getCellValueAsString(Cell cell) {
        if (cell == null) {
            return null;
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue().trim();
            case NUMERIC:
                // 如果是日期格式
                if (DateUtil.isCellDateFormatted(cell)) {
                    return new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
                } else {
                    // 普通数字,避免科学计数法
                    return String.valueOf((long) cell.getNumericCellValue());
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                // 公式计算结果
                try {
                    return String.valueOf(cell.getNumericCellValue());
                } catch (Exception e) {
                    return cell.getStringCellValue();
                }
            case BLANK:
                return null;
            default:
                return null;
        }
    }
    /**
     * 将单元格的值转换为Double,方便处理数字类型的字段
     */
    private double getCellValueAsDouble(Cell cell) {
        if (cell == null) {
            return 0.0;
        }
        switch (cell.getCellType()) {
            case NUMERIC:
                return cell.getNumericCellValue();
            case STRING:
                try {
                    return Double.parseDouble(cell.getStringCellValue().trim());
                } catch (NumberFormatException e) {
                    return 0.0;
                }
            case BOOLEAN:
                return cell.getBooleanCellValue() ? 1.0 : 0.0;
            default:
                return 0.0;
        }
    }
}

主程序入口

创建一个 Main 类来运行导入服务。

public class Main {
    public static void main(String[] args) {
        // Excel文件路径
        String excelPath = "files/users.xlsx";
        ExcelImportService importService = new ExcelImportService();
        importService.importExcel(excelPath);
    }
}

第四步:运行与验证

  1. 运行程序: 运行 Main 类的 main 方法。
  2. 观察控制台输出: 你会看到程序逐行处理数据,并打印出日志信息。
  3. 检查数据库: 连接到你的MySQL数据库,查询 user 表,确认数据是否已正确导入。

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

上面的代码是基础版本,在实际项目中,你需要考虑更多。

使用事务确保数据一致性

代码中已经使用了 conn.setAutoCommit(false)conn.commit()/conn.rollback(),这是非常重要的一步,它能保证要么所有数据都成功导入,要么在出错时全部回滚,避免数据部分导入导致的不一致。

批量插入 (addBatch / executeBatch)

对于大量数据(如上万行),使用 PreparedStatement 的批量插入功能可以极大地提高性能,它会将多个SQL语句打包在一起发送给数据库执行,减少了网络IO和数据库解析的开销。

错误处理与日志

  • 空行/无效行处理: 代码中已经演示了如何跳过空行或关键字段为空的行。
  • 数据类型转换: getCellValueAsString 方法处理了Excel中常见的各种数据类型,防止因类型不匹配导致的程序崩溃。
  • 日志记录: 使用 SLF4JLog4j 等日志框架来记录操作日志,比 System.out.println 更专业,可以记录到文件,并控制日志级别。

内存优化

对于超大Excel文件(如几百MB甚至上GB),一次性将整个文件加载到内存(new XSSFWorkbook(fis))会导致 OutOfMemoryError

解决方案:使用 SXSSFWorkbook (流API) SXSSFWorkbookPOI 提供的用于处理大数据量的API,它基于 XSSFWorkbook,但会将不活跃的行数据写入临时文件(硬盘),从而保持内存在一个较低的水平。

示例代码片段 (使用 SXSSFWorkbook):

// 对于读取,SXSSFWorkbook 和 XSSFWorkbook 用法基本一致
// 主要区别在于写入超大文件时
// Workbook workbook = new SXSSFWorkbook(new XSSFWorkbook(fis)); // 不常用,读取时通常还是用XSSFWorkbook
// 更常见的场景是生成超大Excel文件时使用SXSSFWorkbook

对于读取操作,如果文件真的超大,通常需要先检查文件大小,如果超过阈值,则提示用户分批处理或使用更专业的工具。

性能对比

方法 优点 缺点 适用场景
逐条插入 代码简单,逻辑清晰 性能极差,N+1次IO 仅用于测试或导入极少量数据(<10条)
批量插入 性能良好,代码改动小 内存占用随行数线性增长 推荐使用,适用于大多数场景(<10万行)
SXSSFWorkbook 内存占用极低,可处理海量数据 代码稍复杂,依赖硬盘IO 处理超大Excel文件(>10万行或文件>100MB)

上传功能整合

在Web应用(如Spring Boot)中,通常需要一个文件上传接口,接收用户上传的Excel文件,然后调用上述导入服务。

Spring Boot 示例:

// Controller
@PostMapping("/upload")
public ResponseEntity<String> uploadExcel(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) {
        return ResponseEntity.badRequest().body("请选择文件");
    }
    String originalFilename = file.getOriginalFilename();
    // ... (做一些安全检查,如文件类型、大小等)
    try {
        // 将上传的文件保存到临时目录
        String tempPath = "/tmp/" + originalFilename;
        file.transferTo(new File(tempPath));
        // 调用导入服务
        excelImportService.importExcel(tempPath);
        // 导入成功后,删除临时文件
        Files.deleteIfExists(Paths.get(tempPath));
        return ResponseEntity.ok("文件上传并导入成功!");
    } catch (Exception e) {
        e.printStackTrace();
        return ResponseEntity.internalServerError().body("导入失败: " + e.getMessage());
    }
}

通过以上步骤,你已经掌握了在Java中将Excel导入数据库的核心技术,关键点在于:

  1. 选择合适的工具: Apache POI 是处理Excel的行业标准。
  2. 使用JDBC和事务: 保证数据操作的原子性和一致性。
  3. 采用批量插入: 显著提升大数据量导入的性能。
  4. 健壮的错误处理: 确保程序能应对各种异常情况,如空行、格式错误等。
  5. 考虑性能边界: 对于超大文件,要采用 SXSSFWorkbook 等内存优化策略。

希望这份详细的指南对你有帮助!

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