springboot整合mybatisPlus(3) 乐观锁的实现

1:乐观锁

1.1:乐观锁简介

乐观锁:总是假设最好的情况,在读取数据的使用不会发生并发问题,但在更新的时候比较原数据是否被其他线程发生了改变。主要通过通过版本号机制或CAS算法实现,适用于读多写少的应用场景。

版本号机制:在数据库表中加一个版本号version字段,表示数据被修改的次数,在修改数据前先读取该表中的版本号字段,在修改的使用对比是否是自己读取出来的版本号如果是则进行更新操作并版本号(version)加1如果不是则重新执行进行更新操作直到更新成功为止。

CAS:compare and swap 顾名思义就是比较与替换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。
内存位置的值是否与预期原值一样如果一样则用新值更新,如果不则重试。CAS算法实现会导致ABA问题的产生。

ABA问题:A线程某一时刻获取内存位置的值为10,与其原值也为10,在这个过程中假设B线程对内存位置的值进行了修改,修改我为12,下一毫米又对内存中的值进行了修改,修改为10。这是A线程把值修改为13修改成功。这就是ABA问题。通俗易懂的来讲就是:你大爷是你大爷,你大妈已经不是你大妈了。乐观锁通常使用版本号机制来避免ABA问题。

1.2:乐观锁的例子

未使用乐观锁(实现存钱取钱)

A操作人员 B操作人员
查询余额(100¥) 查询余额(100¥)
存入10¥(100¥+10¥) 喝咖啡中
喝咖啡中 取出10¥(100¥-10¥)
查询余额 (90¥) 喝咖啡中
查询余额 (90¥) 查询余额 (90¥)

以使用乐观锁(实现存钱取钱)

A操作人员 B操作人员
查询余额(100¥)version=1 查询余额(100¥)version=1
存入10¥(100¥+10¥)把查询余额中的version进行对比匹配version+1充值成功 喝咖啡中
喝咖啡中 取出10¥(100¥-10¥)把查询余额中的version进行对比,结果不匹配 ,取出不成功,重新进行取钱操作,查询余额(110¥)version=2,把查询余额中的version进行对比匹配version+1取出成功 (110¥-10¥)
查询余额 (100¥) 喝咖啡中
查询余额 (100¥) 查询余额 (100¥)

2:springboot整合mybatisPlus 乐观锁插件

本章会设计到 springboot整合mybatisPlus整合使用,springboot整合swagger2整合使用,需要的可以看我其他的博文

2.1 插件配置

package cloud.xingzhe.springbootmybatisplus;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@MapperScan("cloud.xingzhe.springbootmybatisplus.mapper")
public class SpringbootMybatisPlusApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootMybatisPlusApplication.class, args);
    }
    /**
     *乐观锁插件
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

2.2 注解实体字段 @Version 必须要!

package cloud.xingzhe.springbootmybatisplus.model;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.Version;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import javax.validation.Valid;

/**
 * @author 行者
 * @since 2020-05-08
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="Bank对象", description="")
public class Bank implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer id;
    @ApiModelProperty(value = "余额")
    private Integer amount;
    @ApiModelProperty(value = "版本号")
    @Version
    private Integer version;
    @ApiModelProperty(value = "用户id")
    private Integer userId;
}

特别说明:

支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下 newVersion = oldVersion + 1
newVersion 会回写到 entity 中
仅支持 updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!

3:代码演示

3.1:数据库表

CREATE TABLE bank (
id int(11) NOT NULL,
amount int(10) NULL DEFAULT NULL COMMENT ‘余额’,
version int(5) NULL DEFAULT NULL COMMENT ‘版本号’,
user_id int(11) NULL DEFAULT NULL COMMENT ‘用户id’,
PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO bank VALUES (1, 100, 0, 2);

3.2:测试并发接口

package cloud.xingzhe.springbootmybatisplus.controller;
import cloud.xingzhe.springbootmybatisplus.model.Bank;
import cloud.xingzhe.springbootmybatisplus.service.IBankService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * 

* 前端控制器 *

* @author 行者 * @since 2020-05-08 */
@Api(tags = "银行管理") @RestController @RequestMapping("/sys/bank") public class BankController { @Autowired private IBankService bankService; @ApiOperation(value="存取测试乐观锁") @RequestMapping(value = "/access",method = RequestMethod.GET) @ResponseBody public String access(String userId,Integer money) throws InterruptedException { QueryWrapper<Bank> queryWrapper=new QueryWrapper<>(); queryWrapper.eq("user_id", userId); Bank bank = bankService.getOne(queryWrapper); Integer amount= bank.getAmount(); bank.setAmount(amount+money); Thread.sleep(5000); boolean b = bankService.updateById(bank); if (b){ return "更新成功"; } return "更新失败,余额已被其他操作人员修改,请重试"; } }

3.2:接口访问截图

springboot整合mybatisPlus(3) 乐观锁的实现_第1张图片
springboot整合mybatisPlus(3) 乐观锁的实现_第2张图片
注意:开两个swagger2 页面进行测试 ,测试的money的参数最好不一样 (有些浏览器接口和参数完全一样会等待接口数据放回才再次请求)
数据库中的version字段会每次都会加一 可以观察看看

5:相关连接:

源码地址: https://github.com/xingzhewenzi/springboot-examples.git
用到的博文地址:
springboot整合mybatis-plus(1) mybatis魂斗罗兄弟p2 实现单表curd零sql
springboot整合mybatisPlus代码生成器 快速生成controller service mapper
springboog整合swagger2 实现便捷高效的接口文档

你可能感兴趣的:(mysql,springboot,mybatis)