• 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

  • 相关阅读:
    苹果是如何做到快速发货的?
    亚马逊危险了!面临创业公司和科技巨头的颠覆
    We are writing to let you know we have removed your selling privileges
    亚马逊获20亿美元信用额度:有助新业务投资
    亚马逊与美国邮政合作试营快递生鲜服务
    2013下半年(11月)信息系统项目管理师考试题型分析(综合知识、案例分析、论文)
    SQL Server Profiler参数说明
    SQL2005查询所有表的大小
    SQL中EXISTS的用法和效率
    在线生成APK
  • 原文地址:https://www.cnblogs.com/leolztang/p/5450316.html
Copyright © 2020-2023  润新知