描述
在数据更新操作时,希望利用分布式锁-乐观锁机制预防并发修改,增加了对于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进行乐观锁检测。
解决方法
- 将本次操作的查询与保存放到俩个事务中;
- 实物保持不变,不用查询出来的实体进行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); } }