一.需求分析

  最近接到一个需求,导入十万级,甚至可能百万数据量的记录了车辆黑名单的Excel文件,借此机会分析下编码过程;

  首先将这个需求拆解,发现有三个比较复杂的问题:

  问题一:Excel文件导入后首先要被解析为存放对象的列表,数据量大的情况下可能会导致内存溢出,解析时间过长;

  问题二:插入数据库的时候,数据量大,写入的时间长

  问题三:要对数据库中的现有数据进项判断,不仅仅要做插入动作,还要将数据库的数据与导入的数据对比,判断是否做更新操作

  

  其中:

  问题一和问题三,可以看做同一类,因为主要涉及内存计算导致的性能问题,以及内存占用过大的溢出问题,

  关于这两个问题,现在线上的机器基本上是4核8G的配置集群部署,内存并不是关键,我会在另一篇文章中给出我的方案,

  今天主要针对问题二,写入的数据库的问题给出我的方案,

  

  问题二主要是多次写入数据库的问题,显然,如果有几十万条数据,那么是不可能连续写几十万次的,不然要写到后年马月才能全部入库,

  解决方案:

    这里我主要采用了多线程的写入方式,十万条数据,2000条写一次(可以自己定义),用线程池提交多个线程任务同时写入,提高性能

二.代码环境

  Springboot2.1.3+POI+PGSQL

  controller层代码

    @PostMapping("/upload")
public void upload1(MultipartFile file, @Validated UploadReq req) throws Exception {
//从数据库查询出现有的数据,根据去重的字段分组去构建成一个HashMap,通过containsKey()判断
//将需要更新的数据放到updateList中
List<User> updateList=new ArrayList<>(); //已取值的行数
int rowNum = 0;
//列号
int colNum = 0;
//真正有数据的行数
int realRowCount = 0;
//得到工作空间
Workbook workbook = null; try {
workbook = ExcelUtil.getWorkbookByInputStream(file.getInputStream(), file.getOriginalFilename());
} catch (IOException e) {
e.printStackTrace();
}
//得到工作表
int numberOfSheets = workbook.getNumberOfSheets();
for (int i = 0; i < numberOfSheets; i++) {
Sheet sheet = ExcelUtil.getSheetByWorkbook(workbook, i)
realRowCount = sheet.getPhysicalNumberOfRows();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List<User> list = new ArrayList<>();
User user = null; for(Row row:sheet) {
if(realRowCount == rowNum) {
break;
}
//空行跳过
if(ExcelUtil.isBlankRow(row)) {
continue;
}
if(row.getRowNum() == -1) {
continue;
}else {
//第一行表头跳过
if(row.getRowNum() == 0) {
continue;
}
}
rowNum ++;
colNum = 1;
user = new User();
ExcelUtil.validCellValue(sheet, row, colNum, "id");
user.setId(Integer.valueOf(ExcelUtil.getCellValue(sheet, row, colNum - 1)));
ExcelUtil.validCellValue(sheet, row, ++ colNum, "name");
user.setId(Integer.valueOf(ExcelUtil.getCellValue(sheet, row, colNum - 1)));
//判断是否是已存在的数据,如果是就更新,不是就新增
//updateList.add(user);
list.add(user); } //新增的逻辑
userService.saveBatch(list);
System.out.println(list);
}
}

  service层代码

@Service
public class UserServiceImpl implements IUserService { @Autowired
private UserMapper userMapper; @Override
public void saveBatch(List<User> list) throws Exception {
//一个线程处理200条数据
int count = 200;
//数据集合大小
int listSize = list.size();
//开启的线程数
int runSize = (listSize / count) + 1;
//存放每个线程的执行数据
List<User> newlist = null; //创建一个线程池,数量和开启线程的数量一样
//Executors 的写法
// ExecutorService executor = Executors.newFixedThreadPool(runSize); //ThreadPoolExecutor的写法
ThreadPoolExecutor executor = new ThreadPoolExecutor(runSize, runSize, 1,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardOldestPolicy()); //创建两个个计数器
CountDownLatch begin = new CountDownLatch(1);
CountDownLatch end = new CountDownLatch(runSize);
//循环创建线程
for (int i = 0; i < runSize; i++) {
//计算每个线程执行的数据
if ((i + 1) == runSize) {
int startIndex = (i * count);
int endIndex = list.size();
newlist = list.subList(startIndex, endIndex);
} else {
int startIndex = (i * count);
int endIndex = (i + 1) * count;
newlist = list.subList(startIndex, endIndex);
}
//线程类
ImportThread mythead = new ImportThread(newlist, begin, end,userMapper);
//这里执行线程的方式是调用线程池里的executor.execute(mythead)方法。
executor.execute(mythead);
}
begin.countDown();
end.await();
//执行完关闭线程池
executor.shutdown();
}

  线程类

public class ImportThread implements Runnable {

