参考博文
https://baijiahao.baidu.com/s?id=1659469738216922362&wfr=spider&for=pc
1 没有锁的风险
开发不设锁,就像没穿裤衩。当海水退去,程序的漏洞就会被一览无遗。现在没出问题,只是因为还有海水为你遮挡了老板们的视线。
听小编给你讲个朋友的故事:一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1多万。
2 乐观锁与悲观锁
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。
接下来小编将介绍如何在mybatis-plus项目中,添加乐观锁。
需求:是否能够确保小李先操作的价格不会被小王后操作而覆盖,对应的版本号是否有自动增加。
注意
- 乐观锁数据类型支持int、integer、long、timestamp
- 仅支持updateById和update方法
1 搭建 springboot 整合 mybatis-plus环境,可参考博文
https://blog.csdn.net/qq_41712271/article/details/115756865
2 MybatisPlusPageConfig 配置类修改
package cn.huawei.guanggao.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusPageConfig {
/* 旧版本配置
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}*/
/**
* 新的分页插件 ,拦截器的原理感觉
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
3 创建一个商品类,字段为:id、name、price、version;这里请注意,必须给version字段加上@Version的注解
CREATE TABLE goods
(
id INT AUTO_INCREMENT COMMENT '商品id'
PRIMARY KEY,
NAME VARCHAR(30) NULL COMMENT '商品名称',
stock INT DEFAULT 0 NULL COMMENT '商品库存',
VERSION INT DEFAULT 0 NULL COMMENT '并发版本控制'
)
COMMENT '商品表' CHARSET = utf8;
package cn.huawei.guanggao.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("goods")
public class Goods implements Serializable {
private static final long serialVersionUID = -1939317434177299884L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String name;
private Integer stock;
@Version
private Integer version;
}
4 测试类
@Autowired
private GoodsMapper goodsMapper;
@Test
void test_23() {
//SELECT id,name,stock,version FROM goods WHERE id=2
//id, name, stock, version
//2, 水果, 100, 0
Goods good1 = goodsMapper.selectById(2);
//SELECT id,name,stock,version FROM goods WHERE id=2
//id, name, stock, version
//2, 水果, 100, 0
Goods good2 = goodsMapper.selectById(2);
//小李把把价格 +50
good1.setStock(good1.getStock() + 50);
//UPDATE goods SET name=水果, stock=150, version=1 WHERE id=2 AND version=0
int i = goodsMapper.updateById(good1);
System.out.println("###################"+i);
//################### 1
//以上修改成功
//小王把价格 -30
good2.setStock(good2.getStock() - 30);
int result = goodsMapper.updateById(good2);
//UPDATE goods SET name=水果, stock=70, version=1 WHERE id=2 AND version=0
//此操作会会失败,因为上面的加100操作成功后,version的版本已经是1
System.out.println("-----------------"+result);
//-----------------0
//所以需要这样来操作
if (result == 0) {
//SELECT id,name,stock,version FROM goods WHERE id=2
//id, name, stock, version
//2, 水果, 150, 1
Goods p2 = goodsMapper.selectById(2);
//UPDATE goods SET name=水果, stock=120, version=2 WHERE id=2 AND version=1
p2.setStock(p2.getStock() - 30);
goodsMapper.updateById(p2);
}
//最后的结果
//SELECT id,name,stock,version FROM goods WHERE id=2
//id, name, stock, version
//2, 水果, 120, 2
Goods goods3 = goodsMapper.selectById(2L);
System.out.println("最后的结果:" + goods3.getStock());
}