Grails / Hibernate:控制器中的乐观锁定

时间:2014-06-05 04:00:34

标签: hibernate grails

使用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

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

2 个答案:

答案 0 :(得分:2)

在Hibernate中的AFAIK锁定实际上只有在会话上下文中有2个并发版本的同一个持久化对象时才会发挥作用。

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

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

答案 1 :(得分:2)

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**

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

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

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

[我想回答为什么第二个例子DID会抛出异常,但是我需要知道你是如何复制的(所有成员都包括ID拷贝?)并确实做到了使用OptimisticLockException或其他原因失败?无论出于什么原因,它都是一系列不切合实际或说明性的奇怪操作,所以尽管找出正在发生的事情可能是一次很好的智力练习,但我会把它留给别人! ]

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