不灭的焱

革命尚未成功,同志仍须努力下载JDK17

作者:Albert.Wen  添加时间:2022-05-08 21:40:48  修改时间:2024-04-28 23:20:03  分类:Java框架/系统  编辑

一、引言

先来大概普及一下数据库锁的概念,以下解释来源网络部分

悲观锁时刻保持着一个悲观的态度,对谁都不信任,总想着别人会修改我的数据,所以为了防止别人修改,每次都会上锁,防止别人修改自己的数据。导致的后果就是每次想要拿到数据就必须要等待拿到锁,是一个很浪费时间的过程,如果访问量很大就是悲观锁一个致命的缺陷。

乐观锁乐观锁对这个世界都很乐观,对每个想要获取数据的操作,他都会认为大家不会对自己的数据进行修改,所以不会上锁,在访问量很大的时候相比于悲观锁,节省了很多时间,用户不需要等待获取锁。

如果说大量读取数据操作的时候,适合使用乐观锁。如果冲突较多建议使用悲观锁。

悲观锁实现方式是数据库采用加锁的机制,而乐观锁最常见的手动就是通过版本号,每次更新的时候需要判断版本号是否一致,如果一致才能正常更新,反之更新失败。

二、具体现实

步骤一:配置乐观锁插件,这个采用的SpringBoot的配置方式。

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // MybatisPlus拦截器
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 添加:分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 添加:乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 添加:防止全表更新与删除插件
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

        return interceptor;
    }
}

步骤二:接着数据库表中增加一个字段用于记录版本,我这里使用 int 类型,默认值为 0(默认值不能为 null,否则乐观锁无效)

步骤三:在实体类版本号的字段上加注解

@Data
public class UserInfo {
    private Integer id;
 
    private String userName;
 
    private String passWord;
 
    private Integer age;
  
   private String email;
 
    @Version
    private Integer version;
}

步骤四:测试调用,从SQL语句得出MP会把设置进去的版本号当作更新条件,并且版本号+1更新进去。

@Test
public void update(){
	// 更新对象
	User user = new User();
	user.setEmail("Test1111@email.com");
	user.setId(1L);

	// 需要把之前从数据库读出来的版本号设置进去
	user.setVersion(1);

	int update = userMapper.updateById(user);
	System.out.println(update);
}

日志信息:

DEBUG==>  Preparing: UPDATE sys_user SET email=?, update_time=?, version=? WHERE id=? AND version=? AND is_delete='0' 
DEBUG==> Parameters: Test1111@email.com(String), 2019-09-19T16:00:38.149(LocalDateTime), 2(Integer), 1(Long), 1(Integer)
DEBUG<==    Updates: 1

三、使用注意细节

细节一:支持的数据类型只有:int, Integer, long, Long, Date, Timestamp, LocalDateTime

细节二:整数类型下 newVersion = oldVersion + 1

细节三:newVersion 会回写到 entity

细节四:仅支持 updateById(id) 与 update(entity, wrapper) 方法

细节五:在 update(entity, wrapper) 方法下, wrapper 不能复用,这里小编给大家演示一下把,通过执行SQL语句看出,第一次更新是成功了,但是第二次更新失败了,在条件后面有两个版本号的条件。

@Test
public void updateByMyWrapper() {
	// 条件构造器
	QueryWrapper<User> wrapper = Wrappers.query();
	wrapper.eq("name", "admin");

	// 对象1
	User user = new User();
	user.setEmail("Test@email.com");
	user.setVersion(2);

	userMapper.update(user,wrapper);

	// 对象2
	User user2 = new User();
	user2.setEmail("Test2@email.com");
	user2.setVersion(3);

	// 注意,注意,这里的wrapper,和上面是是用的同一个
	userMapper.update(user2,wrapper);

}

日志信息:

updateFill......
DEBUG==>  Preparing: UPDATE sys_user SET email=?, update_time=?, version=? WHERE is_delete='0' AND name = ? AND version = ? 
DEBUG==> Parameters: Test@email.com(String), 2019-09-19T16:06:26.172(LocalDateTime), 3(Integer), admin(String), 2(Integer)
DEBUG<==    Updates: 1
updateFill......
DEBUG==>  Preparing: UPDATE sys_user SET email=?, update_time=?, version=? WHERE is_delete='0' AND name = ? AND version = ? AND version = ? 
DEBUG==> Parameters: Test2@email.com(String), 2019-09-19T16:06:26.238(LocalDateTime), 4(Integer), admin(String), 2(Integer), 3(Integer)
DEBUG<==    Updates: 0

 

 




其他Demo代码参考:

1. 我们先后获取同一条数据,获取后再依次修改更新:

UserInfo user1 = userInfoMapper.selectById(53);
UserInfo user2 = userInfoMapper.selectById(53);
 
user1.setAge(111);
if(userInfoMapper.updateById(user1) > 0 ){
    System.out.println("user1 更新成功");
} else {
    System.out.println("user1 更新失败,该记录已被其他人修改!");
}
 
user2.setAge(222);
if(userInfoMapper.updateById(user2) > 0 ){
    System.out.println("user2 更新成功");
} else {
    System.out.println("user2 更新失败,该记录已被其他人修改!");

2. 运行结果如下,可以看到第一次更新成功,第二次失败,说明乐观锁生效了:

 

 

摘自:

MyBatis-Plus 高级功能 —— 乐观锁插件

MyBatis-Plus(十九)使用乐观锁进行数据更新