杰瑞科技汇

Java Excel导入MySQL,如何高效实现?

Java实现Excel导入MySQL数据库:从零到一的完整实战指南(含代码与避坑)

** 本文为Java开发者提供了一份详尽的Excel数据导入MySQL数据库的实战教程,内容涵盖技术选型、环境搭建、核心代码实现(包括Apache POI和EasyExcel两种主流方案)、异常处理、性能优化以及常见问题(如大数据量、日期格式、中文乱码)的解决方案,助你轻松搞定数据导入功能。

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

引言:为什么需要Java实现Excel导入MySQL?

在企业级应用开发中,数据导入导出是一项高频需求,无论是批量录入用户信息、上传产品库存,还是导入财务报表,Excel因其通用性和便捷性,成为了数据交换的“事实标准”。

将Java与Excel、MySQL结合,我们可以构建出稳定、高效的数据处理流程,这不仅解放了人工录入的繁琐,还能通过程序对导入的数据进行校验和清洗,确保数据质量,本文将带你深入探索如何使用Java技术栈,优雅地完成从Excel文件到MySQL数据库的整个导入过程。


技术选型:主流工具对比与选择

在开始编码前,选择合适的工具至关重要,Java操作Excel主要有两大阵营:

工具名称 优点 缺点 适用场景
Apache POI 功能强大,支持所有Excel版本(.xls, .xlsx),API成熟稳定。 内存消耗大,处理超大文件时容易导致OOM(OutOfMemoryError)。 功能全面,需要兼容旧版Excel,文件大小适中的项目。
EasyExcel 阿里巴巴开源,基于SAX模型解析,内存占用极低,性能优异。 功能相对POI稍弱,对复杂格式(如复杂合并单元格)支持有限。 大数据量导入导出,对内存和性能有高要求的场景。
  • 追求功能全面、兼容性好:选择 Apache POI
  • 追求高性能、低内存、处理大文件:选择 EasyExcel

本文将以 Apache POI 为主进行详细讲解,并在文末提供 EasyExcel 的实现思路和代码片段,满足不同场景的需求。

Java Excel导入MySQL,如何高效实现?-图2
(图片来源网络,侵删)

实战准备:环境与依赖

在开始编码前,请确保你的开发环境已准备就绪。

  1. JDK: 1.8 或更高版本。
  2. IDE: IntelliJ IDEA 或 Eclipse。
  3. MySQL: 数据库服务。
  4. 构建工具: Maven 或 Gradle。

1 Maven 依赖配置

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

<!-- Apache POI 核心依赖 -->
<dependencies>
    <!-- POI for .xls -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.3</version>
    </dependency>
    <!-- POI for .xlsx -->
    <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 (简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

2 MySQL 数据库表结构

我们创建一个简单的 user 表作为示例。

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(50) NOT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

核心代码实现:Apache POI + JDBC

我们将分步实现一个完整的Controller、Service、DAO(数据访问对象)层结构。

Java Excel导入MySQL,如何高效实现?-图3
(图片来源网络,侵删)

1 实体类

创建一个与 user 表对应的Java实体类。

// User.java
import lombok.Data;
import java.util.Date;
@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private Date createTime;
}

2 DAO层:数据访问

DAO层负责与数据库交互,实现数据的持久化。

