12

【mybatis-plus】什么是乐观锁?如何实现“乐观锁”

 3 years ago
source link: http://www.cnblogs.com/pingguo-softwaretesting/p/14188305.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

“乐观锁”这个词以前我也没听过。上次在测试需求的时候,查询数据库发现有一个 version 字段,于是请教开发这个字干嘛使,

人家回复我:乐观锁,解决并发更新用的。当时大家都忙,咱也不敢多问。

今天就来折腾一下“乐观锁”。

一、什么是乐观锁

乐观锁其实用一句话来形容其作用就是:当要更新一条记录的时候,希望这条记录没有被别人更新,从而实现线程安全的数据更新。

结合下场景,记得那是一张库存表,有一个字段记录商品库存,涉及多个地方都有可能去更新它:

  1. 程序A 查询到了这条数据,得到库存是800,准备+200更新成1000,但是还没更新。
  2. 程序B 也查询到了这条数据,得到库存是800,准备-200更新成600,并且提交更新了。

那么,这时候A再提交更新之后,B就会发现明明是自己是800-200=600,怎么最后变成了1000?

这就是因为A的事务导致了B的数据更新丢失。

文字可能读起来比较晦涩,有请灵魂画手:

J7RN3eb.png!mobile

正常情况下:

  • 按先后顺序是, A先更新成1000,然后B再拿1000-200,更新成800,这样B就没异议了。
  • 或者实在要2个同时更新,那也只能有一个成功,这样也没异议。

二、MP来实现乐观锁

乐观锁的实现,通过增加一个字段,比如version,来记录每次的更新。

查询数据的时候带出version的值,执行更新的时候,会再去比较version,如果不一致,就更新失败。

还是用之前的user表,增加了新的字段 version

1.在实体类里增加对于的字段,并且加上自动填充(你也可以每次手动填充)

@Data
public class User {
    @TableId(type = IdType.ID_WORKER)
    private Long id;
    private String name;
    private Integer age;
    private String email;

    @TableField(fill = FieldFill.INSERT)        // 新增的时候填充数据
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE) // 新增或修改的时候填充数据
    private Date updateTime;

    @TableField(fill = FieldFill.INSERT)
    @Version
    private Integer version; // 版本号
}
@Component //此注解表示 将其交给spring去管理
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
        this.setFieldValByName("version", 0, metaObject); //新增就设置版本值为0
    }


    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

2. 配置插件

为了便于管理,可以见一个包,用于存放各种配置类,顺便把配置在启动类里的mapper扫描也换到这里来。

package com.pingguo.mpdemo.config;

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
// 配置扫描mapper的路径
@MapperScan("com.pingguo.mpdemo.mapper")
public class MpConfig {

    // 乐观锁插件
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

3.测试乐观锁

先新增一条测试数据:

//    新增
    @Test
    void addUser() {
        User user = new User();
        user.setName("大周");
        user.setAge(22);
        user.setEmail("[email protected]");
        userMapper.insert(user);
    }

新增成功,可以看到version值是0。

VbAnumE.png!mobile

再来试一下正常的修改:

//      测试乐观锁
    @Test
    void testOptimisticLocker() {
        User user = userMapper.selectById(1342502561945915393L);
        user.setName("大周2");
        userMapper.updateById(user);
    }

修改成功,可以看到version 变成了1。

umIZnuM.png!mobile

最后,模拟下并发更新,乐观锁更新失败的情况:

//  测试乐观锁-失败
    @Test
    void testOptimisticLockerFailed() {
        User user = userMapper.selectById(1342502561945915393L);
        user.setName("大周3");

        User user2 = userMapper.selectById(1342502561945915393L);
        user2.setName("大周4");

        userMapper.updateById(user2); // 这里user2插队到user前面,先去更新
        userMapper.updateById(user); // 这里由于user2先做了更新后,版本号不对,所以更新失败

    }

按照乐观锁的原理,user2是可以更新成功的,也就是name会修改为“大周4”,version会加1。user因为前后拿到的版本号不对,更新失败。

7NbMNvU.png!mobile

结果符合预期,我们也可以看下mybatis的日志,进一步了解一下:

可以看到上面首先是2个查询,查询到的version都是1。

ymEfMze.png!mobile

接着,第一个执行update语句的时候,where条件中version=1,可以找到数据,于是更新成功,切更新version=2。

而第二个再执行update的时候,where条件version=1,已经找不到了,因为version已经被上面的更新成了2,所以更新失败。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK