1.乐观锁与悲观锁简介
- 悲观锁:读锁可以共享,如果一个线程占用了写锁,则其他线程就不能获得写锁或读锁
- 乐观锁:读锁可以共享,如果一个线程占用了写锁,其他线程仍可获得读锁或写锁,当该线程的事务进行提交的时候,会去判断是否有其他线程对该数据进行了修改(根据版本号或者其他方式),如果修改了,则不进行提交
2.JPA的实现方式简介(利用@Version注解)
2.1 创建实体类(数据库须增加version字段用来存储版本号)
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue( generator = "hibernate-uuid")
@GenericGenerator( name = "hibernate-uuid",strategy = "uuid")
@Column( name = "id")
private String id;
@Column(name = "name")
private String name;
@Column(name = "age")
private Integer age;
@Version
@Column(name = "version")
private Integer version = 0;
get()/set()...
}
2.2编写业务代码
@Controller
@RequestMapping(value = "/a")
public class UserController {
private static final Logger LOGGER = Logger.getLogger(UserController.class);
@Autowired
private UserService userService;
@RequestMapping("/a")
@ResponseBody
public SimpleResult aaa() throws InterruptedException {
SimpleResult result = new SimpleResult();
User user = userService.findById("123");
user.setName("张三");
try {
userService.saveOrUpdate(user);
}catch (ObjectOptimisticLockingFailureException e){
LOGGER.info(e);
result.setResultCode("01");
result.setResultMsg("已有其他用户对该条记录进行了修改");
return result;
}
result.setResultCode("00");
result.setResultMsg("修改成功");
return result;
}
}
2.3说明
如下图所示,有两个线程,线程A和线程B同时读到了下面这条数据,当线程A修改成功后,version自增1,而线程B再进行更新,此时version会自增1,然后去判断是否大于当前的version,结果1不大于1,抛出异常。
.
可以捕获该异常,并提示给用户,如下图所示。
3.乐观锁异常处理办法
上文采用捕获异常,并提示给用户的方式解决了抛出的异常,但有些场景不能用该方式来处理。比如支付场景,用户已经支付成功了,后台异步修改商户的余额,此时就不能因为乐观锁抛出的异常来告知用户支付失败。此时应能实现并发时也能修改余额。
3.1采用事务回滚重试来做乐观锁的重试机制,将这一部分的业务逻辑写在同一个事务中,发生异常,利用事务回滚,重新运行整个事务,直至成功为止,可参照 https://gist.github.com/crazycode/4970741或者可以尝试@Retry注解进行事务重试
3.2利用AOP来做乐观锁的重试机制 可参照https://nanjiwubing123.iteye.com/blog/2261394