1. 程式人生 > 其它 >解決:MyBatis-plus多資料來源方法上方新增事務,資料來源切換失敗

解決: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)註解的方法呼叫,應放在業務最後方處理。