說明: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)注解的方法調用,應放在業務最后方處理。