当前位置: 首页 > article >正文

Mybatis Plus乐观锁实现

1 引言

乐观锁的主要作用在于提高系统的并发性能和减少锁冲突,同时保证数据的一致性。‌其原理简单来说就是,在修改数据的时候,判断数据是否被其他人改过,如果已被其他人改过,则修改失败。

2 代码

在SpringBoot 3.x+Mybatis Plus多数据源极简配置中的项目基础上更新代码,下面列出需要更新的代码。
在这里插入图片描述

2.1 org/example/mapper/InventoryMapperMaster.java

package org.example.mapper;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.example.entity.Inventory;

@DS("master")
public interface InventoryMapperMaster extends BaseMapper<Inventory> {


}

2.2 org/example/entity/Inventory.java

@Version就是乐观锁字段,用于判断数据是否被修改过。

package org.example.entity;

import com.baomidou.mybatisplus.annotation.*;

import java.util.Date;

/*
 * 库存
 * */
@TableName("t_inventory")
public class Inventory {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer quantity;
    @Version
    @TableField(fill = FieldFill.INSERT)
    private Integer version;
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}

2.3 org/example/controller/InventoryController.java

package org.example.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.example.entity.Inventory;
import org.example.entity.User;
import org.example.mapper.InventoryMapperMaster;
import org.example.mapper.UserMapperMaster;
import org.example.mapper.UserMapperSlave;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Controller
@RequestMapping("/InventoryController")
public class InventoryController {


    @Autowired
    private InventoryMapperMaster inventoryMapperMaster;


    @RequestMapping(value = "/insert")
    @ResponseBody
    @Transactional
    public Object insertMaster() {
        for (int i = 0; i < 5; i++) {
            Inventory inventory = new Inventory();
            inventory.setName("商品" + i);
            inventory.setQuantity(10);
            inventoryMapperMaster.insert(inventory);
        }
        return "数据插入成功";
    }

    @RequestMapping(value = "/update1")
    @ResponseBody
    @Transactional
    public Object update1() {
        QueryWrapper<Inventory> q = new QueryWrapper<>();
        q.eq("name", "商品1");
        Inventory inventory = inventoryMapperMaster.selectOne(q);
        inventory.setQuantity(5);
        try {
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        int i = inventoryMapperMaster.updateById(inventory);
        System.out.println("update1更新:" + i + "条");
        return "数据修改成功";
    }


    @RequestMapping(value = "/update2")
    @ResponseBody
    @Transactional
    public Object update2() {
        QueryWrapper<Inventory> q = new QueryWrapper<>();
        q.eq("name", "商品1");
        Inventory inventory = inventoryMapperMaster.selectOne(q);
        inventory.setQuantity(0);
        int i = inventoryMapperMaster.updateById(inventory);
        System.out.println("update2更新:" + i + "条");
        return "数据修改成功";
    }

}

2.4 org/example/conf/MyMetaObjectHandler.java

package org.example.conf;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/*
 MyBatis-Plus 提供了一个便捷的自动填充功能,用于在插入或更新数据时自动填充某些字段,如创建时间、更新时间等
 */
@Component
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", Integer.valueOf("0"), metaObject);
    }

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

2.5 org/example/conf/MybatisPlusConfig.java

package org.example.conf;

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;

/*
 MyBatis-Plus 配置
 */
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //添加分页拦截器
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        //添加乐观锁拦截器
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

}

3 测试

3.1 创建库存表

CREATE TABLE `t_inventory` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) DEFAULT NULL COMMENT '名称',
  `quantity` int(11) DEFAULT NULL COMMENT '数量',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `version` int(11) DEFAULT NULL COMMENT '版本',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3.2 新增数据

启动项目,然后打开浏览器访问:http://localhost:8080/InventoryController/insert,可以看到当前所有商品的库存数量都是10个。
在这里插入图片描述

3.3 修改数据

  1. 我们以“商品1”为例,先访问http://localhost:8080/InventoryController/update1,将“商品1”的数量改为5。然后立即(10秒内)访问:http://localhost:8080/InventoryController/update2,将“商品1”的数量改为0。为了模拟并发的情况,这里让update1休眠了10秒钟再提交。
    在这里插入图片描述
  2. 可以看到sql中自动加入了version=0的条件,这个version就是乐观锁,由系统自动维护,每次成功修改数据自动+1。具体流程是:
    2.1 update1 首先查询商品1,这时quantity=10,version=0,查完进入休眠。
    2.2 update2 在update1 休眠期间,将商品1的数量改为0,这时version自动+1,变成quantity=0,version=1。
    2.3 update1 休眠结束后提交更新,仍然按照version=0的条件更新数据,此时version=0的数据已经不存在了,因此可以看到update1更新了0条数据,避免了数据不一致的情况。
    在这里插入图片描述

5 完整代码

Gitee代码链接


http://www.kler.cn/a/280683.html

相关文章:

  • 测试用例(还需要输入1个字)
  • 使用 Java 记录简化 Spring Data 中的数据实体
  • AD7606芯片驱动-FPGA实现
  • 贪心算法---分发糖果
  • JUC-Synchronized原理进阶
  • 基于SSM+微信小程序的跑腿平台管理系统(跑腿3)(源码+sql脚本+视频导入教程+文档)
  • 【微信小程序】残存良知之开启npm支持
  • 驱动开发系列13 - Linux Graphics 图形驱动概述(二)
  • 独角数卡,打开商品列表出现Undefined variable form的解决办法
  • sql盲注python脚本学习 (基于bWAPP靶场)
  • vue使用vite配置跨域以及环境配置详解
  • 小程序常用的模板语法
  • Visual Basic:多线程编程的优雅之舞
  • 设计模式实战总结与Java程序员学习路线图
  • 十八、array 类
  • linux检查系统是否感rootkit
  • Web前端性能优化合集
  • HTTP 414错误问题
  • 开源的工作流系统突出优点总结
  • Visual Basic中的继承艺术:构建代码的层级之美