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

MyBatis Plus 乐观锁与悲观锁

1、场景展现

一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。

此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。

现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1万多。

2、乐观锁与悲观锁

如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。

如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。

3、模拟示例

1.创建商品表

CREATE TABLEt_product
(id BIGINT(20) NOT NULL COMMENT '主键ID',NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称 ',price INT(11) DEFAULT 0 COMMENT '价格 ',VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号 ',
PRIMARY KEY (id)
);

2.添加一条数据

INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本 ', 100);

3.添加 JavaBean

注意与表名的下划线构成驼峰命名

package com.goose.entity;import lombok.Data;@Data
public class TProduct {private Long id;private String name;private Integer price;private Integer version;
}

4.创建 mapper

注意添加注解 @Repository

package com.goose.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.goose.entity.TProduct;
import org.springframework.stereotype.Repository;@Repository
public interface ProductMapper extends BaseMapper<TProduct> {}

5.没有乐观锁时

这里直接使用mapper对象进行测试,不创建Service和ServiceImpl

@SpringBootTest
public class HappyAndUpsetTest {@Autowiredpublic ProductMapper productMapper;
}

不加锁时

@Test
public void test01() {//1、小李TProduct p1 = productMapper.selectById(1L);System.out.println("小李取出的价格:" + p1.getPrice());//2、小王TProduct p2 = productMapper.selectById(1L);System.out.println("小王取出的价格:" + p2.getPrice());//3、小李将价格加了50元,存入了数据库p1.setPrice(p1.getPrice() + 50);int result1 = productMapper.updateById(p1);System.out.println("小李修改结果:" + result1);//4、小王将商品减了30元,存入了数据库p2.setPrice(p2.getPrice() - 30);int result2 = productMapper.updateById(p2);System.out.println("小王修改结果:" + result2);//最后的结果TProduct p3 = productMapper.selectById(1L);//价格覆盖,最后的结果:70System.out.println("最后的结果:" + p3.getPrice());
}

输出:

最后的结果:70

6.添加乐观锁

(1)修改实体类,添加 @Version 注解

package com.goose.entity;import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;@Data
public class TProduct {private Long id;private String name;private Integer price;@Versionprivate Integer version;
}

(2)添加乐观锁配置

package com.goose.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 MyConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));//添加乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}
}

(3)测试

@Test
public void testConcurrentVersionUpdate() {
//小李取数据TProduct p1 = productMapper.selectById(1L);
//小王取数据TProduct p2 = productMapper.selectById(1L);
//小李修改 + 50p1.setPrice(p1.getPrice() + 50);int result1 = productMapper.updateById(p1);System.out.println("小李修改的结果:" + result1);
//小王修改 - 30p2.setPrice(p2.getPrice() - 30);int result2 = productMapper.updateById(p2);System.out.println("小王修改的结果:" + result2);if (result2 == 0) {//失败重试,重新获取version并更新p2 = productMapper.selectById(1L);p2.setPrice(p2.getPrice() - 30);result2 = productMapper.updateById(p2);}System.out.println("小王修改重试的结果:" + result2);//老板看价格TProduct p3 = productMapper.selectById(1L);System.out.println("老板看价格:" + p3.getPrice());
}

输出:

老板看价格:90

解读:

🧩 第一步:两个线程分别读取同一个商品数据

TProduct p1 = productMapper.selectById(1L); // 小李
TProduct p2 = productMapper.selectById(1L); // 小王

数据库当前数据为:

{id: 1,name: "外星人笔记本"price: 100,version: 1
}
此时:
  • 小李拿到的是:price=100, version=1
  • 小王拿到的也是:price=100, version=1
🧩 第二步:小李先更新商品价格(+50)
p1.setPrice(150);
int result1 = productMapper.updateById(p1);

MyBatis Plus 执行类似 SQL:

UPDATE product
SET price = 150, version = version + 1
WHERE id = 1 AND version = 1

✅ 由于数据库中 version = 1,匹配成功,更新成功,version 自动加 1:

数据库变为:

{id: 1,price: 150,version: 2
}

输出:

小李修改的结果:1

🧩 第三步:小王尝试修改价格(-30)

p2.setPrice(70);
int result2 = productMapper.updateById(p2);

MyBatis Plus 执行的 SQL:

UPDATE product
SET price = 70, version = version + 1
WHERE id = 1 AND version = 1

⚠️ 此时数据库 version 已是 2,**不再匹配** → **更新失败** → 返回 0。

输出:

小王修改的结果:0

🧩 第四步:小王检测失败,重试更新

p2 = productMapper.selectById(1L); // 重新读取最新数据(price=150, version=2)
p2.setPrice(p2.getPrice() - 30); // 150 - 30 = 120
result2 = productMapper.updateById(p2); // version=2

MyBatis Plus 执行的 SQL:

UPDATE product
SET price = 120, version = version + 1
WHERE id = 1 AND version = 2

✅ version 匹配,更新成功 → 数据变为:

{id: 1,price: 120,version: 3
}

输出:

小王修改重试的结果:1

🧩 第五步:老板查询商品价格

TProduct p3 = productMapper.selectById(1L);
System.out.println("老板看价格:" + p3.getPrice());

最终输出:

老板看价格:120

执行流程图

1. 小李读取:price=100, version=1

2. 小王读取:price=100, version=1

3. 小李更新成功 → price=150, version=2

4. 小王更新失败(version=1 不匹配)

5. 小王重试 → 重新读取 → price=150, version=2

6. 小王再次更新成功 → price=120, version=3

7. 老板看到价格 = 120

http://www.dtcms.com/a/301015.html

相关文章:

  • 《C++ list 完全指南:list的模拟实现》
  • NodeJs接入腾讯云存储COS
  • MySQL 用户管理
  • 第六章 JavaScript 互操(3)JS调用.NET
  • Django5.1(131)—— 表单 API二(API参考)
  • 电科金仓 KingbaseES 深度解码:技术突破・行业实践・沙龙邀约 -- 融合数据库的变革之力
  • Java面试宝典:MySQL索引
  • 2-4、Dify案例实践—基于工作流构建商城用户评价智能分析系统
  • PyTorch武侠演义 第一卷:初入江湖 第7章:矿洞中的计算禁制
  • 基于mnn架构在本地 c++运行llm与mllm模型
  • 数据结构基本内容(第四篇:队列)
  • 2025.7.27
  • Java面试题及详细答案120道之(061-080)
  • C++算法竞赛篇(六)一维数组题型讲解
  • 【工具】python汇总发票(含源码)
  • Java排序算法之<希尔排序>
  • 7月27日星期日今日早报简报微语报早读
  • GitHub 趋势日报 (2025年07月25日)
  • Linux 系统网络配置及 IP 地址相关知识汇总
  • STM32 I2C通信完整教程:从协议原理到硬件实现
  • 一文快速了解Docker和命令详解
  • 模拟实现python的sklearn库中的Bunch类以及 load_iris 功能
  • 文件权限标记机制在知识安全共享中的应用实践
  • minio 对象存储
  • java的break能加标签,return可以加标签吗
  • 从一副蓝牙耳机里get倍思的“实用而美”
  • Python 程序设计讲义(23):循环结构——循环控制语句 break 与 continue
  • 背包DP之多重背包
  • 边缘提取算法结合深度学习的肺结节分割预测
  • 「日拱一码」040 机器学习-不同模型可解释方法