Hibernate - 由于StaleObjectStateException仍尝试保存过时的实例,因此重试失败的更新

时间:2012-08-16 15:16:34

标签: hibernate optimistic-locking

我在使用hibernate和版本控制方面遇到了麻烦。我正在使用Hibernate 3.6.7-Final。这是来自我的DAO类的代码片段(它由Spring Bean调用,使用@Transactional注释,因此它本身不是事务性的,但是为了强制StaleObjectStateException,使用了flush):

@Override
public void save(User user) {
    boolean saved = false;
    while (!saved) {
        try {
            sessionFactory.getCurrentSession().saveOrUpdate(user);
            sessionFactory.getCurrentSession().flush();
            saved = true;
        } catch (StaleObjectStateException exc) {
            sessionFactory.getCurrentSession().refresh(user);
        }
    }
}

版本是一个长值,如下所示:

<version name="version" access="field" column="version" type="long" unsaved-value="null" />

问题是,用户永远不会保存,代码只是永远循环。我稍微调试了一下,事实证明hibernate有一个ActionQueue的概念,在内部,它在不同的集合中有插入,更新等。对于上面代码的每个循环,更新集合增加1.它失败,因为它总是尝试在该集合中的索引0处执行更新,这是“陈旧”的,并且在那时失败,并且不尝试使用刷新版本执行更新。有没有办法让这项工作成功?

也许有一些关于我想做什么的背景知识,所以也许聪明的人能够提出更好的解决方案:假设没有版本控制,并且用户有一个包含无效登录计数的列。每次失败时,此计数增加1,并通过成功登录重置。 (我们使用它来引入超时,以确定用户何时可以在x次尝试失败后登录,以防止对账户施加暴力,但这与此无关。)现在,假设攻击者使用攻击,其中服务器上的多个线程可以访问相同的用户帐户,当2个线程读取具有失败计数的用户时,例如2,然后线程1将其更新为3,保存,然后线程2将其更新为3,并保存 - 我们刚刚失去了一次不成功的尝试,这是一个错误。

所以,为了解决这个问题,我想引入版本控制列,在这种情况下,不会出于其他目的导致上面的线程2抛出异常,在这种情况下,将重试该操作(可能但是,在一个循环中极不可能) - 基本上,这将引入保存用户的序列化,但基于乐观锁定。如前所述,这不起作用。

有人可以帮我解决这个问题吗?这是一个好主意吗?也许有更好的方法?为什么Hibernate代码段不起作用?

1 个答案:

答案 0 :(得分:0)

您可能需要一个新会话来执行更新。 作为一般规则,在引发异常后,您不应该使用会话。

您可以阅读this主题以获取更多信息。 TL; DR: hibernate抛出的异常是不可恢复的。您必须回滚,关闭会话并重新开始。