【问题标题】:How to deal with "StaleObjectStateException" in Hibernate with pessimistic locking?如何使用悲观锁定处理 Hibernate 中的“StaleObjectStateException”?
【发布时间】:2014-08-20 04:25:36
【问题描述】:

编辑: 事实证明,在这种情况下,因为我使用的是“版本”注释,所以我使用的是乐观锁定,而不是悲观锁定。
如果我删除版本并因此禁用乐观锁定。悲观锁定接管并且性能显着下降。
所以我想我必须忍受乐观锁定和偶尔的异常。有没有更好的解决方案?

原文: 我目前通过 ajp 在 apache 2.2 负载均衡器中有多个 tomcat 实例。后端系统处于休眠状态。系统为多个用户和请求提供服务,对于请求,它会从用户的帐户中扣除一个信用。

我使用 hibernate 的悲观锁定来管理信用扣除。
我不时在日志中的用户帐户对象上收到以下信息:

org.hibernate.StaleObjectStateException:行已被另一个事务更新或删除(或未保存值映射不正确)

代码

private boolean decUserAccountQuota(UserAccount userAccount, int creditDec) {

        if(userAccount.getBalance() <1) return false;
        String userName = userAccount.getUsername();
        Manager manager = getManager(userName);


        try{
            manager.beginTransaction();
            manager.refresh(userAccount, LockMode.UPGRADE); //this uses pessimistic locking, this calls sessionFactory.getCurrentSession().refresh();   
            userAccount.setBalance(userAccount.getBalance()-creditDec);
            manager.commitTransaction(); //this causes the Exception
        }catch(Exception exp){
            exp.printStackTrace();
            manager.rollbackTransaction();
            return false;
        }finally{
            manager.closeSession();
        }
        return true;
    }

问题:

  1. 如何防止此异常发生。这里发生了什么 是多个线程尝试更新同一个实体,一个线程 成功,因此,当下一个线程去提交数据时,它 看到它已经被修改并最终抛出 陈旧对象状态异常。但如果我已经在使用悲观 锁了,怎么还会出现异常?
  2. 在性能和完整性方面是否有更好的方法 管理用户帐户信用系统?

【问题讨论】:

  • 从您的描述看来您实际上是在使用乐观锁定? docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html 描述了差异。
  • @Gustav Grusell:请看更新
  • userAccount 中是否有 &lt;version&gt; 映射到注释为 @Version 的字段上?如果是,hibernate 将在事务之间进行乐观锁定。
  • @Serge Ballesta:我应该删除版本和乐观锁定吗?
  • 无论如何,您的异常很奇怪,因为在单个事务中您执行选择、修改和更新,这应该始终有效。 Manager 类是什么?

标签: java mysql hibernate


【解决方案1】:

您的 Hibernate 实体要么使用 @Version 注释,要么在您的 XML Hibernate 映射中定义 &lt;version&gt;。这将启用 Hibernate 提供的乐观锁定。

如果您像描述的那样明确使用悲观锁定,则删除上述内容应该可以解决您的问题。

更多信息here

【讨论】:

  • 在别处移除乐观锁是否安全? decUserAccountQuota 是唯一进行写入的事务。但是还有其他事务会在此方法之前、期间和之后进行读取。
  • 根据您的编辑,您的原始问题似乎已得到解答。使用乐观锁定并不算太糟糕。事实上,与悲观锁相比,它通常是首选,因为它可以很好地扩展并且在 Read-Often Write-Sometimes 情况下工作得特别好(如 Hibernate 文档中所述)。我能想到的使用悲观锁定的一个原因是当您想在事务“早期”知道失败时。
【解决方案2】:

在您的代码中,尽管您指定它,但下面的行没有采用悲观锁,因为您的数据库不支持 SELECT.. FOR UPDATE 。

manager.refresh(userAccount, LockMode.UPGRADE); 

因此,hibernate 使用另一种锁定模式,称为 LockMode.READ 用于 userAccount,它是基于实体中的 @Version 属性的乐观锁定。

我搜索了是否有任何替代方法可以强制休眠对您的场景进行悲观锁定,但看起来不可能。

回到您关于如何最小化或避免 StaleObjectStateException 的问题,这是我的想法:-

在 userAccount 对象上同步。虽然这似乎会影响性能,但只有当同一个用户发出太多并发请求时才会发生这种情况。同时放弃一点性能以确保用户不需要被抛出异常并重试将是理想的场景。

如果您找到任何替代解决方案,请分享。

【讨论】:

  • 将 manager.refresh(userAccount,LockMode.UPGRDE) 更改为 manager.lock(userAccount, LockMode.UPGRADE); StaleObjectStateException 现在经常在 manager.lock 上发生。我想这是因为 userAccount 被早期的读取锁定了。有什么解决办法吗?
  • @user121196 很抱歉。只需检查您的数据库是否支持 SELECT .. FOR UPDATE 选项以及您用于数据库的方言是什么
猜你喜欢
  • 2014-08-30
  • 1970-01-01
  • 2018-10-22
  • 2013-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多