我在使用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代码段不起作用?
答案 0 :(得分:0)
您可能需要一个新会话来执行更新。 作为一般规则,在引发异常后,您不应该使用会话。
您可以阅读this主题以获取更多信息。 TL; DR: hibernate抛出的异常是不可恢复的。您必须回滚,关闭会话并重新开始。