解決@Transaction註解導致動態切換更改數據庫失效問題
@Transaction註解導致動態切換更改數據庫失效
使用場景
- 給所有的Controller方法上加切點
- 在@Before註解的方法裡,根據http請求中攜帶的header,動態切換數據源
- 使用mybatis或者jpa執行操作
遇到問題
當給Controller方法加上@Transaction註解後,動態切換數據源就失效瞭,原因是每次@Before註解的方法運行之前,protected abstract Object determineCurrentLookupKey();就已經運行瞭,而這個方法是切換數據源的關鍵。
解決
其實也算不上解決,就是不要在Controller方法上加事務註解,非要加事務,中間的Service層就不要省瞭。
@Transactional失效的場景及原理
1.@Transactional修飾的方法
為非public方法,這個時候@Transactional會實現。
失敗的原理是:@Transactional是基於動態代理來實現的,非public的方法,他@Transactional的動態代理對象信息為空,所以不能回滾。
2.在類內部沒有添加@Transactional的方法
調用瞭@Transactional方法時,當你調用是,他也不會回滾
測試代碼如下
@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"); //插入到數據庫 userMapper.insertSelective(userEntity); //手動拋出異常 throw new IndexOutOfBoundsException(); } @Override public void saveOne() { insertOne(); } }
失敗的原理:@Transactional是基於動態代理對象來實現的,而在類內部的方法的調用是通過this關鍵字來實現的,沒有經過動態代理對象,所以事務回滾失效。
3.就是在@Transactional方法內部捕獲瞭異常
沒有在catch代碼塊裡面重新拋出異常,事務也不會回滾。
代碼如下:
@Override @Transactional public void insertOne() { try { UserEntity userEntity = new UserEntity(); userEntity.setUsername("Michael_C_2019"); //插入到數據庫 userMapper.insertSelective(userEntity); //手動拋出異常 throw new IndexOutOfBoundsException(); } catch (IndexOutOfBoundsException e) { e.printStackTrace(); } }
所以在阿裡巴巴的Java開發者手冊裡面有明確規定,在 @Transactional的方法裡面捕獲瞭異常,必須要手動回滾,
代碼如下:
@Override @Transactional public void insertOne() { try { UserEntity userEntity = new UserEntity(); userEntity.setUsername("Michael_C_2019"); //插入到數據庫 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邏輯中回滾事務 this.completeTransactionAfterThrowing(txInfo, var17); throw var17; } finally { this.cleanupTransactionInfo(txInfo); } this.commitTransactionAfterReturning(txInfo); return result; } }
他是通過捕獲異常然後在catch裡面進行事務的回滾的,所以如果你在自己的方法裡面catch瞭異常,catch裡面沒有拋出新的異常,那麼事務將不會回滾。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 五分鐘教你手寫 SpringBoot 本地事務管理實現
- springBoot service層事務控制的操作
- mybatis 事務回滾配置操作
- Spring詳細講解事務失效的場景
- Java @Transactional指定回滾條件