10k

JPA/Hibernate optimistic lock not working

描述

在数据更新操作时,希望利用分布式锁-乐观锁机制预防并发修改,增加了对于entity的version的update以引起乐观锁版本的检查。实际的现象是修改可以正常保存,set 的version被忽略,直接在根据数据库中的数据做了递增。

原因

JPA/Hibernate中的实体有四种状态

org.hibernate.event.internal.EntityState{
   PERSISTENT,//托管状态
   TRANSIENT,//瞬时状态
   DETACHED,//游离状态
   DELETED;//删除状态 
}

当除以一个事务中时, 查询出的实体将一直处于PERSISTENT状态,对这个状态下的实体修改不会检测乐观锁,手动setVersion也会被忽略。

所以要让实体不在处于PERSISTENT状态。

当查询与保存不在同一事务时,实体由于查询事务的结束,将变成DETACHED状态,这对JPA来说,该实体是不可信的,所以对实体保存时,JPA将通过主键再次查询数据库,进行数据比对,这也包括乐观锁的检测。 同样new一个实体时,也不会处于PERSISTENT状态, 这也可以迫使JPA进行乐观锁检测。

解决方法

  1. 将本次操作的查询与保存放到俩个事务中;
  2. 实物保持不变,不用查询出来的实体进行set操作,new一个实体,将查出的实体与请求提交的实体合并,然后保存。(利用merge)

JPA/Hibernate 代码实现

org.hibernate.event.internal.DefaultMergeEventListener{
   public void onMerge(MergeEvent event, Map copiedAlready){
       //获取实体上下文,比对得到实体状态
       ...
       switch(entityState) {
           case DETACHED:
               this.entityIsDetached(event, copyCache);
               break;
           case TRANSIENT:
               this.entityIsTransient(event, copyCache);
               break;
           case PERSISTENT:
               this.entityIsPersistent(event, copyCache);
               break;
       }
   }
   protected void entityIsDetached(MergeEvent event, Map copyCache) {
       ...
       if (this.isVersionChanged(entity, source, persister, target)) {
           StatisticsImplementor statistics = source.getFactory().getStatistics();
           if (statistics.isStatisticsEnabled()) {
               statistics.optimisticFailure(entityName);
           }
           
           throw new StaleObjectStateException(entityName, id);
       }
       ...
   }
   protected void entityIsPersistent(MergeEvent event, Map copyCache) {
        LOG.trace("Ignoring persistent instance");
        Object entity = event.getEntity();
        EventSource source = event.getSession();
        EntityPersister persister = source.getEntityPersister(event.getEntityName(), entity);
        ((MergeContext)copyCache).put(entity, entity, true);
        this.cascadeOnMerge(source, persister, entity, copyCache);
        this.copyValues(persister, entity, entity, source, copyCache);
        event.setResult(entity);
    }
}
Thoughts? Leave a comment