一、乐观锁简述
乐观锁指,对于同一个数据,某一个事务正在进行时,不会阻止其它事务的操作,但在更新操作时会判断此前是否有其它事务进行了更新
换句话讲,如果当前事务读取的数据是过时的,那么此事务不被允许进行更新操作
实现乐观锁通常用一个 version 字段来表示当前数据的版本
如果更新时发现当前 version 不是最新的,则不进行更新
二、乐观锁的实现
1. 创建表
参考下面创建表的脚本:
DROP TABLE IF EXISTS `tbl_user`;
CREATE TABLE `tbl_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(30) COMMENT '姓名',
`age` int COMMENT '年龄',
`email` varchar(50) COMMENT '邮箱',
`version` int DEFAULT 1 COMMENT '乐观锁版本号',
PRIMARY KEY (`id`)
)
2. 实体类
在 version 字段上注解 @Version 即可
@Data
@TableName("tbl_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@Version
private Integer version;
}
3. 配置乐观锁拦截器
乐观锁拦截器是 OptimisticLockerInnerInterceptor,将它添加到 MybatisPlusInterceptor 中
和配置分页查询拦截器是同样的操作
@Configuration
public class MybatisPlusConfig {
@Bean
MybatisPlusInterceptor mpInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
4. 效果查看
我们模拟两个事务的运作,对同一行数据读取两次,分别进行更新操作,查看效果
@SpringBootTest
public class MybatisPlusTest {
@Autowired
UserMapper userMapper;
@Test
void testOptimisticLock() {
User user1 = userMapper.selectById(1);
User user2 = userMapper.selectById(1);
user1.setName("Alice"); // 第一个更新操作
int ret1 = userMapper.updateById(user1);
user2.setAge(18); // 第二个更新操作
int ret2 = userMapper.updateById(user2);
System.out.println(ret1 + " " + ret2);
}
}
第一个更新的日志:
==> Preparing: UPDATE tbl_user SET name=?, age=?, email=?, version=? WHERE id=? AND version=?
==> Parameters: Harada Miu(String), 18(Integer), tsewh@mail.com(String), 2(Integer), 1(Long), 1(Integer)
<== Updates: 1
第二个更新的日志:
==> Preparing: UPDATE tbl_user SET name=?, age=?, email=?, version=? WHERE id=? AND version=?
==> Parameters: Harada Miu(String), 18(Integer), tsewh@mail.com(String), 2(Integer), 1(Long), 1(Integer)
<== Updates: 0
可见更新时的 WHERE 语句中自动加入了对 version 的判断,如果数据库中的 version 值与当前 version 不相等,则不会更新
所以第一次更新成功了,并且自动更新了 version + 1,导致第二次更新失败
[注意]
- 进行更新时会先将实例中的 version + 1 再做 UPDATE,即使更新失败依然有 version + 1 的操作,所以更新失败后依然继续进行更新,会不断抬高版本号,最终更新成功