杰瑞科技汇

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

Excel导入数据库Java全攻略:从零开始的详细教程与最佳实践

告别手动录入,掌握Java高效处理Excel数据导入数据库的N种方法 本文为Java开发者提供了一份详尽的Excel导入数据库实战指南,内容涵盖环境搭建、主流技术选型(Apache POI、EasyExcel)、代码示例、异常处理、性能优化以及常见问题解决方案,助您轻松攻克Excel数据导入难题,提升开发效率。

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

引言:为什么需要Excel导入数据库功能?

在当今的企业级应用开发中,数据是核心资产,而Excel作为最普及的数据处理工具,常常扮演着数据临时存储、批量录入和交换的载体,将Excel中的批量数据高效、准确、安全地导入到数据库中,是许多后台管理系统、数据分析平台、ERP系统等场景下的刚需功能。

手动复制粘贴不仅效率低下、容易出错,而且难以满足大数据量和复杂业务逻辑的需求,使用Java程序实现Excel导入数据库功能,成为开发者的必备技能,本文将手把手教您如何实现这一功能,并分享在实际开发中积累的最佳实践。

技术选型:Java处理Excel的王者之争

在Java生态中,处理Excel文件主要有两大主流技术库:Apache POI阿里巴巴的EasyExcel,了解它们的优缺点,是做出正确技术选型的第一步。

特性 Apache POI EasyExcel
简介 Apache软件基金会维护的老牌项目,功能强大全面。 阿里巴巴开源,专为解决POI内存问题而生。
优点 - 功能强大,支持.xls.xlsx格式
- API成熟,社区资源丰富
- 内存占用极低,解决了POI的OOM问题
- 读写性能优异
- API简洁,易于上手
- 基于SAX模型,适合大文件处理
缺点 - 内存占用高,处理大文件时容易OOM
- API相对繁琐
- 相对较新,社区资源略少于POI
- 功能覆盖面不如POI广
适用场景 - 文件较小(MB级别)
- 需要使用POI高级功能(如复杂样式、图表)
- 大文件(GB级别)
- 对性能和内存有高要求的场景
- 常规的数据导入导出业务

对于绝大多数Excel导入数据库的业务场景,尤其是当数据量可能较大时,我们强烈推荐使用EasyExcel,本文将以EasyExcel为核心进行讲解,并在文末补充POI的简要说明。

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

实战准备:环境搭建

在开始编码前,我们需要准备好开发环境。

数据库准备 以MySQL为例,我们创建一张用于接收Excel数据的用户表。

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

Maven项目依赖pom.xml文件中添加EasyExcel和MySQL驱动的依赖。

<!-- EasyExcel 核心依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version> <!-- 请使用最新稳定版 -->
</dependency>
<!-- MySQL 数据库驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version> <!-- 请使用与您数据库版本匹配的驱动 -->
</dependency>

核心实现:使用EasyExcel将Excel数据导入MySQL

我们将分步实现一个完整的Excel导入功能。

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

步骤1:定义Excel数据模型(DTO) 创建一个与Excel表头对应的Java类,EasyExcel会通过反射将Excel行数据映射为该类的对象。

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class UserExcelDTO {
    // index对应Excel中的列索引,value对应表头内容
    @ExcelProperty(index = 0, value = "姓名")
    private String name;
    @ExcelProperty(index = 1, value = "年龄")
    private Integer age;
    @ExcelProperty(index = 2, value = "邮箱")
    private String email;
}

注意:使用@ExcelProperty注解可以指定列的索引和表头,方便Excel结构变化时进行调整。

步骤2:创建数据监听器 EasyExcel采用“监听器”模式来读取数据,它不会一次性将整个Excel文件加载到内存,而是逐行读取,并将数据传递给监听器,从而实现低内存消耗,我们需要实现AnalysisEventListener接口。

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class UserExcelListener extends AnalysisEventListener<UserExcelDTO> {
    private static final Logger LOGGER = LoggerFactory.getLogger(UserExcelListener.class);
    // 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
    private static final int BATCH_COUNT = 5;
    List<UserExcelDTO> list = new ArrayList<>();
    // 可以通过构造函数传入DAO或Service
    // private UserMapper userMapper;
    // public UserExcelListener(UserMapper userMapper) {
    //     this.userMapper = userMapper;
    // }
    @Override
    public void invoke(UserExcelDTO data, AnalysisContext context) {
        LOGGER.info("解析到一条数据: {}", data);
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据量太大容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也保存数据,确保最后的数据也存储到数据库
        saveData();
        LOGGER.info("所有数据解析完成!");
    }
    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{} 条数据,开始存储数据库!", list.size());
        // TODO: 实际项目中,这里应该调用你的Service或Mapper将list数据存入数据库
        // userMapper.insertBatch(list);
        // 模拟数据库操作
        for (UserExcelDTO user : list) {
            System.out.println("正在保存用户: " + user);
            // 实际调用: userMapper.insert(user);
        }
        LOGGER.info("存储数据库成功!");
    }
}

核心思想:

  • invoke方法:每解析一行数据就会调用一次。
  • doAfterAllAnalysed方法:所有数据解析完成后调用。
  • 批量提交:我们设置一个BATCH_COUNT,当数据量达到这个阈值时,就执行一次数据库保存操作,然后清空列表,这是防止OOM和提升性能的关键。

