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

引言:为什么需要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 的实现思路和代码片段,满足不同场景的需求。

实战准备:环境与依赖
在开始编码前,请确保你的开发环境已准备就绪。
- JDK: 1.8 或更高版本。
- IDE: IntelliJ IDEA 或 Eclipse。
- MySQL: 数据库服务。
- 构建工具: 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(数据访问对象)层结构。

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语法确保Workbook和InputStream被自动关闭。 - 单元格解析:
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,解决方案是 分批次处理。
- 调整JVM内存:在启动参数中增加
-Xmx和-Xms的值,-Xmx1024m -Xms512m。 - 分批次读取和插入:
- 修改
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两种主流方案的实践指南。
核心要点回顾:
- 明确需求:根据文件大小和性能要求选择POI或EasyExcel。
- 分层架构:采用Controller-Service-DAO模式,使代码结构清晰、易于维护。
- 事务与批处理:使用JDBC事务和批处理功能保证数据一致性和操作效率。
- 内存管理:处理大文件时,务必采用分批策略或使用EasyExcel等流式解析工具。
- 编码规范:统一字符集,做好数据校验和异常处理。
希望这份详尽的指南能帮助你顺利解决项目中遇到的Excel导入问题,提升开发效率,如果你有任何疑问或更好的实践,欢迎在评论区交流讨论!
