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