解決@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。

推薦閱讀: