乐观锁(Optimistic Locking)是一种在读取数据时不会加锁,而是在更新数据时检查数据是否被其他事务修改的机制。如果数据被修改,则更新失败;否则,更新成功。
- 低并发场景:当系统中的并发量较低时,乐观锁可以有效减少锁的竞争,提高系统的性能。
- 读多写少的场景:如果系统中读操作远多于写操作,使用乐观锁可以避免频繁的锁竞争,提高读操作的效率。
- 数据更新频率低的场景:对于那些更新频率较低的数据,乐观锁可以减少锁的开销,提高系统的响应速度。
- 业务逻辑允许重试的场景:如果业务逻辑允许在更新失败后进行重试,乐观锁是一个不错的选择。因为即使更新失败,也可以通过重试来完成操作。
- 分布式系统中的数据一致性:在分布式系统中,乐观锁可以用来保证数据的一致性,尤其是在跨服务调用时,可以减少锁的竞争和死锁的风险。
MyBatis-Plus 提供了 OptimisticLockerInnerInterceptor 插件,使得在应用中实现乐观锁变得简单。
乐观锁的实现通常包括以下步骤:
- 读取记录时,获取当前的版本号(version)。
- 在更新记录时,将这个版本号一同传递。
- 执行更新操作时,设置 version = newVersion 的条件为 version = oldVersion。
- 如果版本号不匹配,则更新失败。
配置乐观锁插件
1. 配置插件
/**
* 配置Mybatis-Plus乐观锁插件
* 乐观锁是一种在读取数据时不会加锁,只有在更新数据时才会检查数据是否被修改的机制
* 如果数据未被修改,则直接更新;如果数据已被修改,则会抛出异常或进行其他处理
* 这种方式可以提高系统的并发性能,但需要开发者自行处理数据冲突的情况
*/
@Configuration
public class MybatisPlusOptLockerConfig {
/**
* 创建并配置Mybatis-Plus拦截器
* 拦截器用于在SQL执行过程中添加额外的功能或修改SQL
* 在这里,我们添加了乐观锁内拦截器,以实现乐观锁的功能
*
* @return 配置好的Mybatis-Plus拦截器实例
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 创建Mybatis-Plus拦截器实例
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加乐观锁内拦截器到拦截器链中
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 返回配置好的拦截器实例
return interceptor;
}
}
2. 在实体类字段上添加 @Version 注解
@Data
@TableName("sys_user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
@Version
private Integer version;
}
3.使用
/**
* 批量更新带乐观锁
* <p>
* update(et,ew) et:必须带上version的值才会触发乐观锁
*/
@Test
public void testUpdateByEntitySucc() {
// 准备查询条件,查找所有版本号为1的用户记录
QueryWrapper<User> ew = new QueryWrapper<>();
ew.eq("version", 1);
// 获取符合条件的记录数
long count = userMapper.selectCount(ew);
// 创建一个新的User对象,并设置更新后的年龄和版本号
User entity = new User();
entity.setAge(28);
entity.setVersion(1);
// 验证更新的记录数与原始符合条件的记录数相同
Assertions.assertEquals(count, userMapper.update(entity, null));
// 重新初始化查询条件,查找版本号为1的记录
ew = new QueryWrapper<>();
ew.eq("version", 1);
// 版本号为1的记录应已全部更新,因此此时应找不到记录
Assertions.assertEquals(0, userMapper.selectCount(ew).intValue());
// 初始化查询条件,查找版本号为2的记录
ew = new QueryWrapper<>();
ew.eq("version", 2);
// 所有版本号为1的记录应已更新为版本号2,验证更新结果
Assertions.assertEquals(count, userMapper.selectCount(ew).intValue());
}
@Version注解可以查看MyBatis-Plus 学习笔记-注解配置