通过EasyExcel+线程池实现百万级数据从Excel导入到数据库 您所在的位置:网站首页 将数据导入数据库的方法 通过EasyExcel+线程池实现百万级数据从Excel导入到数据库

通过EasyExcel+线程池实现百万级数据从Excel导入到数据库

2024-07-03 22:18| 来源: 网络整理| 查看: 265

EasyExcel的优缺点

优点:

高效性:EasyExcel采用零反射、零注解的方式读写Excel文件,这使得它在处理大型Excel文件时具有出色的性能。此外,它采用了高效的解析算法,能够快速读取和解析文件内容。低内存占用:与传统的Excel文件读取方式相比,EasyExcel显著降低了内存占用。它采用基于事件驱动的模型,通过回调函数来处理每一行数据,而不是一次性将整个文件读入内存。这种流式的处理方式极大地节省了内存资源,使得处理大文件时更加稳定可靠。支持多种文件格式:EasyExcel支持多种Excel文件格式,包括.xls、.xlsx、.xlsm等,这使得它在处理不同版本的Excel文件时具有更大的灵活性。易用性:EasyExcel提供了简单易用的API,使得开发者能够轻松地实现Excel文件的读写操作。它支持读写多种数据类型,如基本类型、集合、自定义对象等,满足了开发者在处理复杂数据时的需求。错误处理与稳定性:在处理大文件时,数据的一致性和完整性至关重要。EasyExcel提供了良好的错误处理机制,能够在读取过程中有效地识别和处理异常情况,确保数据的准确性和稳定性。

缺点:

特殊格式和功能的限制:EasyExcel可能无法完全支持Excel文件中的所有特殊格式和功能。例如,它可能无法正确解析某些复杂的公式、图表或特殊的数据验证规则。这可能导致在读取某些特定格式的Excel文件时出现问题。

对于大文件的写入性能:虽然EasyExcel在读取大文件时表现优秀,但在写入大文件时可能会遇到性能瓶颈。尤其是在处理大量数据并需要频繁写入到Excel文件时,可能会导致写入速度变慢,甚至可能出现超时或内存问题。

并发处理能力有限:EasyExcel在处理高并发请求时可能存在一定的问题。当多个线程或进程同时尝试读取或写入同一个Excel文件时,可能会出现数据冲突或不一致的情况。尽管可以通过一些同步机制来避免这些问题,但这可能会增加系统的复杂性和开销。

依赖环境和版本兼容性:EasyExcel的性能和稳定性可能受到Java环境、操作系统版本或其他依赖库的影响。如果环境配置不当或存在版本不兼容的问题,可能会导致读取大文件时出现异常或错误。

错误处理和日志记录不足:在某些情况下,EasyExcel可能无法提供足够的错误处理和日志记录功能。当遇到复杂的数据结构或格式问题时,可能难以快速定位和解决问题。这可能会增加开发和维护的难度。

具体实现

Maven依赖

