MyBatis-Plus 实现乐观锁

MyBatis-Plus 实现乐观锁

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

作用:乐观锁是为了解决并发过程中数据更新冲突的问题,乐观锁能提高并发过程中的程序吞吐量。

1、版本号控制

使用数据版本(Version)记录机制实现乐观锁,这是乐观锁最常用的一种实现方式。什么是数据版本呢?数据版本具体是指为数据增加一个版本标识,一般是通过为数据库表内增加一个数字类型的 “version” 字段来实现,当读取数据时,将 version 字段的值一同读出,数据每更新一次,则对此 version 自增1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的 version 值进行比对,如果数据记录当前版本号与第一次取出来的 version 值相等,则予以更新,否则认为是过期数据,不予更新。

如上图所示,如果更新操作顺序执行,则数据的版本(version)依次递增,不会产生冲突。但是如果发生有不同的业务操作对同一版本的数据进行修改,那么,先提交的操作(图中B)会把数据 version 更新为2,当 A 在 B 之后提交更新时发现数据的 version 已经被修改了,那么 A 的更新操作会失败。


2、如何实现乐观锁?

@Version 注解标记乐观锁,通过 version 字段来保证数据的安全性,当修改数据的时候,会以 version 作为条件,当条件成立的时候才会修改成功。

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时,update tableName set version = oldVersion + 1 where version = oldVersion
  • 如果 version 不对,就更新失败

1、给数据库表添加 version 字段,并设置默认值为1;

MySQL-User 数据表信息如下:

2、实体类增加 version 属性,并添加 @Version 注解;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor

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

    //乐观锁
    @Version
    private Integer version;
    //字段添加填充内容
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

3、注册配置类(组件);

//扫描我们的repository文件夹
@MapperScan("com.trainingl.repository")
@EnableTransactionManagement
@Configuration  //配置类
public class MyBatisPlusConfig {
    
    

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    
    
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //注册乐观锁插件
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
    
}

4、测试乐观锁;

// 测试乐观锁成功
@Test
public void testOptimisticLock(){
    
    
    //修改用户信息时,先查询用户信息,再进行修改
    //1. 查询用户信息
    User user = userMapper.selectById(1L);
    //2. 修改用户信息
    user.setName("zhangsan");
    user.setAge(36);
    user.setEmail("[email protected]");
    //3. 执行更新操作
    userMapper.updateById(user);
}

从控制台的日志信息发现:修改数据时 version 作为条件判断,并且 version 自动完成自增操作,即:version = version+1

// 测试多线程下乐观锁失败
@Test
public void testOptimisticLock1(){
    
    
    //线程 1
    User user1 = userMapper.selectById(1L);
    user1.setName("zhangshan");
    user1.setEmail("[email protected]");

    //线程 2(在线程1的修改操作未来得及执行时介入)
    User user2 = userMapper.selectById(1L);
    user2.setName("kuangsan");
    user2.setEmail("[email protected]");
    userMapper.updateById(user2);

    //如果没有乐观锁就会覆盖插队线程的值!
    userMapper.updateById(user1); //更新失败
}

线程 1 的日志信息(更新成功):

线程 2 的日志信息(更新失败):

乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定。

猜你喜欢

转载自blog.csdn.net/qq_41775769/article/details/123077430