11.基于mybatisplus实现乐观锁

111 阅读3分钟

简介: 讲解高并发里面的乐观锁介绍

  • 什么是乐观锁

image-20201229145504803

每次去拿数据的时候都认为别人不会修改,更新的时候会判断是别人是否回去更新数据,通过版本来判断,如果数据被修改了就拒绝更新
​
Java里面大量使用CAS, CAS这个是属于乐观锁,性能较悲观锁有很大的提高
AtomicXXX 等原子类底层就是CAS实现,一定程度比synchonized好,因为后者是悲观锁
​
小结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景,乐观锁的吞吐量会比悲观锁多

image-20200212185212328

 

  • 数据库的乐观锁
大多是基于数据版本 (Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通
​
过为数据库表增加一个 “version” 字段来 实现。 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据,库表对应记录的当前版本信息进行比对,如果提交的数据 版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据

Step1.MybatisPlus轻松实现乐观锁

CREATE TABLE `banner` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `img` varchar(524) DEFAULT NULL COMMENT '图片',
  `url` varchar(524) DEFAULT NULL COMMENT '跳转地址',
  `weight` int DEFAULT NULL COMMENT '权重',
  `version` int DEFAULT NULL COMMENT '乐观锁版本号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

模拟数据:

image.png

BannerDO.java 实体类

package com.lzh.xd_shop.model;

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

import java.io.Serializable;

/**
 * @Author:kaiyang.cui
 * @Package:com.lzh.xd_shop.model
 * @Project:xd_shop
 * @name:BannerDO
 * @Date:2023/4/1 下午3:47
 * @Filename:BannerDO
 * @Description:BannerDO
 * @Version:1.0
 */
@Data
@TableName("banner") // 表名映射,mybatisplus必须加
public class BannerDO implements Serializable{

    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    private String img;
    private String url;
    private Integer weight;

    @Version
    private Integer version;
}

MybatisPlusPageConfig.java

分页插件和乐观锁插件
package com.lzh.xd_shop.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;

/**
 * @Author:kaiyang.cui
 * @Package:com.lzh.xd_shop.config
 * @Project:xd_shop
 * @name:MybatisPlusPageConfig
 * @Date:2023/4/1 下午9:31
 * @Filename:MybatisPlusPageConfig
 * @Description:分页插件和乐观锁插件
 * @Version:1.0
 */
@Configuration
public class MybatisPlusPageConfig {
    /**
     * 新的分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        //分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

        //乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

}

Step2. 乐观锁的使用【mybatisPlus版】

场景1 没有其他人更新banner表中id=7的记录

更新前的数据表:

image.png

@Test
@DisplayName("banner-MyBatisPlus乐观所的使用")
public void test21() throws Exception {
    // 一般都是先通过id找到对应的记录,得到id和version,但是mybatisplus帮我们做了

    BannerDO bannerDO = new BannerDO();
    bannerDO.setVersion(1);
    bannerDO.setId(7);
    bannerDO.setUrl("juejin.cn");
    bannerMapper.updateById(bannerDO);
}

image.png

可以发现sql语句,UPDATE banner SET url=?, version=? WHERE id=? AND version=?, 每次更新操作都会在where 后面带上version字段,如果versin字段还是为1,说明此时没有其他人修改这条数据,则执行更新成功!

更新后的数据表:可以看到,在其他人没有修改id=7的这一行记录的时候,此时,“我”正在执行UPDATE banner SET url=?, version=? WHERE id=? AND version=?,则更新操作成功,数据表中version的值由1变为2。

image.png

场景2: 有人更新id=7的记录时,则修改失败

我们在执行更新操作前,再来看一眼数据表:

image.png

@Test
@DisplayName("banner-MyBatisPlus乐观所的使用")
public void test21() throws Exception {
    // 一般都是先通过id找到对应的记录,得到id和version,但是mybatisplus帮我们做了

    BannerDO bannerDO = new BannerDO();
    bannerDO.setVersion(2);
    bannerDO.setId(7);
    bannerDO.setUrl("juejin.cn");
    bannerMapper.updateById(bannerDO);
}

在执行上述测试用例时,我们在更新操作前打一个断点,debug模式运行

image.png

紧接着,在数据表中修改id=7的记录,将字段versin的值由2偷偷修改为3,来模拟其他用户修改词条记录。

image.png

此时执行下一步刚刚断点的位置。

image.png

发现还是3,别人抢先在数据表中更新了id=7的记录,使得“我”的更新操作并没有修改成功,这个例子使用了乐观锁,可以应用于秒杀、发放优惠券业务。