com.alibaba easyexcel 3.0.5 org.springframework.boot spring-boot-starter-data-jpa org.projectlombok lombok 1.18.24 import com.alibaba.excel.EasyExcel; import com.demo.importExcel.entity.User; import com.demo.importExcel.listener.DataReadListener; import com.demo.importExcel.mapper.ImportExcelMapper; import com.demo.importExcel.service.IDataBaseService; import com.demo.importExcel.service.IImportExcelService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @Service public class ImportExcelServiceImpl implements IImportExcelService { @Autowired private IDataBaseService dataBaseService; /** * 导入Excel数据实现 */ @Override public void importExcel() { long startTime = System.currentTimeMillis(); //Excel路径 String path = "D://exportExcel.xlsx"; //读取sheet的数量 int numberSheet = 20; //创建一个固定大小的线程池,大小和sheet数量一样 ExecutorService executor = Executors.newFixedThreadPool(numberSheet); //遍历所有sheet for (int i = 0; i < numberSheet; i++) { //lambda表达式中的变量必须是final的 int sheetNumber = i; //向线程池提交任务 executor.submit(()->{ //使用EasyExcel获取相对于sheet数据 EasyExcel.read(path, User.class,new DataReadListener(dataBaseService)) .sheet(sheetNumber)//sheet数 .doRead();//开始读取数据 }); } //线程池关闭 executor.shutdown(); //等待所以任务完成读取操作 try { executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } long endTime = System.currentTimeMillis(); System.out.println("导入时长:" + String.valueOf(endTime-startTime)); } }

实现并发读取多个sheet代码:

import com.alibaba.excel.EasyExcel; import com.demo.importExcel.entity.User; import com.demo.importExcel.listener.DataReadListener; import com.demo.importExcel.mapper.ImportExcelMapper; import com.demo.importExcel.service.IDataBaseService; import com.demo.importExcel.service.IImportExcelService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @Service public class ImportExcelServiceImpl implements IImportExcelService { @Autowired private IDataBaseService dataBaseService; /** * 导入Excel数据实现 */ @Override public void importExcel() { long startTime = System.currentTimeMillis(); //Excel路径 String path = "D://exportExcel.xlsx"; //读取sheet的数量 int numberSheet = 20; //创建一个固定大小的线程池,大小和sheet数量一样 ExecutorService executor = Executors.newFixedThreadPool(numberSheet); //遍历所有sheet for (int i = 0; i < numberSheet; i++) { //lambda表达式中的变量必须是final的 int sheetNumber = i; //向线程池提交任务 executor.submit(()->{ //使用EasyExcel获取相对于sheet数据 EasyExcel.read(path, User.class,new DataReadListener(dataBaseService)) .sheet(sheetNumber)//sheet数 .doRead();//开始读取数据 }); } //线程池关闭 executor.shutdown(); //等待所以任务完成读取操作 try { executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } long endTime = System.currentTimeMillis(); System.out.println("导入时长:" + String.valueOf(endTime-startTime)); } }

先定义sheet的数量和固定大小的线程池数量,每个sheet页做为单独的任务交给线程池处理。定义了DataReadListener,这个类是ReadListener的实现类。当EasyExcel每读取一行数据都会调用invoke方法,在invoke()中可以做我们自己的逻辑处理

以下是DataReadListener:

import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.read.listener.ReadListener; import com.demo.importExcel.entity.User; import com.demo.importExcel.mapper.ImportExcelMapper; import com.demo.importExcel.service.IDataBaseService; import com.demo.importExcel.service.IImportExcelService; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Import; import java.util.ArrayList; import java.util.List; //自定义监听器,处理读取到的Excel数据 @Slf4j public class DataReadListener implements ReadListener { private IDataBaseService dataBaseService; /** * 每次批量插入数据的数量 */ private static final int batchSize = 1000; /** * 用于暂存数据的集合,直到数量等于batchSize时就会进行插入操作并清空集合 */ private List batchList = new ArrayList(); /** * 注入mapper * @param mapper */ public DataReadListener(IDataBaseService dataBaseService) { this.dataBaseService = dataBaseService; } //EasyExcel每读取一行数据就会执行一次 @Override public void invoke(User user, AnalysisContext analysisContext) { log.info("读取到的行数据:{}",user); if(validateData(user)){ batchList.add(user); }else { //没有通过校验的数据,打印日志 log.error("id为[{}]的数据没有通过校验",user.getId()); } //如果集合数量大于设置的批量数量,那么就插入数据并清空集合 if(batchList.size() >= batchSize){ dataBaseService.batchInsert(batchList); batchList.clear(); } } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { log.info("Excel读取完成!"); //如果还有数据就一起插入到数据库中 if(!batchList.isEmpty()){ dataBaseService.batchInsert(batchList); } } /** * 数据校验 * @param user * @return */ private boolean validateData(User user){ int userCount = dataBaseService.findUserById(user.getId()); //判断是否存在数据库中 if(userCount == 0){ return true; } //处理其他逻辑校验........ return false; } }

通过自定义的DataReadListener,我们就可以在读取Excel的时候做处理。

每读取到一行数据会先做数据校验,如果校验通过后就会放到缓存集合中,List数量积累到1000时就会通过Mybatis的批量操作进行数据插入。doAfterAllAnalysed方法会在读取Excel完成后进行调用

MyBatis批量操作:

@Mapper public interface ImportExcelMapper { /** * 批量导入数据 * @param dataList */ void batchInsertData(List dataList); /** * 查询用户 * @return */ List findAllUser(); /** * 根据id查询用户 * @param id * @return */ int findUserById(String id); }

ImportExcel.xml文件:

INSERT INTO t_user (id, name,create_time) VALUES (#{user.id}, #{user.name},#{user.createTime}) select * from t_user select count(id) from t_user where id=#{id}

源码地址:https://github.com/Roaly/importExcel

在CSDN上,一键三连是对作者辛勤创作的最好鼓励!喜欢我的文章,就请点赞、收藏、转发吧!你们的支持是我持续分享知识的动力,感谢大家的陪伴与认可!💖🔝🔄



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有