步骤3:编写导入Controller 创建一个Spring Boot Controller,处理前端文件上传请求。

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
@RestController
@RequestMapping("/api/user")
public class UserController {
    @PostMapping("/import")
    public String importUser(@RequestParam("file") MultipartFile file) {
        // 校验文件
        if (file == null || file.isEmpty()) {
            return "文件不能为空!";
        }
        String originalFilename = file.getOriginalFilename();
        if (!originalFilename.endsWith(".xlsx") && !originalFilename.endsWith(".xls")) {
            return "文件格式不正确,请上传Excel文件!";
        }
        try {
            // 这里为了演示,直接在内存中处理,实际项目建议将文件上传到服务器临时目录。
            EasyExcel.read(file.getInputStream(), UserExcelDTO.class, new UserExcelListener())
                    .sheet() // 读取第一个Sheet
                    .doRead();
        } catch (IOException e) {
            e.printStackTrace();
            return "文件读取失败:" + e.getMessage();
        }
        return "导入成功!";
    }
}

进阶与最佳实践

一个健壮的导入功能,远不止于数据读取和存储。

数据校验 Excel中的数据可能存在格式错误、空值、业务逻辑冲突等问题,我们可以在数据模型中添加校验注解,或在监听器的invoke方法中手动校验。

  • 使用JSR-303校验: 在UserExcelDTO上添加hibernate-validator依赖和校验注解。

    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.2.5.Final</version>
    </dependency>
    @Data
    public class UserExcelDTO {
        @ExcelProperty(index = 0, value = "姓名")
        @NotBlank(message = "姓名不能为空")
        private String name;
        @ExcelProperty(index = 1, value = "年龄")
        @Min(value = 0, message = "年龄不能为负数")
        @Max(value = 150, message = "年龄不能超过150")
        private Integer age;
        @ExcelProperty(index = 2, value = "邮箱")
        @Email(message = "邮箱格式不正确")
        private String email;
    }

    然后在监听器中获取校验结果。

  • 自定义业务校验: 检查邮箱是否已存在。

    // 在UserExcelListener的invoke方法中
    if (userMapper.selectByEmail(data.getEmail()) != null) {
        // 记录错误,并跳过该行
        LOGGER.error("邮箱 {} 已存在,跳过该行数据。", data.getEmail());
        return;
    }
    list.add(data);

错误处理与结果反馈 用户需要知道导入是否成功,以及失败的原因,最佳实践是:

  • 收集错误行:在监听器中创建一个List<String>List<Map>来存储错误信息(如行号、错误原因)。
  • 生成错误报告:导入完成后,如果存在错误,将错误信息生成一个新的Excel文件供用户下载。
  • 返回统一结果:Controller返回一个JSON对象,包含success(是否成功)、message(总览信息)、errorUrl(错误报告下载链接)等字段。

性能优化

  • 批量插入SQL:在saveData方法中,不要使用循环单条插入,而应使用JDBC的addBatch()executeBatch(),或者MyBatis/MyBatis-Plus的批量插入功能。

    // MyBatis-Plus 批量插入示例
    @Autowired
    private UserMapper userMapper;
    // 在UserExcelListener的saveData方法中
    userMapper.insertBatch(list); // 需要在Mapper中定义此方法
  • 数据库连接池:确保使用高效的数据库连接池,如HikariCP。

  • 异步处理:对于耗时较长的导入任务,建议使用异步或消息队列(如RabbitMQ, Kafka),用户先上传文件,系统返回一个任务ID,后台异步执行导入,完成后通过WebSocket或轮询通知用户结果。

Apache POI方案简介

如果您因某些原因必须使用POI,实现逻辑类似。

Maven依赖:

<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>

核心代码 (使用XSSF处理.xlsx):

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class PoiUserImporter {
    public List<UserExcelDTO> importExcel(InputStream inputStream) throws Exception {
        List<UserExcelDTO> userList = new ArrayList<>();
        Workbook workbook = new XSSFWorkbook(inputStream);
        Sheet sheet = workbook.getSheetAt(0); // 获取第一个Sheet
        // 跳过表头行
        for (int i = 1; i <= sheet.getLastRowNum(); i++) {
            Row row = sheet.getRow(i);
            if (row == null) continue;
            UserExcelDTO user = new UserExcelDTO();
            user.setName(getCellValue(row.getCell(0)));
            user.setAge(getIntegerCellValue(row.getCell(1)));
            user.setEmail(getCellValue(row.getCell(2)));
            userList.add(user);
        }
        workbook.close();
        return userList;
    }
    private String getCellValue(Cell cell) {
        if (cell == null) return "";
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                return String.valueOf(cell.getNumericCellValue());
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            default:
                return "";
        }
    }
    private Integer getIntegerCellValue(Cell cell) {
        if (cell == null) return null;
        if (cell.getCellType() == CellType.NUMERIC) {
            return (int) cell.getNumericCellValue();
        }
        return null;
    }
}

注意: POI的传统XSSF模型会将整个文件读入内存,不适合大文件,POI也提供了SXSSF模型(流式API),可以处理大文件,但实现比EasyExcel复杂。

通过本文,我们系统地学习了如何使用Java实现Excel导入数据库功能,我们得出以下核心结论:

  1. 技术选型:优先选择EasyExcel,它凭借其低内存占用和高性能,成为现代Java应用处理Excel数据的首选。
  2. 核心模式:掌握监听器模式是使用EasyExcel的关键,它实现了流式读取,有效避免了内存溢出。
  3. 健壮性:一个优秀的导入功能必须包含数据校验错误处理友好的结果反馈机制。
  4. 高性能:通过批量提交异步处理等手段,可以显著提升导入效率,改善用户体验。

希望这份详尽的攻略能帮助您在实际开发中游刃有余地处理Excel导入数据库的各种挑战,如果您有任何问题或经验分享,欢迎在评论区留言交流!


#Java #Excel #数据库 #数据导入 #EasyExcel #ApachePOI #后端开发 #编程教程

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