    public ImportThread() {
} UserMapper userMapper;
private List<User> list;
private CountDownLatch begin;
private CountDownLatch end; /**
* 方法名: ImportThread
* 方法描述: 创建个构造函数初始化 list,和其他用到的参数
* @throws
*/
public ImportThread(List<User> list, CountDownLatch begin, CountDownLatch end,UserMapper userMapper) {
this.list = list;
this.begin = begin;
this.end = end;
this.userMapper=userMapper;
} @Override
public void run() {
try {
//执行完让线程直接进入等待
userMapper.saveBatch(list);
begin.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//这里要主要了,当一个线程执行完 了计数要减一不然这个线程会被一直挂起
//这个方法就是直接把计数器减一的
end.countDown();
}
} }

  

  

  

十万级百万级数据量的Excel文件导入并写入数据库的更多相关文章

  1. NodeJs之EXCEL文件导入导出MongoDB数据库数据

    NodeJs之EXCEL文件导入导出MongoDB数据库数据 一,介绍与需求 1.1,介绍 (1),node-xlsx : 基于Node.js解析excel文件数据及生成excel文件. (2),ex ...

  2. 读取FTP上的excel文件,并写入数据库

    今天遇到一些问题,需要从ftp上读取一些excel文件,并需要将excel中的数据写入到数据库,这样就可以通过管理页面查看这些数据. 我将相关工作分为三步,1.从ftp上读取相关文件,并将excel文 ...

  3. 批量将制定文件夹下的全部Excel文件导入微软SQL数据库

    以下代码将c:\cs\文件夹下的全部Excle中数据导入到SQL数据库 declare @query vARCHAR(1000) declare @max1 int declare @count1 i ...

  4. PHP Excel文件导入数据到数据库

    1.php部分(本例thinkphp5.1): 下载PHPExcel了扩展http://phpexcel.codeplex.com/ <?phpnamespace app\admin\contr ...

  5. Excel文件导入SQL Server数据库

    Excel表格的使用可谓是非常广泛,博主也简单百度了一下Excel的发展. 发展历程: 1982年 Microsoft推出了它的第一款电子制表软件-Multiplan,并在CP/M系统上大 Excel ...

  6. Excel文件导入SQL Server数据库表

    --office 2003--如果接受数据导入的表已经存在insert into DemoTable select * from OPENROWSET('MICROSOFT.JET.OLEDB.4.0 ...

  7. PHP实时生成并下载超大数据量的EXCEL文件

    最近接到一个需求,通过选择的时间段导出对应的用户访问日志到excel中, 由于用户量较大,经常会有导出50万加数据的情况.而常用的PHPexcel包需要把所有数据拿到后才能生成excel, 在面对生成 ...

  8. Excel导入导出工具(简单、好用且轻量级的海量Excel文件导入导出解决方案.)

    Excel导入导出工具(简单.好用且轻量级的海量Excel文件导入导出解决方案.) 置顶 2019-09-07 16:47:10 $9420 阅读数 261更多 分类专栏: java   版权声明:本 ...

  9. (转)如何将 Excel 文件导入到 Navicat for MySQL 数据库

    场景:工作中需要统计一段时间的加班时长,人工统计太过麻烦,就想到使用程序实现来统计 1 如何将 Excel 文件导入到 Navicat for MySQL 数据库 Navicat for MySQL  ...

随机推荐

  1. 2018-2-13-C#-复制列表

    title author date CreateTime categories C# 复制列表 lindexi 2018-2-13 17:23:3 +0800 2018-2-13 17:23:3 +0 ...

  2. 一、WebApi模型验证实践项目使用

    一.启语 前面我们说到,模型验证的原理(包含1.项目创建,2.模型创建,3.走通测试模型验证,4.在过滤器中处理返回json格式(非控制器内))-完全是新手理解使用的,新番理解 通常情况下,对于那些经 ...

  3. 六、SQL语句进行多条件查询,并解决参数为空的情况

    一.SQL语句进行多条件查询,并解决参数为空的情况 QueryEntity query; var whereSql = new StringBuilder("Where 1=1") ...

  4. office visio project安装

    1.VOL 版和 Retail 零售版的区别 VOL版是大客户版,也叫批量授权版本.VOL版本一个key可以激活指定数量的机器. Retail版即零售版,也就是平时在商店里买的office安装光盘里面 ...

  5. Vue----v-if 条件渲染

    先看一个使用vue v-if的小例子 <div id="example"> <p>小明和小李和小新,小月正在捉迷藏此时</p> <p> ...

  6. Go的学习 append的使用

    1. package main; import "fmt" func test () { ],,,,}; s:=a[:] fmt.Printf(]) s=append(s,); s ...

  7. Java NIO学习(Path接口、Paths和Files工具类的使用)

    NIO学习:Paths和Files工具类的使用 JDK1.7引入了新的IO操作类.在java.nio.file包下,Java NIO Path接口和Files类. Path接口:Path表示的是一个目 ...

  8. Python 石头 剪刀 布

    di = {1: '石头', 2: '剪刀', 3: '布'} def win(x, y): if len({x[0], y[0]}) == 1: print('平局.') else: if {x[0 ...

  9. TP、FP、FN、TN的含义

    true positive(被正确分类的正例) false negative(本来是正例,错分为负例) true negative(被正确分类的负例) false positive(本来是负例,被错分 ...

  10. Go(三)面向对象

    封装数据和行为 数据封装 结构定义 type Employee struct { Id string Name string Age int } field后面没有逗号 实例创建及初始化 e := E ...