MyBatis-Plus 樂(lè)觀鎖的具體實(shí)現(xiàn)
在現(xiàn)代應(yīng)用中,樂(lè)觀鎖(Optimistic Locking)是解決并發(fā)問(wèn)題的重要機(jī)制。它通過(guò)在數(shù)據(jù)更新時(shí)驗(yàn)證數(shù)據(jù)版本來(lái)確保數(shù)據(jù)的一致性,從而避免并發(fā)沖突。與悲觀鎖不同,樂(lè)觀鎖并不依賴(lài)數(shù)據(jù)庫(kù)的鎖機(jī)制,而是通過(guò)檢查數(shù)據(jù)的版本或標(biāo)志字段來(lái)判斷數(shù)據(jù)是否被其他事務(wù)修改過(guò)。
MyBatis-Plus 提供了便捷的樂(lè)觀鎖支持,通過(guò)簡(jiǎn)單的配置即可實(shí)現(xiàn)樂(lè)觀鎖機(jī)制,在高并發(fā)場(chǎng)景下確保數(shù)據(jù)的一致性,同時(shí)不影響系統(tǒng)的并發(fā)性能。
一、什么是樂(lè)觀鎖
樂(lè)觀鎖是樂(lè)觀并發(fā)控制的一種實(shí)現(xiàn)方式,它假設(shè)多個(gè)事務(wù)并發(fā)操作數(shù)據(jù)時(shí)不會(huì)產(chǎn)生沖突,或者認(rèn)為沖突的概率較低,因此在每次操作時(shí)不會(huì)直接鎖定資源。它的基本思路是在數(shù)據(jù)的每條記錄中添加一個(gè)版本號(hào)字段,表示該數(shù)據(jù)的版本。當(dāng)用戶(hù)更新數(shù)據(jù)時(shí),會(huì)檢查該版本號(hào)是否發(fā)生變化。
樂(lè)觀鎖的典型工作流程如下:
- 在讀取數(shù)據(jù)時(shí),同時(shí)讀取該記錄的版本號(hào)。
- 在更新數(shù)據(jù)時(shí),檢查當(dāng)前數(shù)據(jù)的版本號(hào)是否與讀取時(shí)一致。
- 如果版本號(hào)一致,則說(shuō)明數(shù)據(jù)沒(méi)有被其他事務(wù)修改,可以執(zhí)行更新操作,并將版本號(hào)加 1。
- 如果版本號(hào)不一致,則說(shuō)明數(shù)據(jù)已經(jīng)被其他事務(wù)修改,此時(shí)應(yīng)當(dāng)放棄更新,提示用戶(hù)數(shù)據(jù)已被修改。
二、MyBatis-Plus 樂(lè)觀鎖的實(shí)現(xiàn)
MyBatis-Plus 中的樂(lè)觀鎖通過(guò)版本號(hào)字段來(lái)實(shí)現(xiàn),通常需要以下幾個(gè)步驟:
- 在實(shí)體類(lèi)中為數(shù)據(jù)添加一個(gè)版本號(hào)字段。
- 配置 MyBatis-Plus 的樂(lè)觀鎖插件。
- 在更新時(shí)由 MyBatis-Plus 自動(dòng)檢查版本號(hào),并在成功更新后遞增版本號(hào)。
三、MyBatis-Plus 樂(lè)觀鎖的配置
1. 引入依賴(lài)
首先,需要在項(xiàng)目中引入 MyBatis-Plus 的依賴(lài)。如果已經(jīng)使用 MyBatis-Plus,則可以跳過(guò)這一步。
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency>
2. 配置樂(lè)觀鎖插件
MyBatis-Plus 提供了內(nèi)置的樂(lè)觀鎖插件,需要在項(xiàng)目的配置類(lèi)中進(jìn)行注冊(cè):
@Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加樂(lè)觀鎖插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }
OptimisticLockerInnerInterceptor
是 MyBatis-Plus 提供的樂(lè)觀鎖插件,當(dāng)我們進(jìn)行更新操作時(shí),它會(huì)自動(dòng)檢查數(shù)據(jù)的版本號(hào),確保樂(lè)觀鎖生效。
3. 實(shí)體類(lèi)配置
在實(shí)體類(lèi)中,使用 @Version
注解標(biāo)識(shí)樂(lè)觀鎖的版本字段。版本字段的類(lèi)型通常為 Integer
或 Long
,在更新數(shù)據(jù)時(shí),MyBatis-Plus 會(huì)自動(dòng)對(duì)該字段的值進(jìn)行檢查和遞增。
@Data public class User { private Long id; private String name; private Integer age; // 樂(lè)觀鎖版本號(hào)字段 @Version private Integer version; }
在這個(gè)示例中,version
字段用于記錄版本號(hào),@Version
注解告訴 MyBatis-Plus 該字段是樂(lè)觀鎖的版本控制字段。
4. 更新操作
在執(zhí)行更新操作時(shí),MyBatis-Plus 會(huì)自動(dòng)檢查數(shù)據(jù)的版本號(hào)。如果版本號(hào)匹配,更新成功并將版本號(hào)加 1;如果版本號(hào)不匹配,則更新失敗,防止數(shù)據(jù)被覆蓋。
User user = userMapper.selectById(1L); // 讀取用戶(hù)數(shù)據(jù) user.setName("New Name"); user.setAge(30); int result = userMapper.updateById(user); // 執(zhí)行更新 if (result == 0) { // 如果返回值為 0,說(shuō)明更新失敗,版本號(hào)不匹配 System.out.println("更新失敗,數(shù)據(jù)可能已經(jīng)被其他用戶(hù)修改"); } else { // 更新成功,MyBatis-Plus 會(huì)自動(dòng)將 version 字段加 1 System.out.println("更新成功"); }
MyBatis-Plus 自動(dòng)生成的更新 SQL 類(lèi)似于以下 SQL:
UPDATE user SET name = 'New Name', age = 30, version = version + 1 WHERE id = 1 AND version = 1;
version = 1
用于確保數(shù)據(jù)在更新時(shí)未被其他事務(wù)修改。version = version + 1
在更新成功后,自動(dòng)將版本號(hào)遞增。
四、樂(lè)觀鎖的工作原理
MyBatis-Plus 的樂(lè)觀鎖通過(guò) @Version
注解和樂(lè)觀鎖插件實(shí)現(xiàn)。當(dāng)我們更新數(shù)據(jù)時(shí),MyBatis-Plus 會(huì)在生成的 SQL 語(yǔ)句中加入對(duì)版本號(hào)的條件檢查。如果該版本號(hào)匹配,更新成功,并將版本號(hào)加 1;如果版本號(hào)不匹配,說(shuō)明數(shù)據(jù)已經(jīng)被其他事務(wù)修改,更新操作會(huì)失敗。
具體來(lái)說(shuō),MyBatis-Plus 的樂(lè)觀鎖會(huì)執(zhí)行以下幾個(gè)步驟:
- 查詢(xún)數(shù)據(jù):首先,用戶(hù)讀取數(shù)據(jù),同時(shí)讀取數(shù)據(jù)的版本號(hào)。
- 修改數(shù)據(jù):用戶(hù)修改數(shù)據(jù)內(nèi)容,同時(shí)不修改版本號(hào)字段。
- 提交更新:當(dāng)用戶(hù)提交更新時(shí),MyBatis-Plus 會(huì)在
WHERE
條件中加入版本號(hào)的檢查。- 如果版本號(hào)匹配,更新成功,并將版本號(hào)加 1。
- 如果版本號(hào)不匹配,更新失敗,MyBatis-Plus 返回 0,表示更新未成功。
五、樂(lè)觀鎖失敗處理
當(dāng)使用樂(lè)觀鎖進(jìn)行并發(fā)控制時(shí),可能會(huì)出現(xiàn)更新失敗的情況,通常是因?yàn)樵谟脩?hù)提交修改前,數(shù)據(jù)已經(jīng)被其他用戶(hù)修改。這種情況下,需要根據(jù)業(yè)務(wù)場(chǎng)景進(jìn)行處理,常見(jiàn)的處理方式包括:
- 提示用戶(hù)重新獲取最新數(shù)據(jù):在更新失敗后,提示用戶(hù)數(shù)據(jù)已經(jīng)發(fā)生變更,讓用戶(hù)重新查看并進(jìn)行修改。
- 自動(dòng)重試機(jī)制:在更新失敗時(shí),系統(tǒng)可以嘗試重新讀取最新數(shù)據(jù),并在一定次數(shù)內(nèi)重新執(zhí)行更新操作。
- 合并數(shù)據(jù):在某些情況下,可以嘗試將用戶(hù)的修改與數(shù)據(jù)庫(kù)中的最新數(shù)據(jù)進(jìn)行合并,避免數(shù)據(jù)丟失。
以下是自動(dòng)重試機(jī)制的一個(gè)簡(jiǎn)單示例:
int retryCount = 3; // 最大重試次數(shù) boolean success = false; while (retryCount > 0 && !success) { User user = userMapper.selectById(1L); // 重新讀取數(shù)據(jù) user.setName("New Name"); user.setAge(30); int result = userMapper.updateById(user); // 嘗試更新 if (result == 0) { retryCount--; System.out.println("更新失敗,剩余重試次數(shù):" + retryCount); } else { success = true; System.out.println("更新成功"); } } if (!success) { System.out.println("更新失敗,請(qǐng)重試"); }
在這個(gè)例子中,系統(tǒng)會(huì)在更新失敗時(shí)自動(dòng)重試,直到達(dá)到最大重試次數(shù)。
六、樂(lè)觀鎖的應(yīng)用場(chǎng)景
樂(lè)觀鎖適合以下應(yīng)用場(chǎng)景:
- 高并發(fā)環(huán)境:在高并發(fā)場(chǎng)景下,通過(guò)樂(lè)觀鎖可以減少數(shù)據(jù)庫(kù)鎖定的時(shí)間,提高系統(tǒng)的并發(fā)性能。特別是在讀多寫(xiě)少的場(chǎng)景中,樂(lè)觀鎖可以很好地避免頻繁的鎖操作。
- 無(wú)狀態(tài)服務(wù):樂(lè)觀鎖適用于無(wú)狀態(tài)服務(wù),尤其是在分布式系統(tǒng)中。由于樂(lè)觀鎖不依賴(lài)數(shù)據(jù)庫(kù)鎖機(jī)制,因此適合分布式事務(wù)場(chǎng)景。
- 業(yè)務(wù)允許失敗重試:在業(yè)務(wù)邏輯允許用戶(hù)重試的情況下,樂(lè)觀鎖可以確保數(shù)據(jù)一致性,并提供簡(jiǎn)單的失敗處理方式。
七、MyBatis-Plus 樂(lè)觀鎖的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 提高并發(fā)性能:樂(lè)觀鎖不需要數(shù)據(jù)庫(kù)層面的鎖定,避免了資源的長(zhǎng)時(shí)間占用,適合高并發(fā)環(huán)境。
- 避免死鎖:由于樂(lè)觀鎖沒(méi)有數(shù)據(jù)庫(kù)的鎖定操作,避免了在并發(fā)操作中發(fā)生死鎖的問(wèn)題。
- 無(wú)侵入性:MyBatis-Plus 的樂(lè)觀鎖通過(guò)注解和插件實(shí)現(xiàn),對(duì)現(xiàn)有代碼的侵入性非常小。
缺點(diǎn):
- 更新失敗可能性高:在并發(fā)寫(xiě)操作較多的場(chǎng)景下,樂(lè)觀鎖可能導(dǎo)致較高的更新失敗率,需要增加重試機(jī)制來(lái)確保數(shù)據(jù)修改。
- 適用場(chǎng)景有限:樂(lè)觀鎖更適合讀多寫(xiě)少的業(yè)務(wù)場(chǎng)景,如果寫(xiě)操作頻繁,可能會(huì)導(dǎo)致頻繁的更新失敗。
八、總結(jié)
MyBatis-Plus 的樂(lè)觀鎖通過(guò)簡(jiǎn)單的配置和注解,可以輕松實(shí)現(xiàn)高并發(fā)場(chǎng)景下的數(shù)據(jù)并發(fā)控制。通過(guò)版本號(hào)機(jī)制,MyBatis-Plus 確保了在多用戶(hù)同時(shí)操作數(shù)據(jù)時(shí),數(shù)據(jù)不會(huì)被錯(cuò)誤地覆蓋。同時(shí),樂(lè)觀鎖機(jī)制不依賴(lài)數(shù)據(jù)庫(kù)的鎖機(jī)制,適合無(wú)狀態(tài)、分布式系統(tǒng)和高并發(fā)環(huán)境。
到此這篇關(guān)于MyBatis-Plus 樂(lè)觀鎖的具體實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)MyBatis-Plus 樂(lè)觀鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java如何使用fastjson修改多層嵌套的Objectjson數(shù)據(jù)
這篇文章主要介紹了java如何使用fastjson修改多層嵌套的Objectjson數(shù)據(jù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05java實(shí)現(xiàn)遠(yuǎn)程連接執(zhí)行命令行與上傳下載文件
這篇文章主要介紹了java實(shí)現(xiàn)遠(yuǎn)程連接執(zhí)行命令行與上傳下載文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05java 靜態(tài)工廠代替多參構(gòu)造器的適用情況與優(yōu)劣
這篇文章主要介紹了java 靜態(tài)工廠代替多參構(gòu)造器的優(yōu)劣,幫助大家更好的理解和使用靜態(tài)工廠方法,感興趣的朋友可以了解下2020-12-12詳解java開(kāi)啟異步線程的幾種方法(@Async,AsyncManager,線程池)
在springboot框架中,可以使用注解簡(jiǎn)單實(shí)現(xiàn)線程的操作,還有AsyncManager的方式,如果需要復(fù)雜的線程操作,可以使用線程池實(shí)現(xiàn),本文通過(guò)實(shí)例代碼介紹java開(kāi)啟異步線程的幾種方法(@Async,AsyncManager,線程池),感興趣的朋友一起看看吧2023-09-09SpringBoot整合Dubbo zookeeper過(guò)程解析
這篇文章主要介紹了SpringBoot整合Dubbo zookeeper過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02必須掌握的十個(gè)Lambda表達(dá)式簡(jiǎn)化代碼提高生產(chǎn)力
這篇文章主要為大家介紹了必須掌握的十個(gè)Lambda表達(dá)式來(lái)簡(jiǎn)化代碼提高生產(chǎn)力,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04