JPA乐观锁失效解决

问题描述

在数据更新操作中,对实体的version字段进行修改,明显低于数据库version,但是却可以正常保存,并忽略了用户设置的version,直接递增了最新的version。

解决方法

  1. 将本次操作的查询与保存分事务处理,不要放在一个事务中。
  2. 事务保持不变,不用查询出来的实体进行set操作,new一个实体,将查出的实体与请求提交的实体合并,然后保存。

详细经过

如更新一个实体的名称字段

@Transaction
void update(long id, String name, int version){
	T entity = repository.findById(id);//{id: 1, name:"11", version: 10}
	entity.setName(name);
	entity.setVersion(version);//{id: 1, name:"22", version: 8}
	repository.save(entity);
	//保存成功,保存后的实体:{id: 1, name:"22", version: 11}
}
update(1,"22",8);

低版本的实体成功保存, jpa跳过了乐观锁检测。

产生原因

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

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

当处于一个事务中时, 查询出的实体将一直处于PERSISTENT状态,对PERSISTENT状态下的实体进行修改将不会检测乐观锁,手动setVersion也会被忽略。
所以要让实体不再处于PERSISTENT状态,就可以得到解决。
当查询与保存不在同一事务时,实体由于查询事务的结束,将变成DETACHED状态,这对JPA来说,该实体是不可信的,所以对实体保存时,JPA将通过主键再次查询数据库,进行数据比对,这也包括乐观锁的检测。
同样new一个实体时,也不会处于PERSISTENT状态, 这也可以迫使JPA进行乐观锁检测。

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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值