Spring Boot 乐观锁加锁失败

之前写了一些辅助工作相关的Spring Boot怎么使用AOP。这里继续正题,怎么减少Spring Boot 乐观锁加锁报错的情况(基本可以解决)。

1. 包依赖

  • spring-boot-starter-data-jpa, Spring Boot的JPA starter
  • h2, H2内存数据库
  • spring-boot-starter-test,Spring Boot的Junit测试starter
 1         <dependency>
 2             <groupId>org.springframework.boot</groupId>
 3             <artifactId>spring-boot-starter-data-jpa</artifactId>
 4             <version>1.2.6.RELEASE</version>
 5         </dependency>
 6 
 7         <dependency>
 8             <groupId>com.h2database</groupId>
 9             <artifactId>h2</artifactId>
10             <version>1.4.188</version>
11             <scope>runtime</scope>
12         </dependency>
13 
14         <dependency>
15             <groupId>org.springframework.boot</groupId>
16             <artifactId>spring-boot-starter-test</artifactId>
17             <version>1.2.6.RELEASE</version>
18             <scope>test</scope>
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 }
  • 切入点表达式使用注解,不再使用execution
 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. 完整代码

 http://files.cnblogs.com/files/leolztang/sb.aop-v2.tar.gz

原文地址:https://www.cnblogs.com/leolztang/p/5450316.html