利用Spring JPA中的@Version注解實(shí)現(xiàn)樂(lè)觀鎖
簡(jiǎn)介
樂(lè)觀鎖是數(shù)據(jù)庫(kù)和應(yīng)用程序中使用的一種并發(fā)控制策略,用于在多個(gè)事務(wù)嘗試更新單個(gè)記錄時(shí)確保數(shù)據(jù)完整性。Java Persistence API (JPA) 提供了一種借助@Version注解在 Java 應(yīng)用程序中實(shí)現(xiàn)樂(lè)觀鎖的機(jī)制。在基于 Spring 的應(yīng)用程序的上下文中,與擴(kuò)展的 Spring Data JPA 庫(kù)結(jié)合使用時(shí),他就發(fā)揮了作用。
了解樂(lè)觀鎖
在數(shù)據(jù)庫(kù)和應(yīng)用程序開(kāi)發(fā)領(lǐng)域,維護(hù)數(shù)據(jù)的完整性和一致性至關(guān)重要。這種情況下遇到的主要挑戰(zhàn)之一是處理對(duì)共享數(shù)據(jù)資源的并發(fā)訪問(wèn),尤其是在涉及更新時(shí)。在分布式系統(tǒng)中,這個(gè)問(wèn)題就變得更加嚴(yán)重,其中多個(gè)用戶或服務(wù)可能會(huì)嘗試同時(shí)修改同一條數(shù)據(jù)。樂(lè)觀鎖是一種旨在管理并發(fā)訪問(wèn)而無(wú)需進(jìn)行嚴(yán)格鎖定的策略。
什么是樂(lè)觀鎖?
從本質(zhì)上講,樂(lè)觀鎖是在首次讀取或訪問(wèn)數(shù)據(jù)記錄進(jìn)行潛在更新,但實(shí)際上并未鎖定數(shù)據(jù)記錄。使系統(tǒng)在樂(lè)觀的假設(shè)下運(yùn)行,即沖突很少且不會(huì)經(jīng)常發(fā)生。僅當(dāng)即將提交實(shí)際更新時(shí),系統(tǒng)才會(huì)檢查沖突。如果數(shù)據(jù)同時(shí)被另一個(gè)事務(wù)更改,則當(dāng)前更新失敗。
悲觀鎖與樂(lè)觀鎖
悲觀鎖: 在很多方面與樂(lè)觀鎖相反,一旦事務(wù)開(kāi)始就鎖定數(shù)據(jù),從而防止任何其他事務(wù)在第一個(gè)事務(wù)完成之前訪問(wèn)相同的數(shù)據(jù)。雖然這保證了數(shù)據(jù)完整性,但這樣做的代價(jià)是潛在的瓶頸、吞吐量降低和死鎖的風(fēng)險(xiǎn)。
樂(lè)觀鎖: 樂(lè)觀鎖,如前所述,允許多個(gè)事務(wù)并發(fā)訪問(wèn)數(shù)據(jù),只在最后一刻檢查沖突。這使得它適合讀操作遠(yuǎn)多于寫操作并且數(shù)據(jù)沖突很少的場(chǎng)景。
底層機(jī)制
樂(lè)觀鎖的機(jī)制通常涉及系統(tǒng)的版本控制。數(shù)據(jù)庫(kù)中的每個(gè)記錄或?qū)嶓w都有一個(gè)關(guān)聯(lián)的版本號(hào)或時(shí)間戳。當(dāng)事務(wù)讀取記錄時(shí),它還會(huì)記下其版本。在事務(wù)提交更新之前,它會(huì)檢查數(shù)據(jù)庫(kù)中的當(dāng)前版本是否與其之前記錄的版本匹配。如果它們匹配,則更新完成,并且版本號(hào)遞增。如果不是,則檢測(cè)到?jīng)_突,并且更新失敗。
樂(lè)觀鎖的好處
- 增強(qiáng)吞吐量: 由于在事務(wù)持續(xù)時(shí)間的大部分時(shí)間內(nèi)沒(méi)有持有鎖,因此等待時(shí)間最少。這導(dǎo)致同時(shí)處理更多的交易。
- 最小化死鎖: 死鎖是一種事務(wù)無(wú)限期地等待其他人鎖定的資源的情況,這種情況的可能性要小得多,因?yàn)閿?shù)據(jù)不會(huì)長(zhǎng)時(shí)間鎖定。
- 更好的可擴(kuò)展性: 隨著分布式系統(tǒng)和微服務(wù)架構(gòu)的興起,樂(lè)觀鎖定在確保系統(tǒng)能夠有效擴(kuò)展而無(wú)需管理復(fù)雜鎖機(jī)制的開(kāi)銷方面發(fā)揮著關(guān)鍵作用。
缺點(diǎn)
- 沖突管理開(kāi)銷: 在沖突頻繁的場(chǎng)景中,管理和解決沖突可能會(huì)占用大量資源。
- 復(fù)雜性: 實(shí)現(xiàn)樂(lè)觀鎖需要經(jīng)過(guò)深思熟慮的設(shè)計(jì),特別是在處理失敗的事務(wù)時(shí)。
- 過(guò)時(shí)數(shù)據(jù)的可能性: 由于數(shù)據(jù)在讀取時(shí)未鎖定,因此事務(wù)可能會(huì)使用過(guò)時(shí)或過(guò)時(shí)的數(shù)據(jù),如果管理不正確,可能會(huì)導(dǎo)致邏輯錯(cuò)誤或不一致。
Spring @Version注解
Spring JPA(Java Persistence API)為開(kāi)發(fā)人員提供了一組強(qiáng)大的工具來(lái)管理應(yīng)用程序中的關(guān)系型數(shù)據(jù)。其最強(qiáng)大的功能之一是處理并發(fā)性并確保多用戶環(huán)境中的數(shù)據(jù)完整性。這就是@Version注解發(fā)揮作用的地方,它提供了一種在 Spring JPA 中實(shí)現(xiàn)樂(lè)觀鎖的優(yōu)雅方法。
@Version的作用
該@Version注解用于啟用實(shí)體上的樂(lè)觀鎖,確保數(shù)據(jù)庫(kù)中的數(shù)據(jù)更新不會(huì)出現(xiàn)并發(fā)修改問(wèn)題。當(dāng)實(shí)體中的某個(gè)字段標(biāo)記為@Version時(shí),JPA 將使用該字段來(lái)跟蹤更改并確保一次只有一個(gè)事務(wù)可以更新特定行。
它是如何工作的?
每個(gè)用注解標(biāo)記的實(shí)體都@Version將由 JPA 跟蹤其版本。這是基本機(jī)制:
- 初始化: 當(dāng)實(shí)體第一次被持久化(保存到數(shù)據(jù)庫(kù))時(shí),版本字段(通常是整數(shù)或時(shí)間戳)被設(shè)置為其初始值,通常為零。
- 讀?。?/strong> 稍后獲取實(shí)體時(shí),JPA 會(huì)從數(shù)據(jù)庫(kù)中檢索當(dāng)前版本。
- 更新: 在嘗試更新或刪除實(shí)體時(shí),JPA 會(huì)根據(jù)實(shí)體的版本檢查數(shù)據(jù)庫(kù)中的當(dāng)前版本。如果它們匹配,則操作繼續(xù),并且數(shù)據(jù)庫(kù)中的版本增加(用于更新)。
- 沖突: 如果版本不匹配,則表明另一個(gè)事務(wù)同時(shí)更新了實(shí)體,導(dǎo)致 JPA 拋出OptimisticLockException.
應(yīng)用
@Entity public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String author; @Version private int version; }
在此設(shè)置中,版本字段跟蹤每個(gè)圖書(shū)實(shí)體的更改。如果兩個(gè)用戶檢索同一本書(shū),然后嘗試同時(shí)更新它,則只有其中一個(gè)會(huì)成功。另一個(gè)將遇到 OptimisticLockException,因?yàn)閿?shù)據(jù)庫(kù)中書(shū)籍的版本號(hào)將因第一個(gè)用戶的更新而增加。
主要優(yōu)勢(shì)
- 自動(dòng)管理:一旦設(shè)置了 @Version,Spring JPA 就會(huì)自動(dòng)管理版本檢查,從而抽象出與手動(dòng)并發(fā)控制相關(guān)的大部分復(fù)雜性。
- 增強(qiáng)的數(shù)據(jù)完整性:使用 @Version 后,可以確保數(shù)據(jù)不會(huì)被并發(fā)事務(wù)意外覆蓋。
- 簡(jiǎn)化的錯(cuò)誤處理:當(dāng)出現(xiàn)沖突時(shí),會(huì)通過(guò)明確定義的異常 (OptimisticLockException) 拋出異常,從而使錯(cuò)誤處理變得簡(jiǎn)單。
在 Spring JPA 應(yīng)用程序中實(shí)現(xiàn) @Version
使用 Spring 的 @Version 注解非常簡(jiǎn)單,但在確保應(yīng)用程序中的數(shù)據(jù)一致性時(shí)卻非常重要。以下是如何逐步實(shí)施:
定義實(shí)體
第一步是向您的實(shí)體添加版本字段并使用 @Version 對(duì)其進(jìn)行注解。該字段通??梢允钦麛?shù)或時(shí)間戳。
@Entity public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; @Version private int version; }
在此設(shè)置中,JPA 將使用版本字段來(lái)跟蹤每個(gè)產(chǎn)品實(shí)體的更改。
創(chuàng)建Repository
Spring Data JPA 提供了一種無(wú)需太多樣板代碼即可執(zhí)行 CRUD 操作的方法。只需為您的實(shí)體創(chuàng)建一個(gè)擴(kuò)展了 JpaRepository 的接口:
public interface ProductRepository extends JpaRepository<Product, Long> { }
執(zhí)行CRUD操作
創(chuàng)建Repository后,執(zhí)行 CRUD 操作變得輕而易舉。
通過(guò)id獲取產(chǎn)品:
@Autowired ProductRepository productRepository; public Product getProduct(Long id) { return productRepository.findById(id).orElse(null); }
更新產(chǎn)品:
public void updateProductName(Long id, String newName) { Product product = productRepository.findById(id).orElseThrow(() -> new RuntimeException("Product not found")); product.setName(newName); productRepository.save(product); }
如果在獲取產(chǎn)品和嘗試保存更新版本之間另一個(gè)事務(wù)已更改該產(chǎn)品,JPA 將拋出 OptimisticLockException,指示版本沖突。
處理沖突
使用 @Version 時(shí),沖突由 OptimisticLockException 發(fā)出信號(hào)。捕獲此異常并通過(guò)重試操作、通知用戶或任何其他適合您的應(yīng)用程序邏輯的糾正措施進(jìn)行適當(dāng)處理是至關(guān)重要的。
@Transactional public void updateProductNameWithConflictHandling(Long id, String newName) { try { Product product = productRepository.findById(id).orElseThrow(() -> new RuntimeException("Product not found")); product.setName(newName); productRepository.save(product); } catch (OptimisticLockException e) { //處理異常,重試或者通知用戶 } }
處理樂(lè)觀鎖異常
使用 @Version 注解的顯著優(yōu)點(diǎn)之一是它通過(guò) OptimisticLockException 清楚地發(fā)出沖突信號(hào)。雖然異常對(duì)于標(biāo)記并發(fā)修改非常有價(jià)值,但處理它的方式會(huì)顯著影響用戶體驗(yàn)和應(yīng)用程序可靠性。
了解異常
在深入研究處理策略之前,了解 OptimisticLockException 的本質(zhì)至關(guān)重要。當(dāng) JPA 在更新或刪除操作期間檢測(cè)到版本不匹配時(shí),JPA 會(huì)拋出此異常,表明自上次訪問(wèn)以來(lái)嘗試修改的數(shù)據(jù)已被另一個(gè)事務(wù)更改。
基本處理:通知用戶
最直接的方法是捕獲異常并通知用戶發(fā)生了沖突。此策略通常用于沖突很少且可以手動(dòng)解決的應(yīng)用程序。
try { } catch (OptimisticLockException e) { // 通知用戶 throw new ResponseStatusException(HttpStatus.CONFLICT, "數(shù)據(jù)被另一個(gè)事務(wù)更改"); }
高級(jí)處理:自動(dòng)重試
在沖突更加頻繁且手動(dòng)解決不切實(shí)際的情況下,可以考慮實(shí)施自動(dòng)重試
int maxRetries = 3; for (int i = 0; i < maxRetries; i++) { try { //執(zhí)行更新 break; //如果成功跳出循環(huán) } catch (OptimisticLockException e) { if (i == maxRetries - 1) { throw new ResponseStatusException(HttpStatus.CONFLICT, "正在重試"); } } }
高級(jí)處理:合并更改
另一種方法是獲取實(shí)體的最新版本,合并更改,然后再次嘗試更新。當(dāng)需要保留多個(gè)來(lái)源的更改時(shí),此方法非常有用。
try { // 執(zhí)行更新 } catch (OptimisticLockException e) { // 獲取最新的版本 Product latestProduct = productRepository.findById(productId).orElseThrow(); // 合并更改。此步驟將根據(jù)您的應(yīng)用程序邏輯而有所不同。 // 例如,您可能決定合并不沖突的字段或應(yīng)用其他業(yè)務(wù)規(guī)則。 // 保存最新的 productRepository.save(latestProduct); }
總結(jié)
利用Spring JPA 提供的@Version 注解實(shí)現(xiàn)樂(lè)觀鎖是確保并發(fā)數(shù)據(jù)更新的應(yīng)用程序中數(shù)據(jù)完整性的一種簡(jiǎn)單方法。樂(lè)觀鎖對(duì)于提高吞吐量和減少死鎖的優(yōu)勢(shì)很明顯,但有效處理樂(lè)觀鎖定異常對(duì)于保持流暢的用戶體驗(yàn)至關(guān)重要。通過(guò)實(shí)現(xiàn)重試機(jī)制或向用戶提供反饋信息以做出明智的決策,您可以將樂(lè)觀鎖定無(wú)縫集成到 Spring JPA 應(yīng)用程序中。
以上就是利用Spring JPA中的@Version注解實(shí)現(xiàn)樂(lè)觀鎖的詳細(xì)內(nèi)容,更多關(guān)于Spring JPA @Version樂(lè)觀鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringMVC中@ModelAttribute注解的使用教程
在SpringMVC中,我們可以通過(guò)使用@ModelAttribute注解標(biāo)記方法,實(shí)現(xiàn)類似于Struts2中Preparable攔截器的效果,這篇文章主要給大家介紹了關(guān)于SpringMVC中@ModelAttribute注解使用的相關(guān)資料,需要的朋友可以參考下2021-08-08Mybatis有查詢結(jié)果但存不進(jìn)實(shí)體類的解決方案
這篇文章主要介紹了Mybatis有查詢結(jié)果但存不進(jìn)實(shí)體類的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11Fluent Mybatis,原生Mybatis,Mybatis Plus三者功能對(duì)比
本文主要介紹了Fluent Mybatis,原生Mybatis,Mybatis Plus三者功能對(duì)比,分享給大家,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08SpringCloud輪詢拉取注冊(cè)表與服務(wù)發(fā)現(xiàn)流程詳解
這篇文章主要介紹了SpringCloud輪詢拉取注冊(cè)表與服務(wù)發(fā)現(xiàn),現(xiàn)在很多創(chuàng)業(yè)公司都開(kāi)始往springcloud靠了,可能是由于文檔和組件比較豐富的原因吧,畢竟是一款目前來(lái)說(shuō)比較完善的微服務(wù)架構(gòu)2022-11-11Java數(shù)據(jù)結(jié)構(gòu)之有效隊(duì)列定義與用法示例
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之有效隊(duì)列定義與用法,結(jié)合實(shí)例形式分析了java有效隊(duì)列的數(shù)據(jù)插入、刪除、判斷、計(jì)算等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10java關(guān)鍵字abstract(抽象)實(shí)例詳解
在Java中,抽象類是不能實(shí)例化的類,它通常作為其他子類的父類存在,并提供了一種繼承的框架,抽象類中可以包含抽象方法,這些方法沒(méi)有具體的實(shí)現(xiàn),必須由子類來(lái)提供,本文給大家介紹java關(guān)鍵字abstract(抽象)實(shí)例詳解,感興趣的朋友跟隨小編一起看看吧2024-10-10