注意:本教程使用的数据库脚本、数据模型和环境信息请参考 “MyBatis Plus环境准备” 章节,点击下载示例源码。
乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测。如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。
在 MyBatis Plus 中,提供 OptimisticLockerInnerInterceptor 插件和 @Version 注解来实现乐观锁。乐观锁的实现方式大致流程如下:
取出记录时,获取当前 version
更新时,带上这个 version
执行更新时, set version = newVersion where version = oldVersion
如果 version 不对,就更新失败
(1)在 user 表中添加 version 字段。SQL 如下:
ALTER TABLE `user` ADD COLUMN `version` int UNSIGNED NULL COMMENT '版本信息';
(2)定义 user 表的 JavaBean,代码如下:
package com.hxstrive.mybatis_plus.model;
import com.baomidou.mybatisplus.annotation.*;
@TableName(value = "user")
public class AnnotationUser5Bean {
@TableId(value = "user_id", type = IdType.AUTO)
private String userId;
@TableField("name")
private String name;
@TableField("sex")
private String sex;
@TableField("age")
private Integer age;
@Version
private int version;
// 忽略 getter 和 setter 方法
}(3)添加 MyBatis Plus 的乐观锁插件,该插件会自动帮我们将 version 加一操作。如下:
package com.hxstrive.mybatis_plus;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}(4)测试乐观锁代码,我们创建两个线程 A 和 B 分别去修改用户ID为 1 的用户年龄,然后观察年龄和version字段的值。代码如下:
package com.hxstrive.mybatis_plus.simple_mapper.annotation;
import com.hxstrive.mybatis_plus.mapper.AnnotationUser5Mapper;
import com.hxstrive.mybatis_plus.model.AnnotationUser5Bean;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
class AnnotationDemo5 {
@Autowired
private AnnotationUser5Mapper userMapper;
@Test
void contextLoads() throws Exception {
AnnotationUser5Bean userBean = userMapper.selectById(userId);
if (null == userBean) {
return;
}
AnnotationUser5Bean newBean = new AnnotationUser5Bean();
newBean.setName(userBean.getName());
newBean.setSex(userBean.getSex());
newBean.setAge(userBean.getAge() + 1);
newBean.setUserId(userBean.getUserId());
newBean.setVersion(userBean.getVersion());
int result = userMapper.updateById(newBean);
System.out.println("result=" + result + " ==> " + userBean);
}
}运行上面代码,每次更新数据时,MyBatis Plus 将使用下面的 SQL 语句:
Preparing: UPDATE user SET name=?, sex=?, age=?, version=? WHERE user_id=? AND version=? Parameters: 测试(String), 男(String), 2(Integer), 3(Integer), 1(Integer), 2(Integer)