解決:MyBatis-plus多數據源方法上方添加事務,數據源切換失敗


說明:MyBatis-plus配置了多數據源,添加事務后,數據源切換失敗了...

一、場景描述

項目當中使用的多數據源,Impl中有個方法:MethodA。

@Service
@AllArgsConstructor
@DS("tableA")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {

    @Override
    public R<?> MethodA(XXXX xxxx) {
      
    }
}

該方法中同時操作了兩張表:tableA、tableB(tableA、tableB來自兩個數據源)。

@Service
@AllArgsConstructor
@DS("tableA")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {
 
  // ........
  
    @Override
    public R<?> MethodA(XXXX xxxx) {
      // 操作tableA的方法
      operate1(xxxx);
      // 操作tableB的方法
      operate2(xxxx);
    }
}

出於數據一致性考慮,樓主在MethodA上方加了 @Transactional(rollbackFor = Exception.class)

@Service
@AllArgsConstructor
@DS("tableA")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {
 
   // ........
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> MethodA(XXXX xxxx) {
      // 操作tableA的方法
      operate1(xxxx);
      // 操作tableB的方法
      operate2(xxxx);
    }
}

運行項目后發現,執行操作tableB的數據時,數據源並沒有切換。

在這里插入圖片描述

然而,去掉該事務注解后,數據源則切換正常。但不能保證數據一致性。這.....

二、相關疑問

1、為什么在該方法上方加@Transactional(rollbackFor = Exception.class)注解會導致切換數據源失敗?

因為在開啟事務的同時,會從數據庫連接池獲取數據庫連接。內層的service雖然使用了@DS切換數據源,但實質上並沒有改變整個事務的連接。而在事務內的所有數據庫操作,都是在事務連接建立之后進行的,所以會產生數據源沒有切換的問題。

2、如何保證數據源切換正常,事務使用也正常?

想要使內部調用切換@DS起作用,就必須替換數據庫連接,也就是改變事務的傳播機制,使其產生新的事務,獲取新的數據庫連接。

可通過外部方法上方加 @Transactional注解,內部方法上方加@Transactional(propagation = Propagation.REQUIRES_NEW)注解進行解決。

三、解決方法

@Transactional(propagation = Propagation.REQUIRES_NEW)

表示新建事務,如果當前存在事務,把當前事務掛起。

需要注意:添加了該注解的方法,需放在業務的最后方處理,確保掛起的事務方法均已執行成功,然后再去處理開啟新事務的方法。

因為該內部事務方法異常時,會造成外部事務回滾。但是外部事務異常並不會回滾該內部事務方法。

就拿我們示例來說,在MethodA上方加 @Transactional(rollbackFor = Exception.class)注解,在內部調用操作tableB的方法operate2();上方加@Transactional(propagation = Propagation.REQUIRES_NEW)注解。

@Service
@AllArgsConstructor
@DS("tableB")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {
 
 // ........

  @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Boolean operate2()(XXXX xxxx) {
        return save(xxxx);
    }
}

如果定義operate2()調用在前,operate1()調用在后。若operate1()方法異常,operate2()將不會回滾。

@Service
@AllArgsConstructor
@DS("tableA")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {
 
   // ........
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> MethodA(XXXX xxxx) {
    
      // 操作tableB的方法
      operate2(xxxx);
      // 操作tableA的方法
      operate1(xxxx);
    }
}

如果定義operate1()調用在前,operate2()調用在后。若有方法調用異常,則都會回滾。

@Service
@AllArgsConstructor
@DS("tableA")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {
 
   // ........
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> MethodA(XXXX xxxx) {
    
      // 操作tableA的方法
      operate1(xxxx);
      // 操作tableB的方法
      operate2(xxxx);
    }
}

因此需注意,配置了@Transactional(propagation = Propagation.REQUIRES_NEW)注解的方法調用,應放在業務最后方處理。


免責聲明!

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



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