简介
乐观锁的目的是:当要更新一条记录时,希望这条数据没有被别人更新过;为了防止更新冲突的问题。
乐观锁如何实现?
实现方式一:版本号方式
- 取出记录时,获取当前version;
- 更新时,带上这个version;
- 版本正确更新成功,错误更新失败。
场景:
- sql语句
update table set version=newVersion,x=a,y=b where version=oldVersion and z=c;
示例:
update user set name='向南天',version=3 where id=1094592041087729777 and version=2;
引申:
- 数据库中的锁有两种(乐观锁、悲观锁);
- 悲观锁是通过数据库的锁机制实现的;
- 两种锁各有优缺点,不能认为一种好于另一种;
- 乐观锁适用于写比较少的场景下,就是多读场景;因为多读场景即使有冲突,也很少会发生,这样省去了系统的开销,加大了系统的整体吞吐量;
- 但如果是多写的情况下,一般会经常产生冲突,就会导致上层应用不断的进行重试,这样反倒是降低了系统的性能,所以一般多写的场景用悲观锁比较合适,而多读的场景是用乐观锁比较合适。
功能实现:
一、实现步骤
- 配置乐观锁插件OptimisticLockerInterceptor:
package com.mp.first.configuration;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
// @Bean
// public PaginationInterceptor paginationInterceptor(){
// return new PaginationInterceptor();
// }
// 3.1.1以下版本需要此配置,以上版本不需要此配置
// @Bean
// public ISqlInjector sqlInjector(){
// return new LogicSqlInjector();
// }
}
- 实体类中找到与数据库中列名version对应的变量名version,并给它加上注解@Version:
@Version
private int version;
- 测试
package com.mp.first;
import com.mp.first.dao.UserMapper;
import com.mp.first.entity.User;
import org.junit.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
public class OptTest {
@Autowired
private UserMapper userMapper;
// SELECT id,name,age,email,manager_id,create_time,update_time,version FROM user WHERE id=? AND deleted=0
// UPDATE user SET name=?, age=?, email=?, manager_id=?, create_time=?, update_time=?, version=? WHERE id=? AND version=? AND deleted=0
@Test
public void updateById() {
User user = userMapper.selectById(3l);
user.setName("张大嘴");
int rows = userMapper.updateById(user);
System.out.println("影响行数:" + rows);
}
}
二、注意事项
- 支持的数据类型有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下:newVersion = oldVersion + 1
- newVersion 会回写到 entity 中
- 仅支持 updateById(id) 与 update(entity,wrapper) 方法
- 在update(entity,wrapper) 方法下, wrapper 不能复用!!!
update(entity,wrapper) 方法复用后出现问题示例:
// UPDATE user SET name=?, age=?, update_time=?, version=? WHERE deleted=0 AND (age = ? AND version = ? AND version = ?)
@Test
public void updateByEntity(){
User user = userMapper.selectById(3L);
user.setName("李大嘴");
LambdaQueryWrapper<User> lambdaWrapper = Wrappers.<User>lambdaQuery().eq(User::getAge, 28);
userMapper.update(user, lambdaWrapper);
User u = new User();
u.setName("王大锤");
userMapper.update(u, lambdaWrapper);
}
可以看到生成的sql中,条件语句里多了两个version=?,这样会导致更新语句更新不成功。