之前写了一些辅助工作相关的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
         <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>1.2.6.RELEASE</version>
</dependency> <dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.188</version>
<scope>runtime</scope>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>1.2.6.RELEASE</version>
<scope>test</scope>
</dependency>

2. 如何在启用乐观锁?

我用的是JPA, 所以很简单,在实体类加一个字段,并注解@Version。

 @Entity
public class Account { //primary key, auto generated
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id; private String name; // enable optimistic locking version control
@Version
private int version; /*omitted getter/setter, but required*/
}

3. 通过AOP实现对RetryOnOptimisticLockingFailureException的恢复

为了减少对代码的侵入,对之前的AOP例子进行少许修改:

  • 自定义一个注解,用来标注需要恢复这个错误的接口
 @Retention(RetentionPolicy.RUNTIME)
public @interface RetryOnOptimisticLockingFailure { }
  • 切入点表达式使用注解,不再使用execution
     @Pointcut("@annotation(RetryOnOptimisticLockingFailure)")
public void retryOnOptFailure() {
// pointcut mark
}
  
@Around("retryOnOptFailure()")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
do {
numAttempts++;
try {
return pjp.proceed();
} catch (OptimisticLockingFailureException ex) {
if (numAttempts > maxRetries) {
//log failure information, and throw exception
throw ex;
}else{
//log failure information for audit/reference
//will try recovery
}
}
} while (numAttempts <= this.maxRetries); return null;
}
  • 在需要对错误进行恢复的RESTFul接口加上恢复标签

至于为什么一定是要在RESTFul接口上加,而不是其他地方(例如service层),是因为Spring Boot的事务管理的上下文是从resource层开始建立的,在service层恢复是无效的,因为数据库的操作依然是在之前失败的事务里,之后再细说吧。

 @RestController
@RequestMapping("/account")
public class AccountResource { @Autowired
private AccountService accountService; @RequestMapping(value = "/{id}/{name}", method = RequestMethod.PUT)
@ResponseBody
@RetryOnOptimisticLockingFailure
public void updateName(@PathVariable Integer id, @PathVariable String name) {
accountService.updateName(id, name);
}
}

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

Spring Boot 乐观锁加锁失败 - 使用AOP恢复错误的更多相关文章

  1. Spring Boot 乐观锁加锁失败 - 集成AOP

    Spring Boot with AOP 手头上的项目使用了Spring Boot, 在高并发的情况下,经常出现乐观锁加锁失败的情况(OptimisticLockingFailureException ...

  2. spring boot 中@Autowired注解无法自动注入的错误

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/huihuilovei/article/de ...

  3. spring boot 分布式锁组件 spring-boot-klock-starter

    基于redis的分布式锁spring-boot starter组件,使得项目拥有分布式锁能力变得异常简单,支持spring boot,和spirng mvc等spring相关项目 快速开始 sprin ...

  4. 2020最新的Spring Boot 分布式锁的具体实现(内附代码)

    前言 面试总是会被问到有没有用过分布式锁.redis 锁,大部分读者平时很少接触到,所以只能很无奈的回答 "没有".本文通过 Spring Boot 整合 redisson 来实现 ...

  5. 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版

    欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...

  6. spring boot 从开发到上线(三)—AOP 异常监控、上报

    在做这个项目的期间,看到一篇很有启发性的文章<程序员你为什么这么累>.对于初级程序员来说,拿到需求,第一反应是用什么技术来尽快的完成任务,这本身并没有问题.但长此以往,不仅被需求的更改搞得 ...

  7. spring boot开发 @autowired注入失败

    @autowired注入失败 会出现如下错误提示: 2018-05-28 08:39:41.857 INFO 8080 --- [ restartedMain] org.hibernate.Versi ...

  8. spring boot下接口调用失败重试方案

    背景: 在项目开发中,有时候会出现接口调用失败,本身调用又是异步的,如果是因为一些网络问题请求超时,总想可以重试几次把任务处理掉. 一些RPC框架,比如dubbo都是有重试机制的,但是并不是每一个项目 ...

  9. Spring Boot 中使用自定义注解,AOP 切面打印出入参日志及Dubbo链路追踪透传traceId

    一.使用背景 开发排查系统问题用得最多的手段就是查看系统日志,在分布式环境中一般使用 ELK 来统一收集日志,但是在并发大时使用日志定位问题还是比较麻烦,由于大量的其他用户/其他线程的日志也一起输出穿 ...

随机推荐

  1. AC日记——忠诚 洛谷 P1816

    题目描述 老管家是一个聪明能干的人.他为财主工作了整整10年,财主为了让自已账目更加清楚.要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意.但是由于一些人的挑拨,财主还是对管家产生了 ...

  2. Ajax中POST和GET的区别

    Get和Post都是向服务器发送的一种请求,只是发送机制不同. 1. GET请求会将参数跟在URL后进行传递,而POST请求则是作为HTTP消息的实体内容发送给WEB服务器.当然在Ajax请求中,这种 ...

  3. 嵌入式Linux驱动学习之路(二十七)字符设备驱动的另一种写法

    之前讲的字符设备驱动程序,只要有一个主设备号,那么次设备号无论是什么都会和同一个 struct file_operations 结构体对应. 而本节课讲的是如何在设备号相同的情况下,让不同的次设备号对 ...

  4. [LeetCode] 132 Pattern 132模式

    Given a sequence of n integers a1, a2, ..., an, a 132 pattern is a subsequence ai, aj, ak such that  ...

  5. [LeetCode] Maximum Product Subarray 求最大子数组乘积

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...

  6. [LeetCode] Linked List Cycle II 单链表中的环之二

    Given a linked list, return the node where the cycle begins. If there is no cycle, return null. Foll ...

  7. 【MySQL】函数IFNULL、设置默认时间

    MySql 函数 IFNUll用法说明 IFNULL(expr1,expr2) 如果 expr1 不是 NULL,IFNULL() 返回 expr1,否则它返回 expr2. IFNULL()返回一个 ...

  8. python基础补漏-06-内置模块

    1> sys 模块 sys.argv 命令行参数List,第一个元素是程序本身路径 sys.exit(n) 退出程序,正常退出时exit(0) sys.version 获取Python解释程序的 ...

  9. JavaIO学习笔记(五)

    JavaIO前期准备 什么是同步 指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪 什么是异步 异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO ...

  10. WCF、Web API、WCF REST、Web Service比较

    原文地址:http://www.dotnet-tricks.com/Tutorial/webapi/JI2X050413-Difference-between-WCF-and-Web-API-and- ...