解決@Transaction注解導(dǎo)致動(dòng)態(tài)切換更改數(shù)據(jù)庫(kù)失效問題
@Transaction注解導(dǎo)致動(dòng)態(tài)切換更改數(shù)據(jù)庫(kù)失效
使用場(chǎng)景
- 給所有的Controller方法上加切點(diǎn)
- 在@Before注解的方法里,根據(jù)http請(qǐng)求中攜帶的header,動(dòng)態(tài)切換數(shù)據(jù)源
- 使用mybatis或者jpa執(zhí)行操作
遇到問題
當(dāng)給Controller方法加上@Transaction注解后,動(dòng)態(tài)切換數(shù)據(jù)源就失效了,原因是每次@Before注解的方法運(yùn)行之前,protected abstract Object determineCurrentLookupKey();就已經(jīng)運(yùn)行了,而這個(gè)方法是切換數(shù)據(jù)源的關(guān)鍵。
解決
其實(shí)也算不上解決,就是不要在Controller方法上加事務(wù)注解,非要加事務(wù),中間的Service層就不要省了。
@Transactional失效的場(chǎng)景及原理
1.@Transactional修飾的方法
為非public方法,這個(gè)時(shí)候@Transactional會(huì)實(shí)現(xiàn)。
失敗的原理是:@Transactional是基于動(dòng)態(tài)代理來實(shí)現(xiàn)的,非public的方法,他@Transactional的動(dòng)態(tài)代理對(duì)象信息為空,所以不能回滾。
2.在類內(nèi)部沒有添加@Transactional的方法
調(diào)用了@Transactional方法時(shí),當(dāng)你調(diào)用是,他也不會(huì)回滾
測(cè)試代碼如下
@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ù)庫(kù) userMapper.insertSelective(userEntity); //手動(dòng)拋出異常 throw new IndexOutOfBoundsException(); } @Override public void saveOne() { insertOne(); } }
失敗的原理:@Transactional是基于動(dòng)態(tài)代理對(duì)象來實(shí)現(xiàn)的,而在類內(nèi)部的方法的調(diào)用是通過this關(guān)鍵字來實(shí)現(xiàn)的,沒有經(jīng)過動(dòng)態(tài)代理對(duì)象,所以事務(wù)回滾失效。
3.就是在@Transactional方法內(nèi)部捕獲了異常
沒有在catch代碼塊里面重新拋出異常,事務(wù)也不會(huì)回滾。
代碼如下:
@Override @Transactional public void insertOne() { try { UserEntity userEntity = new UserEntity(); userEntity.setUsername("Michael_C_2019"); //插入到數(shù)據(jù)庫(kù) userMapper.insertSelective(userEntity); //手動(dòng)拋出異常 throw new IndexOutOfBoundsException(); } catch (IndexOutOfBoundsException e) { e.printStackTrace(); } }
所以在阿里巴巴的Java開發(fā)者手冊(cè)里面有明確規(guī)定,在 @Transactional的方法里面捕獲了異常,必須要手動(dòng)回滾,
代碼如下:
@Override @Transactional public void insertOne() { try { UserEntity userEntity = new UserEntity(); userEntity.setUsername("Michael_C_2019"); //插入到數(shù)據(jù)庫(kù) userMapper.insertSelective(userEntity); //手動(dòng)拋出異常 throw new IndexOutOfBoundsException(); } catch (IndexOutOfBoundsException e) { e.printStackTrace(); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
失敗原理:這時(shí)候我們來看看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) { //異常時(shí),在catch邏輯中回滾事務(wù) this.completeTransactionAfterThrowing(txInfo, var17); throw var17; } finally { this.cleanupTransactionInfo(txInfo); } this.commitTransactionAfterReturning(txInfo); return result; } }
他是通過捕獲異常然后在catch里面進(jìn)行事務(wù)的回滾的,所以如果你在自己的方法里面catch了異常,catch里面沒有拋出新的異常,那么事務(wù)將不會(huì)回滾。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中StringBuilder與StringBuffer的區(qū)別
在Java編程中,字符串的拼接是一項(xiàng)常見的操作。為了有效地處理字符串的拼接需求,Java提供了兩個(gè)主要的類:StringBuilder和StringBuffer,本文主要介紹了Java中StringBuilder與StringBuffer的區(qū)別,感興趣的可以了解一下2023-08-08一篇文章帶你了解jdk1.8新特性--為什么使用lambda表達(dá)式
Lambda是一個(gè)匿名函數(shù),我們可以把Lambda表達(dá)式理解為是一段可以傳遞的代碼,本篇文章就帶你了解,希望能給你帶來幫助2021-08-08Java定時(shí)器例子_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
本文給大家分享了java定時(shí)器例子,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-05-05Springboot啟用多個(gè)監(jiān)聽端口代碼實(shí)例
這篇文章主要介紹了Springboot啟用多個(gè)監(jiān)聽端口代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Java靜態(tài)代理與動(dòng)態(tài)代理案例詳解
這篇文章主要介紹了Java靜態(tài)代理與動(dòng)態(tài)代理案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07SpringBoot快速搭建web項(xiàng)目詳細(xì)步驟總結(jié)
這篇文章主要介紹了SpringBoot快速搭建web項(xiàng)目詳細(xì)步驟總結(jié) ,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12