悲观锁: 悲观锁比较适合插入数据,简单粗暴但是性能一般
乐观锁: 比较适合更新数据, 性能好但是成功率低(多个线程同时执行时只有一个可以执行成功),还需要访问数据库造成数据库压力过大
模拟乐观锁实现流程
第一步: 数据库中增加商品表t_product
并插入一条数据
CREATE TABLE t_product(
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id));
--插入一条数据
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
第二步: 定义实体类Product
和mapper接口ProductMapper
@Data
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
第三步: 在测试方法中模拟小王小李同时获取到商品价格然后做修改操作
@Test
public void testConcurrentUpdate() {
//小李取出的价格100
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的价格:" + p1.getPrice());
//小王取出的价格100
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的价格:" + p2.getPrice());
//小李将价格加了50元存入了数据库,此时商品价格150
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("小李修改结果:" + result1);
//小王将商品减了30元存入了数据库,此时商品价格70
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
System.out.println("小王修改结果:" + result2);
//老板查询商品价格发现是70,即小李的操作被小王覆盖了
Product p3 = productMapper.selectById(1L);
System.out.println("最后的结果:" + p3.getPrice());
}
Mybatis-Plus实现乐观锁
第一步: 数据库商品表t_product
中添加version字段默认值是0
第二步: 在实体类的version属性上添加@Version注解
表示该属性对应的字段是乐观锁版本号字段
@Data
public class Product {
private Long id;
private String name;
private Integer price;
@Version
private Integer version;
}
第三步: 在config.MybatisPlusConfig
配置类的MybatisPlusInterceptor
插件对象中添加乐观锁插件对象OptimisticLockerInnerInterceptor
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件对象
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//添加乐观锁插件对象
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
第四步: 在测类方法中测试乐观锁的效果
@Test
public void testConcurrentUpdate() {
//小李取出的价格100
//SELECT id,`name`,price,`version`FROM product WHERE id = 1;
Product p1 = productMapper.selectById(1L);
//小王取出的价格100
//SELECT id,`name`,price,`version`FROM product WHERE id = 1;
Product p2 = productMapper.selectById(1L);
//小李将价格加了50元后存入了数据库,此时商品信息的版本号已经变为了1
p1.setPrice(p1.getPrice() + 50);
//update t_product set name = 外星人笔记本,price = 150,version = 1 where id = 1 and version = 0
int result1 = productMapper.updateById(p1);
//小王将商品减了30元,但是执行更新操作时发现商品信息的版本号与自己取出版本号不一致所以会更新失败
//update t_product set name = 外星人笔记本,price = 70,version = 1 where id = 1 and version = 0
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
//商品最后的价格150,小王的更新操作失败了
Product p3 = productMapper.selectById(1L);
System.out.println("最后的结果:" + p3.getPrice());
}
优化: 如果小王更新失败就重新获取商品的版本号,然后再次执行更新的操作
@Test
public void testConcurrentVersionUpdate() {
//小李取出的价格100
Product p1 = productMapper.selectById(1L);
//小王取出的价格100
Product p2 = productMapper.selectById(1L);
//小李将价格价格50
p1.setPrice(p1.getPrice() + 50);
productMapper.updateById(p1);
//小王将价格减30
p2.setPrice(p2.getPrice() - 30);
int result = productMapper.updateById(p2);
//result等于0表示更新失败,此时需要重新执行查询语句获取商品版本号并再次执行更新操作
if(result == 0){
p3 = productMapper.selectById(1L);
p3.setPrice(p3.getPrice() - 30);
//update t_product set name = 外星人笔记本,price = 120,version = 2 where id = 1 and version = 1
productMapper.updateById(p3);
}
//最后商品的价格120
Product p4 = productMapper.selectById(1L);
System.out.println("老板看价格:" + p4.getPrice());
}