【问题标题】:Grails/Hibernate: Optimistic Locking in ControllerGrails/Hibernate:控制器中的乐观锁定
【发布时间】:2014-06-05 04:00:33
【问题描述】:

使用 Grails 2.3.9(Groovy (2.2.2)、Mysql 5.5.37 (MySQLUTF8InnoDBDialect)、JDK 1.7

我正在尝试在控制器端实现和测试 Grails/Hibernate 的乐观锁定功能。

我的直觉如下

def instance = Group.findByXXX(...)
instance.properties = params
// ...
instance.version = 5 // something smaller than the current
instance.save flush:true, failOnError: true

会因为版本错误而抛出异常。但是,无论如何都会保存实例。

这个问题大概和this one一样,只是没看懂。这是我在阅读此问题/答案后尝试的:

def copyInstance = copy(instance)   // I instantiate a new item, copy all members
                                    // from instance to the new one
copyInstance = copyInstance.merge()
copyInstance.version = 5            // something smaller than the current
copyInstance.save flush:true, failOnError: true

这有预期的结果(保存失败)。但我还是不太明白:有人可以解释上层对象“instance”和下层“copyInstance”之间的区别吗?而且,这是实现乐观锁定的方式吗(在我看来,可能不需要额外的复制)?

【问题讨论】:

标签: hibernate grails


【解决方案1】:

TL;DR:您的第一个示例无法崩溃,因为您以错误的方式干扰了版本控制功能...

虽然公认的答案大体上是正确的,但它确实有一些顺序不正确,我认为值得添加一些澄清。我认为理解乐观锁定的幕后发生的事情非常重要,因此我在这里是迂腐的。

从接受的答案:“您的最佳示例有效,因为您只有一个实例,因此可以透明地监视其状态而无需锁定”

首先,“乐观锁定”是用词不当;过程中的任何地方都没有锁定(它只是版本控制 - 悲观锁定确实使用了锁 - 这很令人困惑),因此“可以透明地监控”的手波使得底层过程听起来比实际复杂得多。

实际发生的情况:当发布更新时,Hibernate 会将对象的版本号从它在此事务中首次加载时开始包含到生成的更新语句中。如果更新零行,则抛出 OptimisticLockException。

这就回答了为什么您的第一个示例运行时没有错误...

def instance = Group.findByID(49)

结果:

select * from tbl_group where id=49

假设版本是 28。Hibernates 在加载对象时保留对象的所有属性的副本 - 它这样做是为了在事务提交时进行脏检查 - 如果没有任何值发生变化,它不需要进行更新。

所以这行代码对hibernate将在更新中使用的版本号没有影响:

instance.version = 5 // something smaller than the current

当你最终调用 save 时:

instance.save flush:true, failOnError: true

这将导致

update tbl_group set version=**29** where id=49 **and version=28**

(顺便说一句,Hibernate 中的逻辑是,如果此更新未能更新任何行,则另一个事务必须修改该行并增加其版本号。)

这就是为什么您没有看到异常,使用的版本是该事务最初读取的版本号,而不是您人为写入对象的版本号。

对该功能的更好测试是在执行 save() 之前暂停执行(例如,使用调试器)。现在进入数据库并更改该行的版本列(更改为更高的数字)。现在 save() 将因 OptimisticLockException 而失败,因为 Hibernate 认为另一个事务首先修改了该行。

[我想回答为什么第二个示例 DID 抛出异常,但我需要知道您是如何进行复制的(包括 ID 在内的所有成员都复制了吗?)并且它肯定会因 OptimisticLockException 或别的东西?不管是什么原因,这是一组奇怪的操作,既不现实也不具有说明性,因此尽管弄清楚发生了什么可能是一个很好的智力练习,但我会把它留给其他人!]

编辑添加:我已经运行了一个测试,第二个示例确实抛出了一个 OptimisticLockException - 但它在你甚至更改版本号之前的 merge() 处。所以你因为错误的原因得到了正确的结果。我会进一步调查,但这是一个老问题,我怀疑有人在乎 - 但我粗略的感觉是,通过复制对象并尝试让两个对象代表同一个持久实体,实体管理器会感到困惑并将其提升为乐观锁异常.我确信有一个更完整的解释(这与作为合并的第 1 步发布的选择有关),我可能会在另一天回到那个。基本上,如果你给它做一些奇怪的事情,Hibernate 会做一些奇怪的事情!

【讨论】:

  • 感谢您的解释。是的,太久远了,我最近没碰过grails。
  • @tokosh 你非常非常幸运!值得庆幸的是,我将其作为淘汰我们最后一个 Grails 应用程序的项目的一部分恢复了。删除的日子不能很快到来;-)
【解决方案2】:

只有当会话上下文中有同一个持久对象的 2 个并发版本时,Hibernate 中的 AFAIK 锁定才真正发挥作用。

您的顶级示例之所以有效,是因为您只有一个实例,因此可以透明地监控它的状态,而无需锁定。 Hibernate 知道您的对象不可能有两种不同的持久状态,因为它只有一个实例,因此它不会费心检查版本。它知道这个对象比数据库中的对象更新,所以它只写你的更改。

您的第二个实例失败,因为您有同一个对象的 2 个实例。当您尝试使用较低版本保存第二个实例时,它会失败,因为该对象已被数据库锁定。 Hibernate 将使用版本号来确定哪些对象较新,并将这些更改保存到数据库中。

【讨论】:

  • 那么,如果我想对 REST API 进行这种乐观锁定,我需要像第二次尝试那样做吗?
  • 如果您有多个用户同时访问您的对象,则会发生乐观锁定。所以如果两个用户都请求同一个对象,那么 Hibernate 会透明地处理锁定机制
  • 我想,我想做的与hibernate的乐观锁定没有任何关系。这只是我必须自己处理的版本冲突(使用方法 2 或在保存/删除之前添加检查)。
  • 请确保您在链接的那个问题上考虑到 JB Nizet 的 cmets,如果您认为正确,请不要忘记接受我的回答。
猜你喜欢
  • 1970-01-01
  • 2018-03-29
  • 2016-07-18
  • 1970-01-01
  • 2018-10-22
  • 1970-01-01
  • 2015-05-14
  • 1970-01-01
  • 2011-05-21
相关资源
最近更新 更多