javascript
Spring Data JPA 从入门到精通~@Version处理乐观锁的问题
@Version 處理樂觀鎖的問題
@Version 樂觀鎖介紹
我們在研究 Auditing 的時候,發現了一個有趣的注解 @Version,源碼如下:
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 {}發現它幫我們處理了樂觀鎖的問題,什么是樂觀鎖,還有線程的安全性,在另外一本書《Java 并發編程從入門到精通》里面,或者看作者的另外一篇 Chat:Java 多線程與并發編程 · Java 工程師必知必會,作者做了深入的探討。
對于數據來說,簡單理解:在數據庫并發操作時,為了保證數據的正確性,我們會做一些并發處理,主要就是加鎖。在加鎖的選擇上,常見有兩種方式:悲觀鎖和樂觀鎖。
- 悲觀鎖:簡單的理解就是把需要的數據全部加鎖,在事務提交之前,這些數據全部不可讀取和修改。
- 樂觀鎖:使用對單條數據進行版本校驗和比較,來對保證本次的更新是最新的,否則就失敗,效率要高很多。在實際工作中,樂觀鎖不止在數據庫層面,其實我們在做分布式系統的時候,為了實現分布式系統的數據一致性,分布式事物的一種做法就是樂觀鎖。
數據庫操作舉例說明
悲觀鎖的做法:
select * from user where id=1 for update; update user set name='jack' where id=1;通過使用 for update 給這條語句加鎖,如果事務沒有提交,其他任何讀取和修改,都得排隊等待。在代碼中,我們加事務的 Java 方法就會自然的形成了一個鎖。
樂觀鎖的做法:
select uid,name,version from user where id=1; update user set name='jack', version=version+1 where id=1 and version=1假設本次查詢 version=1,在更新操作時,帶上這次查出來的 Version,這樣只有和我們上次版本一樣的時候才會更新,就不會出現互相覆蓋的問題,保證了數據的原子性。
@Version 用法
在沒有 @Version 之前,我們都是自己手動維護這個 Version 的,這樣很有可能做什么操作的時候給忘掉。或者是我們自己底層做框架,用 AOP 的思路做攔截底層維護這個 Version 的值。而 Spring Data JPA 的 @Version 就是通過 AOP 機制,幫我們動態維護這個 Version,從而更優雅的實現樂觀鎖。
(1)實體上的 Version 字段加上 @Version 注解即可。
我們對上面的實體 UserCustomerEntity 改進如下:
@Entity @Table(name = "user_customer", schema = "test", catalog = "") public class UserCustomerEntity extends AbstractAuditable {//新增控制樂觀鎖的字段。并且加上@Version注解@Version@Column(name = "version", nullable = true)private Long version; ...... }(2)實際調用
userCustomerRepository.save(new UserCustomerEntity("1","Jack")); UserCustomerEntity uc= userCustomerRepository.findOne(1); uc.setCustomerName("Jack.Zhang"); userCustomerRepository.save(uc);我們會發現 Insert 和 Update 的 SQL 語句都會帶上 Version 的操作。當樂觀鎖更新失敗的時候,會拋出異常 org.springframework.orm.ObjectOptimisticLockingFailureException。
實現原理關鍵代碼
(1)SimpleJpaRepository.class 里面的 save 方法如下:
public <S extends T> S save(S entity) {if (entityInformation.isNew(entity)) {em.persist(entity);return entity;} else {return em.merge(entity);} }(2)如果我們在此處設置一個 debug 斷點的話,我們一步一步往下面走會發現進入 JpaMetamodelEntityInformation.class 的關鍵代碼如下:
@Overridepublic 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);}所以到這里,可以看出當我們更新的時候,若實體對象上面有 @Version 注解,那么就一定要帶上 version,如果沒帶上 version 字段的值,只有 ID 字段的值,系統也會認為是新增。相反,如果我們沒有 @Version 注解的字段,那么就會以 @ID 字段來判斷是否是新增。其實這里我們也明白,省去了傳統都需要我們自己去實現的 saveOrUpdate 方法。
(3)其實我們多看看代碼,多 debug 幾次就會發現,也可以在 @Entity 的類里面覆蓋掉 isNew() 方法,這樣可以實現自己的 isNew 的判斷邏輯。
@Entity @Table(name = "user") public class UserEntity implements Persistable {@Transient //這個注解表明這個字段不是持久化的@JsonIgnore //json顯示的時候我們也可忽略這個字段@Overridepublic boolean isNew() {return getId() == null;}.... }總結
以上是生活随笔為你收集整理的Spring Data JPA 从入门到精通~@Version处理乐观锁的问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html前端如何缓存页面,Nuxt中如何
- 下一篇: Effective Java~37. 用