Hibernate Grails乐观锁定不';检测不到并发更新?

Hibernate Grails乐观锁定不';检测不到并发更新?,hibernate,grails,gorm,optimistic-locking,optimistic-concurrency,Hibernate,Grails,Gorm,Optimistic Locking,Optimistic Concurrency,乐观锁定是否应该捕获并发更新问题 所谓并发更新,我的意思是两个不同的用户都试图更新具有相同版本号的对象。例如,如果有一个Person域类,id=1,版本=0的Person的name=Jack,并且两个不同的用户都试图在版本0上更新名称,我希望只有一个用户成功并将版本更改为1。我希望第二个用户得到Hibernate staleStateException或类似的东西。但事实并非如此 以下是我的用例: Grails 3.1.5 grails generate-app person grails cr

乐观锁定是否应该捕获并发更新问题

所谓并发更新,我的意思是两个不同的用户都试图更新具有相同版本号的对象。例如,如果有一个Person域类,id=1,版本=0的Person的name=Jack,并且两个不同的用户都试图在版本0上更新名称,我希望只有一个用户成功并将版本更改为1。我希望第二个用户得到Hibernate staleStateException或类似的东西。但事实并非如此

以下是我的用例:

Grails 3.1.5 grails generate-app person grails create-domain-class Person Edit Person.groovy to include String name grails generate-all person.Person gradle bootRun Grails 3.1.5 grails生成appperson grails创建域类Person 编辑Person.groovy以包含字符串名称 grails生成所有person.person 格雷德尔·布特伦
使用两种不同的浏览器访问应用程序,如Chrome和Firefox,以确保两种浏览器处于不同的会话中。在其中一个浏览器中创建一个人,然后在两个浏览器中打开同一个人(版本0)进行编辑。两个浏览器现在都应该编辑person的版本0。在一个浏览器中保存名称更改,这将起作用并将对象的持久化版本更改为1,但第二个浏览器仍在编辑版本0。现在将更改保存到第二个浏览器中,这也可以工作。尽管第二个浏览器刚刚将更改保存到现在已过时的对象(版本0),但不会引发任何StaleObject或StaleStateException。这是正确的行为吗?

是的,Grails乐观锁定将检测并发更新并引发异常。然而,根据您所描述的,您并没有进行并发更新。让我们仔细看看。

  • 起点是两个浏览器都检索到了相同版本的对象。但是,请注意,浏览器不包含对这些对象的引用。GSP代码从数据库中获取它们,并将它们呈现给浏览器显示。换句话说,同一对象正在查看而不是编辑
  • 当第一个浏览器调用控制器保存更改时,控制器从数据库中检索对象的新副本:例如
    DomainClass.get(params.id)
    。然后保存更改
  • 当第二个浏览器调用控制器保存更改时,控制器再次从数据库检索对象的新副本。这次是版本1,但这与此无关,因为版本与检索对象无关,仅在保存时。因此,第二次保存成功,因为版本与数据库中已有的版本匹配
  • 要创建您正在寻找的条件,您必须使用它,直到两次保存之间有足够长的重叠,以便发生如下顺序:

    def a = DomainClass.get(1)
    def b = DomainClass.get(1)
    
    /* change a */
    a.save()
    
    /* change b */
    b.save() // This would throw an exception because b.version does not match what's in the database.
    

    我认为确切的答案是,Grail没有在开箱即用的请求之间实现乐观锁定

    混淆源于这样一个事实,即脚手架生成了一些必要(但不充分)的代码,用于
    edit.gsp

    <g:hiddenField name="version" value="${myInstance?.version}" />
    

    因此,如果我理解正确的话,Grails/GORM/Hibernate乐观锁定功能实际上是为了检测服务器内存中发生的并发更新问题而设计的。因此,我希望通过将版本号发送到客户机并返回后续更新请求,将此功能扩展到客户机,这确实超出了它的功能范围?正确。不一定在内存中,而只是在服务器端。您传递版本号的想法正是如何在客户端实现功能。但是,等等,Grails生成的应用程序已经在将版本号传递给客户端,客户端正在将其发回。但是,当请求数据绑定到域对象时,Grails只按id而不是id和版本查找对象,而且,它会用db查找中的版本覆盖客户机中的版本。它不会覆盖更新的名称属性,而是使用客户端的名称值。如果它也只使用客户端的版本号,那么该功能将扩展到client.Cool。我不知道它会那样做。
    if (new Long(params.version) != myInstance.version) {
        myInstance.errors.rejectValue("", "", "This record has been updated by a concurrent user. Please reopen it before saving again");
        myInstance.version = new Long(params.version); //the version to be re-rendered must still be the original one, just in case the user try saving again without refreshing
        return render(view: "edit", model: [myInstance: myInstance]);
    }