1.乐观锁
**作用:**当要更新一条记录的时候,希望这条记录没有被别人更新,从而减少线程冲突导致的问题
乐观锁的实现方式:
取出记录时,获取当前 version 更新时,带上这个 version 执行更新时, set version = newVersion
where version = oldVersion 如果 version 不对,就更新失败
场景
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1万多。
实体类加入version注解
//乐观锁version注解
@Version
private Integer version;
编写配置类
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setOptimizeJoin(true);
paginationInnerInterceptor.setDbType(DbType.MYSQL);
paginationInnerInterceptor.setOverflow(true);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
//添加乐观锁拦截器
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
调用service方法
@GetMapping("/OCC")
@Operation(summary = "乐观锁",description = "乐观锁")
public Result OCC(String id,String name){
Users users = iUsersService.getById(id);
users.setName("xxxxx");
users.setEmail("sadasdasd");
iUsersService.updateById(users);
return Result.success(users);
}
观察输出信息
发现执行修改操作时会判断当前version
支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下 newVersion = oldVersion + 1
newVersion 会回写到 entity 中
仅支持 updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
调用 updateById 方法更新数据库中的记录。在这个过程中,MyBatis-Plus 会自动检查版本号或时间戳,如果数据库中的记录被其他事务修改了,那么这个更新操作就会失败。
乐观锁与悲观锁
如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后(有一个操作的时候另一人操作会被阻塞也就是无法操作),才能对价格进行操作,也会保证最终的价格是120元。(也就是说你先弄 你弄完之后我才能弄;也就不会导致上面的问题)
@GetMapping("/OCC")
@Operation(summary = "乐观锁",description = "乐观锁")
public Result OCC(String id){
Users suhai = iUsersService.getById(id);
System.out.println("suhai.getAge() = " + suhai.getAge());
Users suyue = iUsersService.getById(id);
System.out.println("suyue.getAge() = " + suyue.getAge());
//suhai操作年龄-5
suhai.setAge(suhai.getAge()-5);
iUsersService.updateById(suhai);
//suyue操作年龄+3
suyue.setAge(suyue.getAge()+3);
iUsersService.updateById(suyue);
//调用 updateById 方法更新数据库中的记录。在这个过程中,
// MyBatis-Plus 会自动检查版本号或时间戳,如果数据库中的记录被其他事务修改了,那么这个更新操作就会失败。
return Result.success("模仿成功");
}
这是一个demo 如果suhai进行操作 则version会自动+1 而suyue再线程进行操作 则会判断当前version是否相同 如果不同则操作失败 不会执行接下来的语句
悲观锁
悲观锁解决方案非常简单 直接在操作sql中添加 for update语句即可
select id,商品名,商品价格,version from 商品表 where id =2 for update
使用 for update操作 可以认为是给每次操作都加上表级别的悲观锁 在事务没结束前 其他事务必须等待加锁的事务提交后才可以
并发环境下都不建议使用悲观锁,因为悲观锁容易锁表,导致事务等待,性能低下。
批量查询
// 测试批量查询!
@Test
public void testSelectByBatchId(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}