学生管理功能,具备增删改查学生信息的逻辑。支持批量新增;
准备5条学生信息数据用例,至表student_exp,其中id=1 和 id=2 的stu_no重复。
目标:当将学生信息使用多线程插入student_info表中后,预期stu_no应该无重复记录。
// step1: 从student_exp获取5个用例
List<StudentExp> studentExps = studentExpService.studentExps();
// step2: 转换成student_info表实体
List<StudentInfo> createStudentsInfo(List<StudentExp> studentExps)
// step3: 提交到线程池,让线程池处理
for (StudentInfo info : infos) {
completionService.submit(() -> {
try {
this.saveInfo(info);
} catch (Exception e) {
e.printStackTrace();
}
}, null);
}
// step4 : 不存在则保存,存在则更新
void saveInfo(StudentInfo studentInfo) {
String stuNo = studentInfo.getStuNo();
StudentInfo one = this.getOne(new LambdaQueryWrapper<StudentInfo>().eq(StudentInfo::getStuNo, stuNo), false);
if (one != null) {
studentInfo.setId(one.getId());
this.updateById(studentInfo);
return; }
try {
Thread.sleep(2000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
this.save(studentInfo);
}
图一: 先查再更新或保存 ^756709
执行结果正常
//替换通用编码step3
for (StudentInfo info : infos) {
this.saveInfo(info);
}
耗时:8975
//核心线程,以及最大线程数均设置成1
return getExecutor("批量插入-", 1, 1, 10, 60, new ThreadPoolExecutor.CallerRunsPolicy());
耗时:8880 ms
页面一直点击刷新即可复现,我快速点了4下,产生如下的数据。很明显在并发条件下,重复值很容易出现,当然还有一些其它情况-可以参考(接口幂等性——防止并发重复插入数据),对照[[并发导致重复添加问题-单机#^756709|图一]],并发情况下,执行到①时,数据库中还未入库,此时通过stu_no查询,结果均为null,因此都会执行插入操作。
//测试代码
for (StudentInfo info : infos) {
completionService.submit(() -> {
try {
this.saveInfo(info);
} catch (Exception e) {
e.printStackTrace();
}
}, null);
}
对照[[并发导致重复添加问题-单机#^756709|图一]],若5个线程同时通过stuNo执行查询,到达②处时,由于此时库中没有与插入数据stuNo相同的数据,因此one均为null,程序将会保存5条记录,其中2条存在重复。实验结果如下id = 1 和 id=5的stu_no重复
耗时:2344 ms
insert into student_info(stu_no, name,age,sex) values('12223333','张三',23,'男');
建表语句 ^b04c6d
CREATE TABLE `student_exp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`stu_no` varchar(20) COLLATE utf8mb4_german2_ci NOT NULL,
`name` varchar(20) COLLATE utf8mb4_german2_ci NOT NULL,
`age` int(3) NOT NULL,
`sex` char(1) COLLATE utf8mb4_german2_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_german2_ci;
INSERT INTO `experiment`.`student_exp`(`id`, `stu_no`, `name`, `age`, `sex`) VALUES (1, '12223333', '张三', 23, '男');
INSERT INTO `experiment`.`student_exp`(`id`, `stu_no`, `name`, `age`, `sex`) VALUES (2, '12223333', '张三2', 24, '女');
INSERT INTO `experiment`.`student_exp`(`id`, `stu_no`, `name`, `age`, `sex`) VALUES (3, '12223334', '李四', 24, '男');
INSERT INTO `experiment`.`student_exp`(`id`, `stu_no`, `name`, `age`, `sex`) VALUES (4, '12223335', '王五', 25, '男');
INSERT INTO `experiment`.`student_exp`(`id`, `stu_no`, `name`, `age`, `sex`) VALUES (5, '12223336', '胡六', 26, '女');
建表语句^cfecf9
CREATE TABLE `student_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`stu_no` varchar(8) COLLATE utf8mb4_german2_ci DEFAULT NULL COMMENT '学号',
`name` varchar(255) COLLATE utf8mb4_german2_ci DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`sex` char(2) COLLATE utf8mb4_german2_ci DEFAULT NULL COMMENT '性别',
`deleted` tinyint(1) DEFAULT '0' COMMENT '0:正常 1: 删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_german2_ci COMMENT='名单列表';
因为逻辑删除之后,库中有多条被逻辑删除之后的记录 ;更普适的看这个博客先查询后插入在高并发下重复插入问题解决 ↩︎