Spring事务执行过程

网友投稿 283 2022-10-26


Spring事务执行过程

先说一下启动过程中的几个点:

AbstractAutowireCapableBeanFactory.doCreateBean --> initializeBean --> applyBeanPostProcessorsAfterInitialization --> beanProcessor.postProcessAfterInitialization --> AbstractAutoProxyCreator.postProcessAfterInitialization --> wrapIfNecessary(bean, beanName, cacheKey) --> getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null)Advisor是Pointcut和Advice的配置器,它包括Pointcut和Advice,是将Advice注入程序中Pointcut位置的代码;AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary:调用txAdvice上图的事务通知设置数据源 DataSourceTransactionManager,ChooseDataSource是项目中配置的自定义的继承至AbstractRoutingDataSource的默认数据源,命名什么的:

启动结束后,发起事务调用,首先拦截方法起CglibAopProxy.intercept --> ReflectiveMethodInvocation.proceed --> ExposeInvocationInterceptor.invoke --> TransactionInterceptor.invoke:

public Object invoke(final MethodInvocation invocation) throws Throwable {        // Work out the target class: may be {@code null}.        // The TransactionAttributeSource should be passed the target class        // as well as the method, which may be from an interface.         Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);        // Adapt to TransactionAspectSupport's invokeWithinTransaction...         return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {             @Override            public Object proceedWithInvocation() throws Throwable {                return invocation.proceed();             }         });     }

TransactionAspectSupport.invokeWithinTransaction :

determineTransactionManager方法先判断当前事务有无配置特定事务管理器,如果没有判断是否设置过默认的事务管理器,都没有的情况下:

public PlatformTransactionManager getTransactionManager() {        return this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);     }

接下来判断加载的事务属性是否存在或者当前事务管理器是否不是CallbackPreferringPlatformTransactionManager,符合条件会执行到createTransactionIfNecessary,先是:

if (txAttr != null && txAttr.getName() == null) {             txAttr = new DelegatingTransactionAttribute(txAttr) {                 @Override                public String getName() {                    return joinpointIdentification;                 }             };         }

DelegatingTransactionAttribute本身除了被实现后使用没有其他作用。然后从事务管理器中取出事务,doGetTransaction从之前set的数据源取出连接set给DataSourceTransactionObject:

protected Object doGetTransaction() {         DataSourceTransactionObject txObject = new DataSourceTransactionObject();         txObject.setSavepointAllowed(isNestedTransactionAllowed());         ConnectionHolder conHolder =                 (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);         txObject.setConnectionHolder(conHolder, false);        return txObject;     }

然后判断事务是否已存在,如果存在例如嵌套事务,会根据定义的传播方式进行处理,具体处理后面会说,这里是还不存在。然后验证了一下事务是否超时。由于从事务定义(TransactionDefinition持有隔离级别等事务属性的对象)中取出的事务传播方式我这里是默认的 PROPAGATION_REQUIRED,第一句用于挂起事务的什么也没做,然后或许事务同步为激活同步,接着就到启动事务了DataSourceTransactionManager doBegin:

