@Version handles optimistic locks
@Introduction to Version lock
When studying Auditing, we found an interesting annotation @ Version. The source code is as follows:
package org.springframework.data.annotation; /** * Demarcates a property to be used as version field to implement optimistic locking on entities. */ @Retention(RUNTIME) @Target(value = { FIELD, METHOD, ANNOTATION_TYPE }) public @interface Version {}
We found that it helps us deal with the problem of optimistic lock, what is optimistic lock, and thread security, in another book Java Concurrent Programming from introduction to mastery Inside, or see another Chat by the author: Java multithreading and concurrent programming , the author makes an in-depth discussion.
For data, simple understanding: in order to ensure the correctness of data during database concurrent operation, we will do some concurrent processing, mainly locking. There are two common ways to choose locks: pessimistic locks and optimistic locks.
- Pessimistic lock: a simple understanding is to lock all the required data. Before the transaction is committed, all these data cannot be read or modified.
- Optimistic lock: use version Verification and comparison for a single piece of data to ensure that the update is up-to-date, otherwise it will fail and the efficiency will be much higher. In practical work, optimistic locking is not only at the database level. In fact, when we are doing distributed systems, in order to achieve data consistency of distributed systems, one method of distributed things is optimistic locking.
Examples of database operations
Pessimistic lock practice:
select * from user where id=1 for update; update user set name='jack' where id=1;
By using for update to lock this statement, if the transaction is not committed, any other reads and modifications must be queued. In the code, our Java method of adding transactions will naturally form a lock.
Optimistic approach:
select uid,name,version from user where id=1; update user set name='jack', version=version+1 where id=1 and version=1
Assuming that the Version of this query is 1, the Version found this time will be brought during the update operation, so that it will be updated only when it is the same as our last Version, so that there will be no problem of mutual coverage and ensure the atomicity of the data.
@Version usage
Before @ Version, we maintained it manually, so we might forget what to do. Or we can use our own framework to intercept and maintain the value of this Version at the bottom with the idea of AOP. The @ Version of Spring Data JPA helps us maintain this Version dynamically through the AOP mechanism, so as to realize optimistic locking more gracefully.
(1) Add @ Version annotation to the Version field on the entity.
We have improved the above entity UserCustomerEntity as follows:
@Entity @Table(name = "user_customer", schema = "test", catalog = "") public class UserCustomerEntity extends AbstractAuditable { //Add a field to control optimistic lock. And add @ Version annotation @Version @Column(name = "version", nullable = true) private Long version; ...... }
(2) Actual call
userCustomerRepository.save(new UserCustomerEntity("1","Jack")); UserCustomerEntity uc= userCustomerRepository.findOne(1); uc.setCustomerName("Jack.Zhang"); userCustomerRepository.save(uc);
We will find that the SQL statements of Insert and Update will carry the operation of Version. When the Update of leguan lock fails, an exception org. Org will be thrown springframework. orm. ObjectOptimisticLockingFailureException.
Implementation principle key code
(1)SimpleJpaRepository. The save method in class is as follows:
public <S extends T> S save(S entity) { if (entityInformation.isNew(entity)) { em.persist(entity); return entity; } else { return em.merge(entity); } }
(2) If we set a debug breakpoint here, step by step, we will find that we enter the jpametodelementityinformation The key codes of class are as follows:
@Override public boolean isNew(T entity) { if (!versionAttribute.isPresent() || versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) { return super.isNew(entity); } BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity); return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true); }
So here, we can see that when we update, if there is @ version annotation on the entity object, we must bring version. If we do not bring the value of version field, but only the value of ID field, the system will also consider it as a new addition. On the contrary, if we do not have a field annotated with @ version, we will judge whether it is a new one by the @ ID field. In fact, we also understand here that the traditional saveOrUpdate method that needs to be implemented by ourselves is omitted.
(3) In fact, if we look at the code and debug it several times, we will find that we can also override the isNew() method in the @ Entity class, so that we can implement our own isNew judgment logic.
@Entity @Table(name = "user") public class UserEntity implements Persistable { @Transient //This annotation indicates that the field is not persistent @JsonIgnore //We can also ignore this field when json is displayed @Override public boolean isNew() { return getId() == null; } .... }