乐观锁面向场景

一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。

此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。

现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1多万。。。

乐观锁与悲观锁

  • 上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。
  • 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。

接下来是基于SpringBoot+MP来完成乐观锁的搭配使用。

模拟冲突

数据库中增加商品表

CREATE TABLE 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 product (id, NAME, price) VALUES (1, '外星人笔记本', 100);

实体类

@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    private Integer version;
}

Mapper接口类

@Repository
public interface ProductMapper extends BaseMapper<Product> {

}

测试,模拟的是开头讲到的场景

@SpringBootTest
public class ProductMapperTest {

    @Autowired
    private ProductMapper productMapper;

    @Test
    void testConcurrentUpdate() {
        //1、小李
        Product p1 = productMapper.selectById(1L);
        System.out.println(p1.getPrice());
        //2、小王
        Product p2 = productMapper.selectById(1L);
        System.out.println(p2.getPrice());
        //3、小李将价格加了50元,存入了数据库
        p1.setPrice(p1.getPrice() + 50);
        productMapper.updateById(p1);
        //4、小王将商品减了30元,存入了数据库
        p2.setPrice(p2.getPrice() - 30);
        int result = productMapper.updateById(p2);
        if (result == 0) {//更新失败,重试
            //重新获取数据
            p2 = productMapper.selectById(1L);
            //更新
            p2.setPrice(p2.getPrice() - 30);
            productMapper.updateById(p2);
        }
        //最后的结果
        Product p3 = productMapper.selectById(1L);
        System.out.println("最后的结果:" + p3.getPrice());
    }
}

解决方案(乐观锁注入)

原理

数据库中添加version字段
取出记录时,获取当前version

SELECT id,`name`,price,`version` FROM product WHERE id=1

更新时,version + 1,如果where语句中的version版本不对,则更新失败

UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1

实现

实体类更新

对version字段添加@Version注解

@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    @Version
    private Integer version;
}

进行一系列配置

创建包config,创建配置类MybatisPlusConfig

注意:此时拥有了删除配置类,就可以将启动类中的 @MapperScan 扫描注解 移动到这边。

然后注入乐观锁的Bean组件

@Configuration
@MapperScan("com.xn2001.mapper")
public class MybatisPlusConfig {

    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
    
}

回到原来的测试中继续测试,将数据库70重新改为100,运行测试类。


Last modification:June 24, 2020
如果觉得我的文章对你有用,请随意赞赏