Spring Boot 樂觀鎖加鎖失敗 - 使用AOP恢復錯誤

leolztang發表於2016-05-01

之前寫了一些輔助工作相關的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

相關文章