在现代应用中,乐观锁(Optimistic Locking)是解决并发问题的重要机制。它通过在数据更新时验证数据版本来确保数据的一致性,从而避免并发冲突。与悲观锁不同,乐观锁并不依赖数据库的锁机制,而是通过检查数据的版本或标志字段来判断数据是否被其他事务修改过。
MyBatis-Plus 提供了便捷的乐观锁支持,通过在实体类中添加版本号字段(通常是一个 int
或 long
类型的字段),并在更新操作时检查版本号,以确保数据的一致性和完整性,同时不影响系统的并发性能。
然而,MyBatis-Plus 本身并没有内置的重试机制来处理乐观锁失败的情况。
MyBatis-Plus 乐观锁的具体实现_java_脚本之家 (jb51.net)乐观锁的具体实现
乐观锁的工作原理
-
版本号字段:在实体类中添加一个版本号字段,通常命名为
version
。 -
更新操作:在更新操作时,增加一个条件,检查版本号是否匹配。如果不匹配,表示数据已经被其他事务修改,更新操作失败
重要:
实现重试机制
1. 自定义 Service 方法
在服务层中手动实现重试逻辑。
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>
2.config层
@Configuration
//@MapperScan("com.beiyou.dao")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
System.out.println("MybatisPlusInterceptor");
return mybatisPlusInterceptor;
}
}
3.dao层
@Mapper
public interface AccountDao extends BaseMapper<Account> {
}
4.entity层
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("account")
public class Account {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@TableField("balance")
private Integer balance;
@Version // 用于mybatis-plus
private Integer version; // 用于乐观锁
}
5.service层
@Component
public class AccountService {
@Autowired
private AccountDao accountDao;
private static final int MAX_RETRIES = 3;
private static final long RETRY_DELAY_MILLIS = 100;
public void updateAccountBalance() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int retryCount = 0;
while (retryCount <= MAX_RETRIES) {
Account account = accountDao.selectById(1);
if (account == null) {
System.out.println("Account not found");
return;
}
account.setBalance(2);
int update = accountDao.updateById(account);
if (update > 0) {
System.out.println("更新成功");
return;
}
retryCount++;
// 版本号不匹配,乐观锁失败
if (retryCount <= MAX_RETRIES) {
try {
System.out.println("Optimistic lock failed, retrying... (attempt " + retryCount + ")");
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MILLIS);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new MybatisPlusException("Thread interrupted during retry", ex);
}
continue;
}
}
System.out.println("Max retries reached, update failed accoutService");
}
});
thread.start();
}
}
6.test
@Autowired
private AccountService accountService;
//自定义 Service 方法
@Test
public void testAccountService() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 15; i++) {
executorService.submit(accountService::updateAccountBalance);
}
try { // 阻塞主线程,防止进程退出
System.in.read();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
2. 使用 AOP 实现重试
你可以使用 Spring AOP 来实现重试逻辑,通过切面编程在方法调用前后进行干预。
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.config层
同上
3.dao层
同上
4.entity层
同上
5.service层
@Service
public class AopService {
private static final Logger logger = LoggerFactory.getLogger(AopService.class);
@Autowired
private AccountDao accountDao;
public boolean updateAccountBalance(int newBalance) {
Account account = accountDao.selectById(1);
if (account == null) {
logger.warn("Account not found: {}", 1);
return false;
}
account.setBalance(newBalance);
int update = accountDao.updateById(account);
if (update > 0) {
logger.info("Account updated successfully: {}", account);
return true;
} else {
logger.warn("No rows updated for account: {}", account);
throw new OptimisticLockingFailureException("重试");
// return false;
}
}
}
6.test
@Autowired
private AopService aopService;
//aop方式
@Test
public void testAopService() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 15; i++) {
executorService.submit(() -> {
aopService.updateAccountBalance(13);
});
}
try {
System.in.read();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
3. 使用 spring-retry
库
你可以使用spring-retry库来实现重试机制。spring-retry提供了方便的注解和配置,可以轻松地实现重试逻辑。注意事项:spring-retry由于是基于AOP实现,所以不支持类里自调用方法。注意:需要在启动类上加@EnableRetry开启spring-retry库
1.引入依赖
<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>
2.config层
同上
3.dao层
同上
4.entity层
同上
5.service层
@Service
public class RetryService {
@Autowired
private AccountDao accountDao;
@Retryable(value = { OptimisticLockingFailureException.class},maxAttempts = 3, backoff = @Backoff(delay = 5000))
public boolean updateUser() {
Account account = accountDao.selectById(1);
account.setBalance(12);
int update = accountDao.updateById(account);
if (update > 0){
System.out.println("更新成功");
}else {
System.out.println("更新失败");
throw new OptimisticLockingFailureException("更新失败");
}
return update > 0;
}
}
6.test
@Autowired
private RetryService retryService;
//使用 spring-retry 库
@Test
void testRetryService() {
// 模拟多个线程并发更新同一个用户
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
executorService.submit(retryService::updateUser);
}
try {
System.in.read();
} catch (IOException e) {
throw new RuntimeException(e);
}
}