0

这是重现异常的示例项目。

此示例说明了当许多并发事务正在修改帐户余额时的问题。Account 可以绑定多个 Card 实体。交易与订单相关,并且在时间上是最后的。每个线程执行如下:

  1. 客户端请求“/order/{hashId}”以获取第一个可用的 Order by given card hash id
  2. 客户端为给定订单开始新的交易 - '/tx/{orderId}/start'
  3. 客户完成 tx - '/tx/{txId}/stop/{amount}' 从账户余额中减去 tx 金额。

实体锁定

Account 和 Order 实体使用@javax.persistence.Version 进行版本控制。在最后一步 Account 实体被悲观写锁锁定:

@Override
public Account getLockedAccount(Integer id) {
    final Account account = findOne(id);
    em.lock(account, LockModeType.PESSIMISTIC_WRITE);
    return account;
}

测试

要测试并发访问,请使用JMeter脚本 src/main/resources/StressTest.jmx。注意:由于使用JSON Path extractor ,必须将额外的库安装到 JMeter 主页才能运行脚本。在普通笔记本电脑上使用这些特定设置,您可以获得大约 10% 的 TxEnd 请求错误:

{  
"timestamp":1425407408204,
"status":500,
"error":"Internal Server Error",
"exception":"org.springframework.orm.ObjectOptimisticLockingFailureException",
"message":"Object of class [sample.data.jpa.domain.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) : [sample.data.jpa.domain.Account#1]",
"path":"/tx/1443/stop/46.4"
}

问题

尽管使用了悲观写锁,我仍然得到乐观锁定异常。是否有任何其他方法可以确保帐户的完整性,而无需为所有更新或同步方法创建任务执行队列?

UPD:任务执行器的工作被放置在另一个分支中。SpringThreadPoolTaskExecutor结合事务性任务解决了这个问题。

4

1 回答 1

0

在查找和锁定之间,Account 对象可能已经被修改。你需要在一个声明中做到这一点

EM.find(Account.class, id, LockModeType.PESSIMISTIC_WRITE)

于 2015-03-03T23:06:37.327 回答