An optimistic lock concept

What is the optimism lock? For what he is, first of all, optimistic locking is used, control data accuracy, such as A connection and B connection to modify A data at the same time, A connection to the data revised, B connection also amend the data, then B to submit to the transaction, if A connection also submit the transaction at this moment, so will cover B connection change, because A query the object does not contain B changes, So this is a problem. Optimistic locking is determined by version. If the version submitted is different from the version in the database, an exception will be thrown to ensure data certainty

Two optimistic lock implementation

Is optimistic locking implemented in the database or in Java code? How does he achieve it? I think this problem will trouble many people. There are many articles on the Internet that just give a general idea, such as the difference between optimistic lock and pessimistic lock

Optimistic lock implementation based on SpringDataJpa

Let’s start with a piece of code

SpecialPrintingRule rule =new SpecialPrintingRule();
rule.audit(identify);
specialPrintingRuleRepository.saveAndFlush(rule);
Copy the code

This code is SpringDataJpa’s save entity code, so let’s see how optimistic locking works. Okay

Let’s take a look at the optimistic lock error code

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(SharedEntityManagerCr eator.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(Reposi toryFactorySupport.java:410) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(Reposito ryFactorySupport.java:364) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:9 9) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.ja va: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(PersistenceExceptionTranslationInterce ptor.java:136) ... 147 common frames omittedCopy the code

If has experienced the classmate of optimistic locking error of the error message must be strange, no experience of students and it doesn’t matter, because only through this period of the realization of the error message to find optimistic locking, we see one line of code of the error message DefaultMergeEventListener. OnMerge (), Why don’t we check out what he did

https://juejin.cn/user/4486440898279832 / / rcy source analysis public void onMerge (MergeEvent event, Map copiedAlready) throws HibernateException { ...... PersistenceContext () To find whether there is and we insert from PersistenceContext entities of the same entity EntityEntry entry = source. GetPersistenceContext (). The 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 Obtain the entityState for subsequent judgment. EntityState = this.getentityState (entity, event.getEntityName(), entry, source); } //3 Switch (entityState) {case 0: this.entityIsPersistent(event, copyCache); break; case 1: this.entityIsTransient(event, copyCache); break; EntityIsDetached (event, copyCache) case 2: //4 This. EntityIsDetached (event, copyCache); break; .Copy the code

We’re going to debug and finally see that the program executes this code, and we’re going to see the first step of the comment, this code

EntityEntry entry = source.getPersistenceContext().getEntry(entity); PersistenceContext: PersistenceContext: PersistenceContext: PersistenceContext: PersistenceContext Take a look

public interface PersistenceContext {
Copy the code

It turns out it’s an interface, so we’re going to look at its implementation

https://juejin.cn/user/4486440898279832 / / rcy source analysis 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; Private Map entityEntries;Copy the code

He is the implementation of the StatefulPersistenceContext class, we see entityEntries, he is to preserve the Map object persistent entities.

Let’s talk about the persistent state of the entity again. When we studied Hibernate, we must have learned that there are several states of the entity, as well as Jpa, they are the life cycle of the Jpa object

New: transient object that does not yet have an ID and is not yet associated with the Persistence Context. Managed: Persisting a Managed object with an ID that has been associated with the Persistence Context Datached: free offline object with id value, but no object associated with the Persistence Context. Removed: The deleted object, which has an ID value, is still associated with the Persistence Context, but is ready to be deleted from the database. When the data is retrieved from the database, the data is associated with the session because of transaction management, and the database has data, has been persisted, and is in the database cache. When we modify the queried data, the cached Session data changes, and the database changes accordingly. So the update operation is performed automatically.

As mentioned above, our persistent state is the Managed state, which is the entity object queried in a transaction. It is different in that it will update the database even if you do not perform the save operation. The PersistenceContext object is associated with the PersistenceContext object, which is written in the source code above

As we continue to look at the source code, the final optimistic locking error will lead to this method

/ / rcy source code analysis 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); } / / this is the front of the error message StaleObjectStateException abnormal throw new StaleObjectStateException (entityName, id); }Copy the code

Then we look at his judgment condition this.isversionChanged (Entity, source, persister, target)

/ / rcy source code analysis https://juejin.cn/user/4486440898279832 private Boolean isVersionChanged (Object entity, EventSource source, EntityPersister persister, Object target) {//1 Check whether the version field exists in the entity. persister.isVersioned()) { return false; } else {//2 Check whether the version of the entity stored in the transaction is the same as that of the entity stored in the database. persister.getVersionType().isSame(persister.getVersion(target, source.getEntityMode()), persister.getVersion(entity, source.getEntityMode()), source.getEntityMode()); return changed && this.existsInDatabase(target, source, persister); }}Copy the code

This code is the core judgment of optimistic lock implementation, which answers the realization of optimistic lock is code implementation or database implementation, and optimistic lock version attribute control concept to match.

But there is one more thing to note here, which is that if the saved entity is in a persistent state, it will not trigger an optimistic lock. Why, we see this code comparison

/ / https://juejin.cn/user/4486440898279832 Boolean rcy source code analysis changed =! persister.getVersionType().isSame(persister.getVersion(target, source.getEntityMode()), persister.getVersion(entity, source.getEntityMode()), source.getEntityMode());Copy the code

It’s actually a comparison of Verson in the Target entity, which we understand is the object we want to store in the database, and the Target is the entity data in the database. In fact, this understanding is wrong.

PersistenceContext: PersistenceContext: PersistenceContext: PersistenceContext: PersistenceContext: PersistenceContext: PersistenceContext: PersistenceContext That is, like Verison in the Entity object, the optimistic lock is not triggered.

Three conclusion

The growth of technology can not be separated from summary and review, and can not be separated from the analysis of cocoons. I hope this article can bring some help to your technological growth. I am Yang Lele who spreads laughter and love, and I look forward to growing up with you on the road of technology.