作者:高振Andy | 来源:互联网 | 2023-10-12 10:31
之前写了一些辅助工作相关的Spring Boot怎么使用AOP。这里继续正题,怎么减少Spring Boot 乐观锁加锁报错的情况(基本可以解决)。
1. 包依赖
1 <dependency>
2 <groupId>org.springframework.bootgroupId>
3 <artifactId>spring-boot-starter-data-jpaartifactId>
4 <version>1.2.6.RELEASEversion>
5 dependency>
6
7 <dependency>
8 <groupId>com.h2databasegroupId>
9 <artifactId>h2artifactId>
10 <version>1.4.188version>
11 <scope>runtimescope>
12 dependency>
13
14 <dependency>
15 <groupId>org.springframework.bootgroupId>
16 <artifactId>spring-boot-starter-testartifactId>
17 <version>1.2.6.RELEASEversion>
18 <scope>testscope>
19 dependency>
2. 如何在启用乐观锁?
我用的是JPA, 所以很简单,在实体类加一个字段,并注解@Version。
1 @Entity
2 public class Account {
3
4 //primary key, auto generated
5 @Id
6 @GeneratedValue(strategy = GenerationType.AUTO)
7 private int id;
8
9 private String name;
10
11 // enable optimistic locking version control
12 @Version
13 private int version;
14
15 /*omitted getter/setter, but required*/
16 }
3. 通过AOP实现对RetryOnOptimisticLockingFailureException的恢复
为了减少对代码的侵入,对之前的AOP例子进行少许修改:
1 @Retention(RetentionPolicy.RUNTIME)
2 public @interface RetryOnOptimisticLockingFailure {
3
4 }
1 @Pointcut("@annotation(RetryOnOptimisticLockingFailure)")
2 public void retryOnOptFailure() {
3 // pointcut mark
4 }
5
6 @Around("retryOnOptFailure()")
7 public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
8 int numAttempts = 0;
9 do {
10 numAttempts++;
11 try {
12 return pjp.proceed();
13 } catch (OptimisticLockingFailureException ex) {
14 if (numAttempts > maxRetries) {
15 //log failure information, and throw exception
16 throw ex;
17 }else{
18 //log failure information for audit/reference
19 //will try recovery
20 }
21 }
22 } while (numAttempts <= this.maxRetries);
23
24 return null;
25 }
- 在需要对错误进行恢复的RESTFul接口加上恢复标签
至于为什么一定是要在RESTFul接口上加,而不是其他地方(例如service层),是因为Spring Boot的事务管理的上下文是从resource层开始建立的,在service层恢复是无效的,因为数据库的操作依然是在之前失败的事务里,之后再细说吧。
1 @RestController
2 @RequestMapping("/account")
3 public class AccountResource {
4
5 @Autowired
6 private AccountService accountService;
7
8 @RequestMapping(value = "/{id}/{name}", method = RequestMethod.PUT)
9 @ResponseBody
10 @RetryOnOptimisticLockingFailure
11 public void updateName(@PathVariable Integer id, @PathVariable String name) {
12 accountService.updateName(id, name);
13 }
14 }
4. 测试用例
@Test
public void testUpdate() {
new Thread(() -> this.client.put(base + "/1/llt-2", null)).start();
new Thread(() -> this.client.put(base + "/1/llt-3", null)).start();
try {
//waiting for execution result of service
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
5. 测试一下效果如何
- 没有在AccountResource的updateName方法加@RetryOnOptimisticLockingFailure:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.ObjectOptimisticLockingFailureException: Object of class [com.leolztang.sb.aop.model.Account] with identifier [1]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.leolztang.sb.aop.model.Account#1]] with root cause
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.leolztang.sb.aop.model.Account#1]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2541)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3285)
- 在AccountResource的updateName方法加@RetryOnOptimisticLockingFailure:
Original:name=[llz-1],version=[0],New:name=[llt-2],version=[1]
Original:name=[llt-2],version=[1],New:name=[llt-3],version=[2]
6. 完整代码
https://files.cnblogs.com/files/leolztang/sb.aop-v2.tar.gz