// UserDao.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
public class UserDao {
    // 数据库连接配置 (实际项目中应使用配置文件或连接池)
    private static final String URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8";
    private static final String USER = "root";
    private static final String PASSWORD = "your_password";
    public void batchInsert(List<User> userList) {
        String sql = "INSERT INTO user (name, age, email) VALUES (?, ?, ?)";
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            // 1. 获取数据库连接
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
            // 2. 关闭自动提交,开启事务
            conn.setAutoCommit(false);
            // 3. 创建预编译语句
            pstmt = conn.prepareStatement(sql);
            // 4. 设置参数并添加到批处理
            for (User user : userList) {
                pstmt.setString(1, user.getName());
                pstmt.setInt(2, user.getAge());
                pstmt.setString(3, user.getEmail());
                pstmt.addBatch(); // 添加到批处理
            }
            // 5. 执行批处理
            pstmt.executeBatch();
            // 6. 提交事务
            conn.commit();
        } catch (SQLException e) {
            // 发生异常时回滚事务
            try {
                if (conn != null) {
                    conn.rollback();
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            // 7. 释放资源
            try {
                if (pstmt != null) pstmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

关键点:

  • 事务管理conn.setAutoCommit(false)conn.commit()/conn.rollback() 确保数据一致性。
  • 批处理addBatch()executeBatch() 大幅提升插入性能,避免了循环执行单条SQL的开销。

3 Service层:业务逻辑

Service层负责调用DAO层,处理业务逻辑,如数据校验。

// UserService.java
import org.apache.poi.ss.usermodel.*;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class UserService {
    private UserDao userDao = new UserDao();
    public String importExcel(InputStream inputStream, String fileName) {
        List<User> userList = new ArrayList<>();
        try (Workbook workbook = WorkbookFactory.create(inputStream)) {
            // 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);
            // 从第二行开始遍历(假设第一行是标题)
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (row == null) continue;
                User user = new User();
                // 获取单元格,并处理可能的空值
                user.setName(getCellValueAsString(row.getCell(0)));
                user.setAge(getCellValueAsInteger(row.getCell(1)));
                user.setEmail(getCellValueAsString(row.getCell(2)));
                // 简单校验
                if (user.getName() == null || user.getName().isEmpty()) {
                    return "第 " + (i + 1) + " 行:姓名不能为空!";
                }
                userList.add(user);
            }
            // 调用DAO层批量插入
            userDao.batchInsert(userList);
            return "导入成功!共导入 " + userList.size() + " 条数据。";
        } catch (Exception e) {
            e.printStackTrace();
            return "导入失败:" + e.getMessage();
        }
    }
    // 辅助方法:将单元格值转为String
    private String getCellValueAsString(Cell cell) {
        if (cell == null) {
            return null;
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    return String.valueOf(cell.getDateCellValue());
                } else {
                    return String.valueOf((long) cell.getNumericCellValue());
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                return cell.getCellFormula();
            default:
                return null;
        }
    }
    // 辅助方法:将单元格值转为Integer
    private Integer getCellValueAsInteger(Cell cell) {
        if (cell == null) {
            return null;
        }
        if (cell.getCellType() == CellType.NUMERIC) {
            return (int) cell.getNumericCellValue();
        }
        return null;
    }
}

关键点:

  • 资源关闭:使用 try-with-resources 语法确保 WorkbookInputStream 被自动关闭。
  • 单元格解析getCellValueAsString 方法处理了不同类型的单元格(字符串、数字、日期等),并考虑了空值情况。
  • 数据校验:在插入前进行简单的业务校验,如“姓名不能为空”。

4 Controller层:接口暴露

Controller层接收前端传来的Excel文件,调用Service层处理。

// UserController.java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.InputStream;
@RestController
public class UserController {
    @Resource // 或 @Autowired
    private UserService userService;
    @PostMapping("/import")
    public String importUsers(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return "请选择要上传的文件!";
        }
        try (InputStream inputStream = file.getInputStream()) {
            String fileName = file.getOriginalFilename();
            return userService.importExcel(inputStream, fileName);
        } catch (Exception e) {
            e.printStackTrace();
            return "文件处理失败:" + e.getMessage();
        }
    }
}

关键点:

  • 文件接收:使用 MultipartFile 接收前端上传的文件。
  • 流处理:将 MultipartFile 转换为 InputStream 传递给Service层。

性能优化与常见问题(避坑指南)

在真实项目中,你可能会遇到各种挑战。

1 性能优化:百万级数据导入

当Excel文件包含数十万甚至上百万行数据时,直接全部加载到内存会导致OOM,解决方案是 分批次处理

  1. 调整JVM内存:在启动参数中增加 -Xmx-Xms 的值,-Xmx1024m -Xms512m
  2. 分批次读取和插入
    • 修改 UserService,设定一个批次大小(如 BATCH_SIZE = 1000)。
    • 在循环中,每读取 BATCH_SIZE 条数据,就调用一次 userDao.batchInsert(),然后清空当前批次列表。
    • 这样可以显著降低单次数据库操作的压力和内存峰值。

2 日期格式问题

Excel中的日期可能是数字格式(如 44927 代表 2025/1/1),也可能被格式化为字符串。DateUtil.isCellDateFormatted(cell) 是判断是否为日期格式的关键。

  • 解决方案:在 getCellValueAsString 方法中,如果判断是日期,就使用 cell.getDateCellValue() 获取 Date 对象,再进行格式化或存储。

3 中文乱码问题

乱码通常由字符编码不一致引起。

  • 数据库层面:确保MySQL数据库、表、字段的字符集均为 utf8mb4
  • JDBC连接串:在URL中明确指定字符集,如 ?characterEncoding=UTF-8
  • Excel文件本身:确保Excel文件是UTF-8编码保存的,POI在读取时会尝试处理,但明确指定总没错。

4 数据库连接池

在上述DAO示例中,我们使用了原生JDBC,每次操作都创建新连接,性能低下。务必使用数据库连接池,如 HikariCP、Druid 或 C3P0。

只需在 pom.xml 中添加HikariCP依赖,并修改DAO层代码使用连接池即可。

<!-- HikariCP 依赖 -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.0.1</version>
</dependency>

EasyExcel 实现方案(高性能版)

如果你选择了EasyExcel,其核心思想是 逐行解析,不会将整个文件加载到内存。

1 Maven 依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.1</version>
</dependency>

2 核心代码

使用EasyExcel的 AnalysisEventListener 来监听解析过程。

// EasyExcelListener.java
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次new出来,然后里面用到spring可以构造方法传进去
public class EasyExcelListener extends AnalysisEventListener<User> {
    // 存储解析的数据
    private List<User> dataList = new ArrayList<>();
    // 可以注入你的Service或DAO
    // private UserService userService;
    // public EasyExcelListener(UserService userService) {
    //     this.userService = userService;
    // }
    @Override
    public void invoke(User user, AnalysisContext context) {
        // 每解析一行,就会调用一次此方法
        dataList.add(user);
        // 达到BATCH_SIZE,就进行一次批量插入
        if (dataList.size() >= 1000) {
            saveData();
            dataList.clear();
        }
    }
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 所有数据解析完毕,处理剩余数据
        saveData();
        System.out.println("所有数据解析完成!");
    }
    private void saveData() {
        if (!dataList.isEmpty()) {
            // 调用DAO或Service进行批量插入
            // userDao.batchInsert(dataList);
            System.out.println("存入数据库 " + dataList.size() + " 条数据。");
        }
    }
}

3 Controller调用

@PostMapping("/import/easyexcel")
public String importUsersEasyExcel(@RequestParam("file") MultipartFile file) {
    try {
        // 这里需要指定一个实体类User,EasyExcel会根据注解自动映射
        EasyExcel.read(file.getInputStream(), User.class, new EasyExcelListener()).sheet().doRead();
        return "EasyExcel导入成功!";
    } catch (Exception e) {
        return "EasyExcel导入失败:" + e.getMessage();
    }
}

EasyExcel优势:代码更简洁,内存占用极低,天然支持大文件流式读取,无需手动分批。


本文系统地讲解了使用Java实现Excel导入MySQL的完整流程,从技术选型、环境搭建,到分层代码实现,再到性能优化和常见问题解决,提供了Apache POI和EasyExcel两种主流方案的实践指南。

核心要点回顾:

  1. 明确需求:根据文件大小和性能要求选择POI或EasyExcel。
  2. 分层架构:采用Controller-Service-DAO模式,使代码结构清晰、易于维护。
  3. 事务与批处理:使用JDBC事务和批处理功能保证数据一致性和操作效率。
  4. 内存管理:处理大文件时,务必采用分批策略或使用EasyExcel等流式解析工具。
  5. 编码规范:统一字符集,做好数据校验和异常处理。

希望这份详尽的指南能帮助你顺利解决项目中遇到的Excel导入问题,提升开发效率,如果你有任何疑问或更好的实践,欢迎在评论区交流讨论!

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