MyBatisPlus+SpringBoot实现乐观锁功能

news/2025/2/12 16:15:08/

一、商城数据不一致的场景

如果商城中有一件商品,成本价是80元,售价是100元。经理先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,经理觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1万多。

二、演示这一过程

1、数据库中增加商品表

CREATE TABLE product
(id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',price INT(11) DEFAULT 0 COMMENT '价格',version INT(11) DEFAULT 0 COMMENT '乐观锁版本号',PRIMARY KEY (id)
);INSERT INTO product (id, NAME, price) VALUES (1, '笔记本', 100);

2、创建实体类

@Data
public class Product {private Long id;private String name;private Integer price;private Integer version;
}

3、创建Mapper

public interface ProductMapper extends BaseMapper<Product> {}

4、进行测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductVersionTest {@Resourceprivate ProductMapper productMapper;@Testpublic void testProductUpdate() {//1、小李Product p1 = productMapper.selectById(1L);//2、小王Product p2 = productMapper.selectById(1L);//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);//最后的结果Product p3 = productMapper.selectById(1L);System.out.println("最后的结果:" + p3.getPrice());}
}

最后输出的是 70元,与经理预期的120元不同,导致亏损,如何防止这样的异常发生,解决方案是使用乐观锁

三、乐观锁方案

数据库中添加version字段:取出记录时,获取当前version

SELECT id,`name`,price,`version` FROM product WHERE id=1

更新时,version + 1,如果where语句中的version版本不对,则更新失败

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

四、乐观锁实现流程

1、修改实体类

添加 @Version 注解

@Version
private Integer version;

2、添加乐观锁插件

@Configuration
//@MapperScan("com.koo.modules.*.dao")
public class MybatisPlusConfig {/*** 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//实现乐观锁,保证数据的准确性interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}@Beanpublic ConfigurationCustomizer configurationCustomizer() {return configuration -> configuration.setUseDeprecatedExecutor(false);}}

3、优化流程(判断第二次更新数据是否成功,不成功则重新取数据进行更新)

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductVersionTest {@Resourceprivate ProductMapper productMapper;@Testpublic void testProductUpdate() {//1、小李Product p1 = productMapper.selectById(1L);//2、小王Product p2 = productMapper.selectById(1L);//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);if(result2 == 0){//更新失败,重试System.out.println("小王重试");//重新获取数据p2 = productMapper.selectById(1L);//更新p2.setPrice(p2.getPrice() - 30);productMapper.updateById(p2);}//最后的结果Product p3 = productMapper.selectById(1L);System.out.println("最后的结果:" + p3.getPrice());}
}

输出结果为120,数据正确

至此,一个简单的乐观锁就实现了。


http://www.ppmy.cn/news/34353.html

相关文章

分布式事务详解

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;分布式事务详解 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林在闪闪发光…

CSRF漏洞的概念、利用方式、防御方案

CSRF漏洞1.CSRF的概念1.1 什么是CSRF&#xff1f;1.2 基本攻击流程2.CSRF攻击实现2.1 靶场练习2.2 CSRFXSS组合拳2.2.1 攻击页面部署2.2.2 构造恶意xss语句&#xff0c;实现重复生效的CSRF3. CSRF攻击的防御**3.1 只使用JSON API****3.2 验证HTTP Referer字段****3.3 在请求地址…

国产化大趋势下学习linux的必要性

由于国际上的一些国家的制裁和威胁。最近几年国产化大趋势慢慢的兴起&#xff0c;我们国产化硬件的需求越来越大。对国产操作系统的需求也越来越多&#xff0c;那么我们一直用的Windows系统为什么不用了呢&#xff1f;众所周知的原因&#xff0c;不管是最新的Windows11还是正值…

go错误处理(2)——panic函数使用及捕获

前言 前面我们讲过了error类型来处理一般的错误&#xff0c;本文会描述使用panic函数和recover函数来处理比较极端的错误。简单来说&#xff0c;当程序运行时遇到无法处理的错误或异常情况时&#xff0c;会调用panic函数引发一个运行时错误&#xff0c;此时程序会终止执行并抛…

‘/’ 和 ‘%’ 在编程中的作用【附加练习题】

‘/’和‘%’在编程中有非常重要的作用&#xff0c;使用它们可以说是在使用一种简单算法&#xff0c;不仅易于理解&#xff0c;而且会极大的减少你的代码量&#xff0c;让你的程序看起来高级一点点&#x1f92a;&#xff01;/ 除我们通常都是除10的倍数&#xff0c;比如‘10’只…

真要被00后职场整顿了?老员工纷纷表示真的干不过.......

最近聊到软件测试的行业内卷&#xff0c;越来越多的转行和大学生进入测试行业。想要获得更好的待遇和机会&#xff0c;不断提升自己的技能栈成了测试老人迫在眉睫的问题。 不论是面试哪个级别的测试工程师&#xff0c;面试官都会问一句“会编程吗&#xff1f;有没有自动化测试…

Postgres schema search_path

/**在 TEST_DM库中新建 schema&#xff1a;test_dm 并授权*/CREATE schema test_dm authorization uown_test_dm;CREATE SCHEMA (模式名&#xff09;AUTHORIZATION&#xff08;用户名&#xff09;中的用户名指的是将拥有该模式的用户名&#xff0e;如果省略&#xff0c;缺省为执…

Linux网络 远程访问及控制

目录 1、SSH远程管理 &#xff08;1&#xff09;SSH原理 &#xff08;2&#xff09;SSH客户端APP以及服务端APP &#xff08;3&#xff09;ssh服务端主要包括两个服务功能 ssh远程连接和sftp服务 &#xff08;4&#xff09;ssh远程登录方式 (5)服务配置与管理 (6)安全调…