解決@Transaction注解導致動態(tài)切換更改數(shù)據(jù)庫失效問題
@Transaction注解導致動態(tài)切換更改數(shù)據(jù)庫失效
使用場景
- 給所有的Controller方法上加切點
- 在@Before注解的方法里,根據(jù)http請求中攜帶的header,動態(tài)切換數(shù)據(jù)源
- 使用mybatis或者jpa執(zhí)行操作
遇到問題
當給Controller方法加上@Transaction注解后,動態(tài)切換數(shù)據(jù)源就失效了,原因是每次@Before注解的方法運行之前,protected abstract Object determineCurrentLookupKey();就已經(jīng)運行了,而這個方法是切換數(shù)據(jù)源的關(guān)鍵。
解決
其實也算不上解決,就是不要在Controller方法上加事務(wù)注解,非要加事務(wù),中間的Service層就不要省了。
@Transactional失效的場景及原理
1.@Transactional修飾的方法
為非public方法,這個時候@Transactional會實現(xiàn)。
失敗的原理是:@Transactional是基于動態(tài)代理來實現(xiàn)的,非public的方法,他@Transactional的動態(tài)代理對象信息為空,所以不能回滾。
2.在類內(nèi)部沒有添加@Transactional的方法
調(diào)用了@Transactional方法時,當你調(diào)用是,他也不會回滾
測試代碼如下
@Service
public class UserServiceImpl extends BaseServiceImpl<UserEntity> implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional
public void insertOne() {
UserEntity userEntity = new UserEntity();
userEntity.setUsername("Michael_C_2019");
//插入到數(shù)據(jù)庫
userMapper.insertSelective(userEntity);
//手動拋出異常
throw new IndexOutOfBoundsException();
}
@Override
public void saveOne() {
insertOne();
}
}
失敗的原理:@Transactional是基于動態(tài)代理對象來實現(xiàn)的,而在類內(nèi)部的方法的調(diào)用是通過this關(guān)鍵字來實現(xiàn)的,沒有經(jīng)過動態(tài)代理對象,所以事務(wù)回滾失效。
3.就是在@Transactional方法內(nèi)部捕獲了異常
沒有在catch代碼塊里面重新拋出異常,事務(wù)也不會回滾。
代碼如下:
@Override
@Transactional
public void insertOne() {
try {
UserEntity userEntity = new UserEntity();
userEntity.setUsername("Michael_C_2019");
//插入到數(shù)據(jù)庫
userMapper.insertSelective(userEntity);
//手動拋出異常
throw new IndexOutOfBoundsException();
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
所以在阿里巴巴的Java開發(fā)者手冊里面有明確規(guī)定,在 @Transactional的方法里面捕獲了異常,必須要手動回滾,
代碼如下:
@Override
@Transactional
public void insertOne() {
try {
UserEntity userEntity = new UserEntity();
userEntity.setUsername("Michael_C_2019");
//插入到數(shù)據(jù)庫
userMapper.insertSelective(userEntity);
//手動拋出異常
throw new IndexOutOfBoundsException();
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
失敗原理:這時候我們來看看spring的源碼:
TransactionAspectSupport類里面的invokeWithinTransaction方法
TransactionAspectSupport
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
TransactionAttributeSource tas = this.getTransactionAttributeSource();
TransactionAttribute txAttr = tas != null ? tas.getTransactionAttribute(method, targetClass) : null;
PlatformTransactionManager tm = this.determineTransactionManager(txAttr);
String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);
Object result;
if (txAttr != null && tm instanceof CallbackPreferringPlatformTransactionManager) {
TransactionAspectSupport.ThrowableHolder throwableHolder = new TransactionAspectSupport.ThrowableHolder(null);
try {
result = ((CallbackPreferringPlatformTransactionManager)tm).execute(txAttr, (status) -> {
TransactionAspectSupport.TransactionInfo txInfo = this.prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
Object var9;
try {
Object var8 = invocation.proceedWithInvocation();
return var8;
} catch (Throwable var13) {
if (txAttr.rollbackOn(var13)) {
if (var13 instanceof RuntimeException) {
throw (RuntimeException)var13;
}
throw new TransactionAspectSupport.ThrowableHolderException(var13);
}
throwableHolder.throwable = var13;
var9 = null;
} finally {
this.cleanupTransactionInfo(txInfo);
}
return var9;
});
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
} else {
return result;
}
} catch (TransactionAspectSupport.ThrowableHolderException var19) {
throw var19.getCause();
} catch (TransactionSystemException var20) {
if (throwableHolder.throwable != null) {
this.logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
var20.initApplicationException(throwableHolder.throwable);
}
throw var20;
} catch (Throwable var21) {
if (throwableHolder.throwable != null) {
this.logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
}
throw var21;
}
} else {
TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
result = null;
try {
result = invocation.proceedWithInvocation();
} catch (Throwable var17) {
//異常時,在catch邏輯中回滾事務(wù)
this.completeTransactionAfterThrowing(txInfo, var17);
throw var17;
} finally {
this.cleanupTransactionInfo(txInfo);
}
this.commitTransactionAfterReturning(txInfo);
return result;
}
}
他是通過捕獲異常然后在catch里面進行事務(wù)的回滾的,所以如果你在自己的方法里面catch了異常,catch里面沒有拋出新的異常,那么事務(wù)將不會回滾。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot實現(xiàn)跨域的幾種常用方式總結(jié)
跨域是指一個域下的文檔或腳本試圖去請求另一個域下的資源,或者涉及到兩個不同域名的資源之間的交互,由于同源策略(Same Origin Policy)的限制,瀏覽器不允許跨域請求,本文小編給大家分享了SpringBoot實現(xiàn)跨域的幾種常用方式,需要的朋友可以參考下2023-09-09
SpringCloudConfig之client端報錯Could?not?resolve?placeholder問
這篇文章主要介紹了SpringCloudConfig之client端報錯Could?not?resolve?placeholder?‘from‘?in?value?“${from}“問題及解決方案,具有很好的參考價值,希望對大家有所幫助2022-12-12
SpringBoot如何實現(xiàn)定時任務(wù)示例詳解
使用定時任務(wù)完成一些業(yè)務(wù)邏輯,比如天氣接口的數(shù)據(jù)獲取,定時發(fā)送短信,郵件。以及商城中每天用戶的限額,定時自動收貨等等,這篇文章主要給大家介紹了關(guān)于SpringBoot如何實現(xiàn)定時任務(wù)的相關(guān)資料,需要的朋友可以參考下2021-10-10
Idea配置maven-tomcat-plugin插件實現(xiàn)項目部署
今天小編就為大家分享一篇關(guān)于Idea配置maven-tomcat-plugin插件實現(xiàn)項目部署,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-02-02
SpringBoot利用注解來實現(xiàn)Redis分布式鎖
有些業(yè)務(wù)請求,屬于耗時操作,需要加鎖,防止后續(xù)的并發(fā)操作,同時對數(shù)據(jù)庫的數(shù)據(jù)進行操作,需要避免對之前的業(yè)務(wù)造成影響。本文將利用注解來實現(xiàn)Redis分布式鎖,需要的可以參考一下2022-09-09
java HttpClient傳輸json格式的參數(shù)實例講解
這篇文章主要介紹了java HttpClient傳輸json格式的參數(shù)實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01

