Mybatis Plus 乐观锁插件(手把手教学)
一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
截止目前, 星球 内专栏累计输出 66w+ 字,讲解图 2896+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2300+ 小伙伴加入学习 ,欢迎点击围观
前言
大家好,我是小哈。
程序设计中,若遇到高并发场景,需要多线程修改共享资源的时候,为了保证数据的原子性,就需要频繁使用到锁。锁的类型也有很多,如互斥锁、自旋锁、读写锁、悲观锁、乐观锁等,因为本文主要内容是 Mybatis Plus 实现乐观锁功能,具体就不展开讲了。
什么是乐观锁?
乐观锁就好比一个人性格非常积极乐观。当多个线程需要修改同一个资源时,乐观锁认为发生冲突的概率很低,所以不会有加锁的动作,先改了再说,所以乐观锁也称为无锁编程。
实现方式
乐观锁通常会使用版本号机制或者 CAS 算法来实现。 这里主要回顾一下版本号机制,通常是在建表时,新增一个 version
版本号字段,假设初始值为 1。当多线程修改某条记录时,会先进行查询操作(包括版本号 version
),后续修改数据时,会判断查询出来的当前版本号是否与数据库中的一致,一致才会进行更新,并且对 version
版本号进行加 1 操作,否则更新失败。
乐观锁应用场景
乐观锁适用于读多写少(读数据多写数据少)的场景,因为没有加锁,避免了锁竞争带来的性能消耗,所以吞吐量非常高。
Mybatis Plus 实现乐观锁
配置乐观锁插件
Mybatis Plus 有现成的乐观锁插件,就好比分页插件一样,集成一下即可实现相关功能。配置该插件有两种方式:
注意:考虑到项目中可能会添加多个插件,需要注意顺序关系,官方推荐顺序如下:
多租户插件 -> 动态表名插件 -> 分页插件 -> 乐观锁插件 -> sql 性能规范插件 -> 防止全表更新与删除插件。
Spring Boot 整合
新建 config
包,用于放置配置类,然后,新建 MybatisPlusConfig
配置类,代码如下:
/**
* @Author: 犬小哈
* @From: 公众号:小哈学Java, 网站:www.quanxiaoha.com
* @Date: 2022-12-15 18:29
* @Version: v1.0.0
* @Description: TODO
**/
@Configuration
@MapperScan("com.quanxiaoha.mybatisplusdemo.mapper")
public class MybatisPlusConfig {
/**
* 插件相关
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
Spring MVC 整合
xml
文件配置如下:
<bean class="com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor" id="optimisticLockerInnerInterceptor"/>
<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
<property name="interceptors">
<list>
<ref bean="optimisticLockerInnerInterceptor"/>
</list>
</property>
</bean
数据库表与数据
新建一张带有 version
字段的秒杀商品表,Schema 建表脚本如下,并添加一条:
DROP TABLE IF EXISTS t_seckill_goods;
CREATE TABLE `t_seckill_goods` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`goods_name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',
`count` int(11) NULL DEFAULT NULL COMMENT '库存',
`version` int(11) NOT NULL DEFAULT 1 NULL COMMENT '乐观锁版本号, 默认从 1 开始',
PRIMARY KEY (`id`)
) COMMENT = '秒杀商品表';
INSERT INTO `test`.`t_seckill_goods` (`id`, `goods_name`, `count`, `version`) VALUES (1, '《三国演义》', 1000, 1);
实体类
新建秒杀商品实体类 SeckillGoods
:
/**
* @author: 犬小哈
* @from: 公众号:小哈学Java, 网站:www.quanxiaoha.com
* @date: 2022-12-13 14:13
* @version: v1.0.0
* @description: 秒杀商品实体类
**/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_seckill_goods")
public class SeckillGoods {
/**
* 主键 ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 商品名称
*/
private String goodsName;
/**
* 库存
*/
private Integer count;
/**
* 乐观锁版本号
*/
@Version
private Integer version;
}
TIP :
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
都是 Lombok 注解,偷懒用的,加上它即编译自动添加getXXX/setXXX
、类构造器
等相关方法,不了解的小伙伴可自行搜索一下如何使用。
注意:
@Version
注解仅支持数据类型为:int, Integer, long, Long, Date, Timestamp, LocalDateTime- 整数类型下会执行
newVersion = oldVersion + 1
newVersion
会回写到entity
实体类中- 仅支持
updateById(id)
与update(entity, wrapper)
方法- 在
update(entity, wrapper)
方法下,wrapper
不能复用!!!
新建 Mapper 接口
在 mapper
包下创建 SeckillGoodsMapper
接口,继承自 BaseMapper
:
/**
* @author: 犬小哈
* @from: 公众号:小哈学Java, 网站:www.quanxiaoha.com
* @date: 2022-12-13 14:13
* @version: v1.0.0
* @description: TODO
**/
public interface SeckillGoodsMapper extends BaseMapper<SeckillGoods> {
}
测试一波看看乐观锁是否生效
在单元测试类中注入 SeckillGoodsMapper
:
@Autowired
private SeckillGoodsMapper seckillGoodsMapper;
新建一个单元测试方法 testVersion()
,代码如下:
@Test
void testVersion() {
UpdateWrapper<SeckillGoods> wrapper = new UpdateWrapper<>();
// 库存减一
wrapper.setSql("count = count - 1");
// 设置当前版本号
SeckillGoods seckillGoods = SeckillGoods.builder().version(1).build();
// 乐观锁更新
seckillGoodsMapper.update(seckillGoods, wrapper);
System.out.println("回填版本号:" + seckillGoods.getVersion());
}
执行该单元测试,实际执行 SQL 如下:
我们对库存进行了减一操作,并且乐观锁插件自动将 version
进行了加 1 操作,同时进行了版本号判断。
结语
本小节中,我们学习了什么是乐观锁,以及其实现方式(版本号机制)与应用场景,最后通过 Mybatis Plus 整合了乐观锁插件,最后以单元测试实测是完全没有问题的。