热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringBoot乐观锁加锁失败使用AOP恢复错误

之前写了一些辅助工作相关的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.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 }
  • 切入点表达式使用注解,不再使用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. 完整代码

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


推荐阅读
author-avatar
高振Andy
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有