讲到了mybatis-plus的基本使用,简单的使用@Version
和一个基础配置类即可实现乐观锁。
但是mybatis-plus本身并没有自带重试机制。
即当我们带上版本号去更新数据,但是由于另一个线程已经将版本号修改了,导致这次的修改失败,那么应该重新读取数据,再次更新,这就是重试机制。
实现重试机制
使用spring-retry 库
这里可以使用spring-retry库来实现重试机制。spring-retry提供了方便的注解和配置,可以轻松地实现重试逻辑。注意事项:spring-retry由于是基于AOP实现,所以不支持类里自调用方法。注意:需要在启动类 上加@EnableRetry
开启spring-retry库
引入依赖
xml
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
config层、dao层、entity层同上一篇文章
Service层
这里修改对Service层进行修改,新增了一个带@Retryable
的注解
这里注解的意思是,当遇到OptimisticLockingFailureException
就进行重试,重试的最大次数的3次。
加上第一次报错,则这个方法最多会被执行4次
java
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
/**
* 新增余额
* @param id 用户id
* @param addBalance 需要新增的余额
* @return 是否成功
*/
@Retryable(value = {OptimisticLockingFailureException.class},maxAttempts = 3,backoff = @Backoff(delay = 2000))
public boolean incrementBalance(Integer id,Integer addBalance){
User user = getById(id);
user.setBalance(user.getBalance()+addBalance);
boolean flag = updateById(user);
if (flag){
System.out.println("更新成功");
}else{
System.out.println("更新失败");
throw new OptimisticLockingFailureException("更新失败");
}
return true;
}
/**
* 初始化用户余额
* @param id 用户Id
* @param balance 余额
* @return 是否成功
*/
public boolean setUserBalance(Integer id,Integer balance){
User user = getById(id);
user.setBalance(balance);
boolean flag = updateById(user);
if (flag){
System.out.println("更新成功");
}else{
System.out.println("更新失败");
throw new OptimisticLockingFailureException("更新失败");
}
return true;
}
}
测试
在这里模拟了一个高并发场景:
- 给用户1初始化余额为0元
- 创建了15个线程,每个线程都对该用户余额+1
- 通过原子类来计算,成功次数,和失败次数。
- 期望:用户的最终余额=0+成功次数
java
@SpringBootTest
public class MpApplicationTests {
@Autowired
private UserService userService;
@Test
public void test3() throws IOException {
// 给用户1的余额初始化成 0元
userService.setUserBalance(1,0);
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 执行次数
AtomicInteger count = new AtomicInteger();
// 成功次数
AtomicInteger successCnt = new AtomicInteger();
// 失败次数
AtomicInteger failCnt = new AtomicInteger();
for (int i = 0; i < 15; i++) {
executorService.submit(() -> {
try {
// 给用户1,新增1元余额
userService.incrementBalance(1,1);
successCnt.incrementAndGet();
}catch (OptimisticLockingFailureException e){
// 重试多次后还是失败,抛出乐观锁异常
failCnt.incrementAndGet();
}
count.incrementAndGet();
});
}
while (count.get() <= 15){
ThreadUtil.sleep(1000);
System.out.println("执行完毕");
System.out.println("执行次数:"+count.get()+" 成功:"+successCnt.get()+" 失败:"+failCnt.get());
User user = userService.getById(1);
System.out.println("用户最终余额:"+user.getBalance());
if (count.get() == 15){
break;
}
}
}
}
测试结果如下,符合期望
执行完毕
执行次数:15 成功:13 失败:2
用户最终余额:13
如果觉得这个更新成功率太低了,可以自行修改重试次数。当我修改成5次时,这15个并发基本能达到100%了,这个视情况而定