【MybatisPlus】基於@Version注解的樂觀鎖實現


最近項目有資金賬戶的相關需求,需要使用鎖做並發控制,借此機會整理下基於MybatisPlus @Version注解的樂觀鎖實現的方案,以及項目中遇到的坑

 

一.MybatisPlus 樂觀鎖的配置

  參考MybatisPlus(以下簡稱MP)官方文檔,https://baomidou.com/pages/0d93c0/#optimisticlockerinnerinterceptor

 MP已經提供了樂觀鎖插件,使用起來很方便,只要兩步即可完成配置

  MP樂觀鎖插件的配置

  1.配置樂觀鎖攔截器 OptimisticLockerInnerInterceptor,將攔截器Bean注入到到Spring容器中,MP官方提供了兩種方案,一種是XML配置,另外一種是使用@Bean注解注入,這里使用第二種方式即@Bean方式注入

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

  2.在實體類的字段上加上@Version注解

  例如:

@Version
private Integer version;

 

二.MybatisPlus 樂觀鎖的使用

  這里官方提供了兩種方式

  1.使用updateById(entity)

  2.使用update(entity,wrapper)

  最終打印出來的SQL語句,除了原有的更新語句之后,還會根據@Version修飾的字段version自動加上update table set version=? where version=?

  例如

UPDATE account_sub_info SET version=?,balance=? WHERE (acc_sub_id = ? AND acc_id = ? AND version = ?) 

 

三. 踩坑指南(重點)

  在使用過程中遇到了幾個點導致樂觀鎖更新失敗

  1.在使用的updateById(entity)或update(entity,wrapper)這兩個語句進行樂觀鎖控制時,參數entity的version屬性必須為null則更新失敗

    也就是說

    在新增時,我們需要為version字符賦默認值

    在更新時,我們可以在更新之前將數據先查一次,再使用上述方式根據查詢結果的對象實體更新

  2.同一個方法中,對同一條數據執行一次查詢,但是執行了兩次更新,則第二次更新會因為樂觀鎖更新失敗

  例如,

        LambdaQueryWrapper<AccountSubInfo> wrapper = Wrappers.<AccountSubInfo>lambdaQuery()
                .eq(AccountSubInfo::getAccSubId, 11094)
                .eq(AccountSubInfo::getAccId, 1248);
        AccountSubInfo accSubInfo = accountSubInfoService.getOne(wrapper,false);
        //第一次更新
        AccountSubInfo entity =new AccountSubInfo();
        entity.setAccSubId(accSubInfo.getAccSubId());
        entity.setAccId(accSubInfo.getAccId());
        entity.setDbId(accSubInfo.getDbId());
        entity.setBalance(accSubInfo.getBalance().add(new BigDecimal(10)));
        boolean result = accountSubInfoService.updateById(entity);
        //第二次更新
        entity.setBalance(accSubInfo.getBalance().add(new BigDecimal(10)));
        boolean result2 = accountSubInfoService.updateById(entity);

  原因是,在同一個方法中,第二次更新使用的實體對象AccountSubInfo的版本號仍然是更新前的版本號,所以導致更新失敗

  解決方案:

        //第一次更新
        AccountSubInfo entity =new AccountSubInfo();
        entity.setAccSubId(accSubInfo.getAccSubId());
        entity.setAccId(accSubInfo.getAccId());
        entity.setDbId(accSubInfo.getDbId());
        entity.setBalance(accSubInfo.getBalance().add(new BigDecimal(10)));
        boolean result = accountSubInfoService.updateById(entity);
        //第二次更新
        entity.setVersion(entity.getVersion()+1); //特殊處理,修改版本號的預期值
        entity.setBalance(accSubInfo.getBalance().add(new BigDecimal(10)));
        boolean result2 = accountSubInfoService.updateById(entity);

  3.分庫分表下的樂觀鎖實現

    sharding分庫分表后,使用updateById(entity) 或 update(entity,wrapper) 都需要傳入實體對象,

    這時會如果實體內包含了分片鍵,則會提示錯誤,不能更新分片鍵,如果沒有包含分片鍵,則會走到所有分表的全路由,性能很低

    因此為了解決這個問題,這里對樂觀鎖機制進行手動處理,不使用上述兩個updateById(entity) 或 update(entity,wrapper) 方法進行樂觀鎖控制

    參考:

        BigDecimal afterAmt = accSubInfo.getBalance().add(new BigDecimal(10));
        accSubInfo.setBalance(afterAmt);
        LambdaUpdateWrapper<AccountSubInfo> updateWrapper = Wrappers.<AccountSubInfo>lambdaUpdate()
                .eq(AccountSubInfo::getAccSubId, accSubInfo.getAccSubId()) //分片鍵字段
                .eq(AccountSubInfo::getAccId, accSubInfo.getAccId())
                .eq(AccountSubInfo::getVersion,accSubInfo.getVersion())    //版本號條件
                .set(AccountSubInfo::getVersion,accSubInfo.getVersion()+1) //設置版本號
                .set(AccountSubInfo::getBalance, afterAmt);
        boolean ret = accountSubInfoService.update(updateWrapper);

 

  

  

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM