一 乐观锁概念
乐观锁是什么?他是干什么用的,首先乐观锁是用来,控制数据准确性,比如A连接和B连接同时修改一条数据,A连接将数据修改了,B连接也将数据修改了,这时B先提交了事务,这时如果A连接也提交了事务,那么A就会覆盖B连接的修改,因为A查询出的对象不包含B的修改,所以这时是有问题的。而乐观锁就是通过version进行判断,谁先提交谁后提交,如果后提交的和数据库中的version不一致,就抛出异常,从而保证数据的准确定
二 乐观锁实现
乐观锁是数据库实现的还是java代码中实现的呢?他是怎么实现的呢,我想这个问题会困扰很多人,网上有很多篇文章只是说了一个大概,比如乐观锁和悲观锁的区别,只是说了乐观锁需要我们手动实现,而并没有提怎么实现,现在就来看看他到底是怎么实现的
1 基于SpringDataJpa的乐观锁实现
首先我们看一段代码
SpecialPrintingRule rule =new SpecialPrintingRule();
rule.audit(identify);
specialPrintingRuleRepository.saveAndFlush(rule);
这段代码就是SpringDataJpa的保存实体的代码,我们就通过他来看看乐观锁是怎么一回事
我们来看看乐观锁报错的代码
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.sunisco.eir.domain.EdiBooking#8a8ac82978d388880178f1fdcd5e247f]
at org.hibernate.event.def.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:485)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:255)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:84)
at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:867)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:851)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:855)
at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:853)
at sun.reflect.GeneratedMethodAccessor816.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
at com.sun.proxy.$Proxy62.merge(Unknown Source)
at com.inspireso.framework.jpa.repository.support.InternalRepository.saveInternal(InternalRepository.java:436)
at com.inspireso.framework.jpa.repository.support.InternalRepository.save(InternalRepository.java:352)
at com.inspireso.framework.jpa.repository.support.GenericRepositorySupport.save(GenericRepositorySupport.java:344)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:413)
at sun.reflect.GeneratedMethodAccessor815.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:425)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:410)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:364)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
... 147 common frames omitted
如果经历过乐观锁报错的同学对这段报错信息一定不陌生,没经历过的同学也没关系,因为只是通过这段报错信息来找到乐观锁的实现,我们看到报错信息中有一行代码DefaultMergeEventListener.onMerge(),我们去查看下他做了什么
//杨乐乐源码分析 https://juejin.cn/user/4486440898279832
public void onMerge(MergeEvent event, Map copiedAlready) throws HibernateException {
......
//1 获取PersistenceContext对象,再从PersistenceContext中查找是否有和我们插入实体相同的实体
EntityEntry entry = source.getPersistenceContext().getEntry(entity);
if (entry == null) {
EntityPersister persister = source.getEntityPersister(event.getEntityName(), entity);
Serializable id = persister.getIdentifier(entity, source);
if (id != null) {
EntityKey key = new EntityKey(id, persister, source.getEntityMode());
Object managedEntity = source.getPersistenceContext().getEntity(key);
entry = source.getPersistenceContext().getEntry(managedEntity);
if (entry != null) {
entityState = 2;
}
}
}
if (entityState == -1) {
//2 获取entityState状态为后续判断做准备
entityState = this.getEntityState(entity, event.getEntityName(), entry, source);
}
//3 通过上面返回的entityState来进行判断走不同方法
switch(entityState) {
case 0:
this.entityIsPersistent(event, copyCache);
break;
case 1:
this.entityIsTransient(event, copyCache);
break;
case 2:
//4 触发了乐观锁会进入此方法
this.entityIsDetached(event, copyCache);
break;
......
我们跟着调试最终看到,程序执行了这段代码,我们看到注释的第一步,这段代码
EntityEntry entry = source.getPersistenceContext().getEntry(entity);
他会先获取PersistenceContext对象,翻译一下就是持久化的上下文对象,听起来就很厉害,这个对象里面有什么呢?一起看一看
public interface PersistenceContext {
原来他是一个接口,那我们就去看他的实现
//杨乐乐源码分析 https://juejin.cn/user/4486440898279832
public class StatefulPersistenceContext implements PersistenceContext {
public static final Object NO_ROW = new MarkerObject("NO_ROW");
private static final Logger log = LoggerFactory.getLogger(StatefulPersistenceContext.class);
private static final Logger PROXY_WARN_LOG = LoggerFactory.getLogger(StatefulPersistenceContext.class.getName() + ".ProxyWarnLog");
private static final int INIT_COLL_SIZE = 8;
private SessionImplementor session;
private Map entitiesByKey;
private Map entitiesByUniqueKey;
//保存持久化实体的Map映射
private Map entityEntries;
他的实现是StatefulPersistenceContext类,我们看到entityEntries,他就是保存持久化实体的Map对象。
这里再说一下实体的持久化状态,再我们学习Hibernate的时候一定学习过实体有几种状态,Jpa也一样,他们就是Jpa对象的生命周期
image.pngNew:瞬时对象,尚未有id,还未和Persistence Context建立关联的对象。
Managed:持久化受管对象,有id值,已经和Persistence Context建立了关联的对象。
Datached:游离态离线对象,有id值,但没有和Persistence Context建立关联的对象。
Removed:删除的对象,有id值,尚且和Persistence Context有关联,但是已经准备好从数据库中删除
当从数据库获取的数据后,因为有事务管理,所以数据已与session关联,并且数据库有数据,已经持久化了,并且在数据库的缓存当中了,当我们对查询出来的数据进行修改时,缓存缓存Session中的数据发生改变,那么接着数据库也会跟着进行相应的改变。所以就自动执行了update的更新操作。
如上所述,我们的持久化状态就是Managed状态,也就是在一个事务中查询出来的实体对象,他有一个不一样的地方就是,就算你不进行save方法操作,在提交事务的时候,他也会去更新数据库,因为他与PersistenceContext对象进行了关联,这个关联就是上面的源码中所写的
我们接着看源码,最终的乐观锁报错会走到这个方法
//杨乐乐源码分析 https://juejin.cn/user/4486440898279832
protected void entityIsDetached(MergeEvent event, Map copyCache) {
log.trace("merging detached instance");
....
if (this.isVersionChanged(entity, source, persister, target)) {
if (source.getFactory().getStatistics().isStatisticsEnabled()) {
source.getFactory().getStatisticsImplementor().optimisticFailure(entityName);
}
//这里就是前面报错信息中的 StaleObjectStateException异常
throw new StaleObjectStateException(entityName, id);
}
然后我们看看他的判断条件this.isVersionChanged(entity, source, persister, target)
//杨乐乐源码分析 https://juejin.cn/user/4486440898279832
private boolean isVersionChanged(Object entity, EventSource source, EntityPersister persister, Object target) {
//1 判断实体中有没有version字段,如果没有不回触发乐观锁
if (!persister.isVersioned()) {
return false;
} else {
//2 判断事务中要保存的实体和数据库中的实体的version是否一致,不一致就会触发乐观锁
boolean changed = !persister.getVersionType().isSame(persister.getVersion(target, source.getEntityMode()), persister.getVersion(entity, source.getEntityMode()), source.getEntityMode());
return changed && this.existsInDatabase(target, source, persister);
}
}
这段代码就是乐观锁实现的核心判断,这里面就解答了乐观锁的实现是代码实现还是数据库中实现,也和乐观锁的version属性控制的概念向吻合。
但是这里还有一点要注意,就是如果保存的实体是持久化的状态是不回触发乐观锁的,为什么呢,我们看到这段代码的比较
//杨乐乐源码分析 https://juejin.cn/user/4486440898279832
boolean changed = !persister.getVersionType().isSame(persister.getVersion(target, source.getEntityMode()), persister.getVersion(entity, source.getEntityMode()), source.getEntityMode());
实际上就是target实体和entity实体中verson的比较,我们理解entity实体是我们要保存到数据库中的对象,target就是数据库中的的实体数据。其实这样理解是错误的。
通过对target对象的获取,我们可以分析到,实际上他的赋值逻辑是,如果在PersistenceContext对象,能找到对应的实体,那么他就不回去查数据库,那么这时他的version就是不准确,也就是会和entity对象中verison一样,是不回触发乐观锁的。
三 结语
技术的成长离不开总结与复盘,也离不开抽丝剥茧的分析,希望这篇文章在你的技术成长上能带来一些帮助,我是散播欢笑散播爱的杨乐乐,在技术的道路上期待和大家一起成长。
网友评论