尝试使用Hibernate使乐观锁定在Grails中工作

尝试使用Hibernate使乐观锁定在Grails中工作,hibernate,grails,jpa,Hibernate,Grails,Jpa,我发现在我的带有hibernate域类的Grails小演示应用程序中,乐观锁定不适用于我。域类很简单: @Entity @Table(name = "DELME", uniqueConstraints = @UniqueConstraint(columnNames = "ID")) public class DelmeV implements java.io.Serializable { private int id; private Long version; pri

我发现在我的带有hibernate域类的Grails小演示应用程序中,乐观锁定不适用于我。域类很简单:

@Entity
@Table(name = "DELME", uniqueConstraints = @UniqueConstraint(columnNames = "ID"))

public class DelmeV implements java.io.Serializable {
    private int id;
    private Long version;
    private String d2;
    private String dsc;

    // ... constructors goes here
    @Id
    @Column(name = "ID", nullable = false)
    public int getId() {...}
    public void setId(int id) {...}

    @Version
    @Column(name = "VERSION")
    public Long getVersion() { ... }
    public void setVersion(Long version) { ... }

    @Column(name = "D2")
    // appropriate getters and setters goes here ...

    @Column(name = "DSC") 
    // appropriate getters and setters goes here ...

}
控制器代码为脚手架:

@Transactional(readOnly = true)
class DelmeVController {

// methods which are out of interest skipped... 
@Transactional
def update(DelmeV delmeVInstance) {
    if (delmeVInstance == null) {
        notFound()
        return
    }

    if (delmeVInstance.hasErrors()) {
        respond delmeVInstance.errors, view:'edit'
        return
    }

    delmeVInstance.save flush:true

    request.withFormat {
        form multipartForm {
            flash.message = message(code: 'default.updated.message', args: [message(code: 'DelmeV.label', default: 'DelmeV'), delmeVInstance.id])
            redirect controller:"delmeV", id:delmeVInstance.id
        }
        '*'{ respond delmeVInstance, [status: OK] }
    }
}
}
测试程序如下:

  • 运行grails应用程序,将具有给定id(比如1)的Delme行放入编辑表单
  • 在数据库服务器上执行“sql update delme set d2='bad value',version=version+1,其中id=1;”
  • 在数据库服务器上执行“sql select*from delme where id=1;”,并验证d2是否在步骤2中设置了值,并且版本实际上是递增的
  • 在打开的编辑表单中更改DSC字段值并点击“更新”按钮
  • 结果:更新进展顺利,第2步中设置的D2值没有任何变化,没有异常发生。糟糕
  • 那么,问题1:上面出了什么问题?为什么乐观锁定对我来说不是开箱即用

    嗯,我放弃了默认的乐观锁定行为,并编写了我自己的(考虑到我们的实际业务表没有“版本”列,并且悲观锁定在OLTP env中不是一个选项): 我将“版本”设置为瞬态,在onLoad方法中填写,并在preUpdate中签入:

    @Entity
    @Table(...) 
    
    public class DelmeV  implements org.hibernate.classic.Lifecycle {
      // unisteresting stuff...
    
      @Transient
      private String version;
      // getters, setters ...
    
      public void onLoad (Session s, java.io.Serializable id) {
        if (this != null) {
          try {
            com.fasterxml.jackson.databind.ObjectMapper om = new com.fasterxml.jackson.databind.ObjectMapper();
            om.configure (com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            this.version = om.writeValueAsString (this);
          } catch (com.fasterxml.jackson.core.JsonProcessingException e) {
             System.out.println ("Jackson exception caught on #" + this.id);
             e.printStackTrace();
             this.version = "/x/";
         }
       }
    
       @javax.persistence.PreUpdate
       public void actualOnUpdate (Session s) throws StaleObjectStateException {
    
         SessionFactory sessionFactory = s.getSessionFactory();
         Session ns = null;
         Object newInstance = null;
         String dbVersion = null;
    
         try  {
           ns = sessionFactory.openSession();
           Transaction tx = ns.beginTransaction ();
           newInstance =   ns.get (this.getClass(), this.id);
           dbVersion = ((DelmeV) newInstance).getVersion();
           tx.commit();
         } catch (Throwable ex) {
           System.out.println ("Session creation exception caught on " + this.getClass().getName() + ", id=" + this.id);
           ex.printStackTrace();
         } finally {
           ns.close();
         }
       boolean eq = dbVersion.equals (this.version);
       if (!eq) {
         throw new StaleObjectStateException (this.getClass().getName(), this.id);
       }
     }
    }
    

    我注册了事件监听器,它扫描域类中的@preUpdate方法并调用它,只要Grails不自动调用它。到目前为止,这个解决方案还不错,但它恰好与其他框架(特别是SpringBatch)不兼容,因为JPA要求@preUpdate方法具有空参数列表,我不知道如何从preUpdate方法中找出当前会话或SessionFactory(或EntityManagerFactory,只要涉及JPA)(在Grails中,我将会话作为参数传递,从事件侦听器调用preUpdate方法)因此问题2:如何以一致的、独立于框架的方式从域方法中找出Session/SessionFactory/EntityManagerFactory?

    直接通过数据库更新数据是一个问题。Hibernate使用自己的缓存将以前的缓存版本与新更新的版本进行比较,并看到一切正常是的。如果您要直接对数据库进行更新,而不仅仅是通过Hibernate进行更新,请关闭Hibernate缓存。当您绕过系统时,系统无法工作。这是您的问题。谢谢,但如何实现呢?我试着将cache.use_second_level_cache=false,cache.region.factory_class='org.Hibernate.cache.internDataSource.groovy中的al.NoCachingRegionFactory',cache.provider='org.hibernate.cache.NoCacheProvider',域类中的@cache(usage=cacheconcurrentystrategy.NONE)。两者都没有帮助。我认为无状态会话在这里可能有帮助,但我不知道如何配置Grails来使用它。挖掘工作正在继续。。。