1.前几天我在给数据库批量添加数据,但是效率很慢,不能复用,于是乎写了一个方法,用于批量数据添加,代码为:获取用户传入的设备ID,查询该对象,并赋值,然后一条条添加;使用insert
方法(为什么不使用 “insertBatch”这个方法呢,测试过,很慢,需要52000ms,比一条条数据还慢),看了鱼皮的 数据恢复来了灵感,他使用了“CompletableFuture”这个类,来进行异步的处理;
我之前的代码为这样:
public String createEquipmentDataBatch(String... equipmentCode) {
Random random = new Random();
String[] myArray = {"东风", "东南风", "南风", "北风", "东北风"};
String[] myArray2 = {"金龟子", "蟋蟀", "蛾"};
for (String equipmentid : equipmentCode) {
//LinkedList<EquipmentDataDO> list = new LinkedList<>();
//根据设备ID 查询 基地ID 地块ID 同时查询 设备分类名称 设备监测类型
Map<String, Object> maps = equipmentDataMapper.selectEquInfo(equipmentid);
//字符切割遍历 设备采集类型 针对不同值进行赋值,并进行循环插入
for (String item : maps.get("monitorType").toString().split(",")) {
//取当前时间 并循环前十天数据 并插入相关数据进行新增
for (int i = 0; i < 10; i++) { // 插入
EquipmentDataDO equipmentDataDO = new EquipmentDataDO();
//对设备数据进行基地ID 赋值 / 地块ID 赋值 / 设备分类名称赋值 赋值
// 将LocalDate与随机的时分秒组合成LocalDateTime
equipmentDataDO.setEquipmentCode(equipmentid)
.setBaseCode(maps.get("baseCode").toString())
.setPlotCode(maps.get("plotCode").toString())
.setCollectionType(maps.get("collectionType").toString())
.setMonitoringType(item)
.setCollectionTime(LocalDate.now().minusDays(i)
.atTime(LocalTime.of(random.nextInt(24),
random.nextInt(60),
random.nextInt(60))))
.setId(IdUtil.getSnowflakeNextIdStr());
//监测类型数值判断并添加 对应检测数值跟单位
switch (item) {
case "土壤温度":
case "温度":
equipmentDataDO.setDataValue(String.format("%.2f", 15.0 + (32.0 - 15.0) * random.nextDouble())).setYyUnit("℃");
break;
//这里有很多就不展示了
}
//list.add(equipmentDataDO);
equipmentDataMapper.insert(equipmentDataDO);
}
}
}
//equipmentDataMapper.insertBatch(list);
// 返回
return "true";
}
但是发现这个添加2个设备,即90条数据,竟然需要42000ms
叔叔能忍,婶婶也不能忍;
于是乎,简单参考了一下鱼皮的文章,发现一个很好用的类,可以“CompletableFuture”他的作用是可以异步的处理,类似于多线程,也可以传入线程池,这个方法这里就不做讨论了;
直接上代码,然后给你们解释:
public String createEquipmentDataA(String... equipmentCode) {
// 提前创建避免重复创建浪费资源
Random random = new Random();
String[] myArray2 = {"金龟子", "蟋蟀", "蛾"};
//存放任务队列的集合
List<CompletableFuture<Void>> completable = new ArrayList<>();
//存放数据集合
List<EquipmentDataDO> list = new LinkedList<>();
//获取userId 以及部门ID
Long userId = SecurityFrameworkUtils.getLoginUserId();
Long loginUserDeptId = SecurityFrameworkUtils.getLoginUserDeptId();
for (String equipmentId : equipmentCode) {
//根据设备ID 查询 基地ID 地块ID 同时查询 设备分类名称 设备监测类型
Map<String, Object> maps = equipmentDataMapper.selectEquInfo(equipmentId);
//字符切割遍历 设备采集类型 针对不同值进行赋值,并进行循环插入
for (String item : maps.get("monitorType").toString().split(",")) {
//取当前时间 并循环前十天数据 并插入相关数据进行新增
for (int i = 0; i < 10; i++) { // 插入
EquipmentDataDO equipmentDataDO = new EquipmentDataDO();
//设置对象属性
EquipmentDataDOSetParameter(equipmentDataDO,random,myArray2,equipmentId,maps,item,i,userId,loginUserDeptId);
list.add(equipmentDataDO);
}
}
}
//批量插入 90条数据 测试时共耗时6168ms,若使用并行流耗时11335ms
final int batchSize =10;
for (int i = 0; i < list.size(); i+=batchSize) {
// 获取当前批次
int endIndex = Math.min(i + batchSize, list.size());
// 获取当前批次
int finalI = i;
// 添加任务
completable.add(CompletableFuture.runAsync(() -> equipmentDataMapper.insertBatch(list.subList(finalI, endIndex))));
}
//等待所有任务完成
CompletableFuture.allOf(completable.toArray(new CompletableFuture[0])).join();
// 返回
return "success, 已添加:"+list.size()+"条数据";
}
大概流程就是,把所有要添加的数据,进行了存放list的情况,然后根据我想要的大小进行分割(为什么不直接使用insertBatch添加整个列表呢,因为我试过了添加效率比一个个还慢,需要真正52000ms左右,索性还不如使用异步的方法进行添加),所以我按照鱼皮的方案,直接使用了这个对象的异步处理(不需要返回的版本),这样在添加完后,实测共耗时:6168ms,比之前快了600%的效率;
到这里有的同学会问,这样写很麻烦,为啥不适用stream的并行流呢,嘿嘿,我替你们试过了,使用并行流,添加90条数据,共耗时:若使用并行流耗时11335ms,所有最后采用了“CompletableFuture”这个异步处理的方案;大家有时间可以都测试测试,这种方案不一定最快,最好,找到最适合自己的;
大家有时间可以学习一个这个异步处理类,说不定,真的那天就派上用场了
本文灵感来源于鱼皮:员工写了个比删库更可怕的 Bug!