十三、MybatisPlus-插件功能-乐观锁插件

243 阅读3分钟

本文是系列文章,目录:
一、MybatisPlus-基本使用
二、MybatisPlus-进阶使用-条件构造器
三、MybatisPlus-进阶使用-自定义sql
四、MybatisPlus-进阶使用-Service接口(1)-基本使用
五、MybatisPlus-进阶使用-Service接口(2)-自定义service
六、MybatisPlus-进阶使用-Service接口(3)- 批量新增
七、MybatisPlus-进阶使用-逻辑删除
八、MybatisPlus-进阶使用-枚举处理器
九、MybatisPlus-进阶使用-JSON类型处理器
十、MybatisPlus-进阶使用-配置文件加密
十一、MybatisPlus-插件功能-分页插件(1)
十二、MybatisPlus-插件功能-分页插件(2)-通用分页封装
十三、MybatisPlus-插件功能-乐观锁插件
十四、MybatisPlus-插件功能-sql性能分析
十五、MybatisPlus-自动填充字段
MybatisPlus-问题汇总

简介

当出现并发操作时,需要确保各个用户对数据的操作不产生冲突,此时需要一种并发控制手段。

悲观锁是,在对数据库的一条记录进行修改时,先直接加锁(数据库的锁机制),锁定这条数据,然后再进行操作;

乐观锁,它比较乐观,会假设不存在冲突,会先查询当前记录的版本号,然后在实际进行数据操作时,会比对版本号是否是读取时的版本号,如果是则更新成功,并且版本号+1,若果不是则更新失败。乐观锁的一种通常实现是版本号,在MySQL中也有名为MVCC的基于版本号的并发事务控制。

在读多写少的场景下:乐观锁比较适用,能够减少加锁操作导致的性能开销,提高系统吞吐量。

在写多读少的场景下:悲观锁比较使用,否则会因为乐观锁不断失败重试,反而导致性能下降。

MyBatis-Plus 提供了 OptimisticLockerInnerInterceptor 插件,使得在应用中实现乐观锁变得简单,因为它会在更新数据时,自动在where条件上加入版本号,且会自动升级版本号。

乐观锁的实现原理

乐观锁的实现通常包括以下步骤:

  1. 读取记录时,获取当前的版本号(version)。
  2. 更新时,带上这个version。
  3. 执行更新操作时,设置 version = newVersion 的条件为 version = oldVersion
  4. 如果oldVersion与数据库中的version不一致,就更新失败。

这种思想和CAS(Compare And Swap)非常相似。

乐观锁实现步骤

  1. 配置乐观锁插件
  2. 在实体中版本号字段上添加@Version注解

1.配置乐观锁插件

package com.pino.demo.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 MybatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
       // 初始化核心插件
       MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
       // 添加乐观锁插件
       interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
       return interceptor;
    }
}

2.在实体中版本号字段上添加@Version注解

/**
 * 版本号
 */
@Version
private Integer version;

3. 在业务层修改更新方法

public void saveUser(@RequestBody UserFormDTO userFormDTO){
    // 1.转换DTO为PO
    User user = BeanUtil.copyProperties(userFormDTO, User.class);
    // 如果是新增,则版本号置为0
    if (user.getId() == null) {
       user.setVersion(0);
    } else {
    // 如果是修改,则先查询出来旧版本号
       User u = userService.getById(user.getId());
       user.setVersion(u.getVersion());
    }
    // 2.新增
    boolean updateResult = userService.saveOrUpdate(user);
    if (updateResult) {
       System.out.println("更新成功");
    } else {
       System.out.println("更新失败");
    }

}

执行结果:

image.png

注意事项

  • 支持的数据类型包括:intIntegerlongLongDateTimestampLocalDateTime
  • 对于整数类型,newVersion 是 oldVersion + 1
  • newVersion 会自动回写到实体对象中。
  • 支持内置的 updateById(entity) 和 update(entity, wrapper)saveOrUpdate(entity)insertOrUpdate(entity) (version >=3.5.7) 方法。
  • 自定义方法更新时如果满足内置参数的参数条件方式也会执行乐观锁逻辑,例如自定义myUpate(entity) 这个和 updateById(entity) 是等价的,会提取参数进行乐观锁填充,但更新实现需要自行处理。
  • 在 update(entity, wrapper) 方法中,wrapper 不能复用。