注意:本教程使用的数据库脚本、数据模型和环境信息请参考 “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)