if (txObject.getConnectionHolder() == null ||                     txObject.getConnectionHolder().isSynchronizedWithTransaction()) {                 Connection newCon = this.dataSource.getConnection();                if (logger.isDebugEnabled()) {                     logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");                 }                 txObject.setConnectionHolder(new ConnectionHolder(newCon), true);             }

先从数据源中getConnection,set给DataSourceTransactionObject,设置为同步事务,prepareConnectionForTransaction根据配置确定事务是否只读,被嵌套的事务中前一个的隔离级别txObject.setPreviousIsolationLevel(previousIsolationLevel):

判断是否已设置为自动提交,如果是设置则设置事务对象为自动还原为自动提交,有点拗口,意思大概是当事务复用数据库连接时第一个事务提交后,同一个连接的下一个事务还是设置为自动提交,否则当前事务如果被设为手痛提交,因为连接池中的连接会被复用,在同一个连接上的后续事务可能需要手动调用conn.commit才能提交下一个事务,设置connection holder代表的连接的事务是活动的;检查超时;判断当前事务的连接是否是新创建的,是则注册给TransactionSynchronizationManager,通过ThreadLocal将线程和事务绑定;prepareSynchronization设置这个事务同步管理器是否包含实际执行的事务,当前线程事务隔离级别、是否只读以及事务定义名,初始化同步事务(private static final ThreadLocal> synchronizations = new NamedThreadLocal>("Transaction synchronizations")):

public static void initSynchronization() throws IllegalStateException {         if (isSynchronizationActive()) {             throw new IllegalStateException("Cannot activate transaction synchronization - already active");         }         logger.trace("Initializing transaction synchronization");         synchronizations.set(new LinkedHashSet());     }

回到TransactionAspectSupport,prepareTransactionInfo方法创建事务信息,并通过ThreadLocal绑定给当前线程:

TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);

private void bindToThread() {            // Expose current TransactionStatus, preserving any existing TransactionStatus            // for restoration after this transaction is complete.             this.oldTransactionInfo = transactionInfoHolder.get();             transactionInfoHolder.set(this);         }

绑定中保留了当前事务状态,应该用来在嵌套事务中内层事务完成后恢复外层事务的现场。

createTransactionIfNecessary结束,回到TransactionAspectSupport的invokeWithinTransaction方法,接下来是invocation.proceedWithInvocation,这个方法上面贴了,new InvocationCallback时实现了这个方法,代码只有一句调用了MethodInvocation的proceed,也就是环绕通知调用被切方法的方法(也有可能调用其他Interceptor的切面方法),我这直接调用了被切的方法,然而并没有直接走到后面的completeTransactionAfterThrowing或commitTransactionAfterReturning,也没有到清理事务信息的方法,因为有嵌套事务,于是被嵌套的切了,与上面过程相同之处就不说了,说说不同的。

getTransaction这次因为已经存在事务,使用同一个连接,JdbcTransactionObjectSupport实例保存了connectionHolder,所以这次走进了这个判断分支:

if (isExistingTransaction(transaction)) {            // Existing transaction found -> check propagation behavior to find out how to behave.             return handleExistingTransaction(definition, transaction, debugEnabled);         }

handleExistingTransaction方法内根据不同的事务传播方式走不同的代码分支,我这就是默认REQUIRED使用现有事务,所以这个方法基本和没走也差不多。嵌套的都完了就走到cleanupTransactionInfo方法了,这方法实际调用了TransactionInfo.restoreThreadLocalStatus,实际上还原了之前的事务信息:

private void restoreThreadLocalStatus() {            // Use stack to restore old transaction TransactionInfo.            // Will be null if none was set.             transactionInfoHolder.set(this.oldTransactionInfo);         }

然后是commitTransactionAfterReturning:

/**      * Execute after successful completion of call, but not after an exception was handled.      * Do nothing if we didn't create a transaction.      * @param txInfo information about the current transaction     */     protected void commitTransactionAfterReturning(TransactionInfo txInfo) {        if (txInfo != null && txInfo.hasTransaction()) {            if (logger.isTraceEnabled()) {                 logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");             }             txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());         }     }

commit方法在AbstractPlatformTransactionManager中,先提交的是内层的事务,这里需要提的,嵌套事务的子事务报错但没有抛给外层事务,可能会出现rollback-only的问题,defStatus.isLocalRollbackOnly()就是判断是否有内层事务出错设置rollbackOnly为true了,另外,关于全局事务,似乎说的是用的两段式XA?,不过目前用不上,只是一个连接对数据库,这里可以考虑下。该到具体处理提交的方法processCommit了。同样在AbstractPlatformTransactionManager中。prepareForCommit方法是空的,protected应该是准备给子类重写的,或者这就是我要找的。savepoint没有,由于是内层事务,triggerBeforeCommit、triggerBeforeCompletion、triggerAfterCommit和triggerAfterCommit方法没有执行,设置事务状态后,这个内层事务就提交完了。

ExposeInvocationInterceptor(可以暴露出拦截器链,一般用不到它,用到时应该在链首)还原外层被拦截方法的执行:

@Override    public Object invoke(MethodInvocation mi) throws Throwable {         MethodInvocation oldInvocation = invocation.get();         invocation.set(mi);        try {            return mi.proceed();         }        finally {             invocation.set(oldInvocation);         }     }

这个外层就是实际被拦截的方法,会通过CglibAopProxy执行。

再来就是提交外层事务了,cleanupTransactionInfo的old这次是null了,一样的流程就不说了,由于外层事务是创建了同步对象所以triggerBeforeCommit执行了:

TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly());

public static void triggerBeforeCommit(boolean readOnly) {        for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {             synchronization.beforeCommit(readOnly);         }     }

这里的beforeCommit由于我用的是mybatis,所以如果执行的话,会执行到SqlSessionUtils.beforeCommit,然而由于SqlSessionUtils是不能被继承的,所以这里不太好动手脚,只能在这个类的外层想办法。triggerBeforeCompletion是类似的。外层事务会有获取status.isGlobalRollbackOnly()用于doCommit(status)之后是否报错,注意,就是说其实并不会打断提交的执行。doCommit(status):

@Override    protected void doCommit(DefaultTransactionStatus status) {         DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();         Connection con = txObject.getConnectionHolder().getConnection();        if (status.isDebug()) {             logger.debug("Committing JDBC transaction on Connection [" + con + "]");         }        try {             con.commit();         }        catch (SQLException ex) {            throw new TransactionSystemException("Could not commit JDBC transaction", ex);         }     }

没错,我们用的是druid,这里不需要解释了。之后就是一些完成回调,各种解绑、clear、reset了,之前设置的必须还原为自动提交会在doCleanupAfterCompletion还原,最后关闭连接。

过程就是这样了,其实我想实验个想法,于是看了这些代码。以上。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:使用CSharp编写Google Protobuf插件
下一篇:mybatis 为什么千万不要使用 where 1=1
相关文章

 发表评论

暂时没有评论,来抢沙发吧~