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<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<Set<TransactionSynchronization>>("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<TransactionSynchronization>());
}
回到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还原,最后关闭连接。
过程就是这样了,其实我想实验个想法,于是看了这些代码。以上。
org/springframework/transaction/interceptor/TransactionAspectSupport --> createTransactionIfNecessary -->
/org/springframework/transaction/support/AbstractPlatformTransactionManager --> status = tm.getTransaction(txAttr) --> doBegin
createTransactionIfNecessary --> {doCleanupAfterCompletion commitTransactionAfterReturning}
--> org/springframework/transaction/support/AbstractPlatformTransactionManager
--> commit --> processCommit --> {prepareForCommit, doCommit, triggerAfterCommit triggerAfterCompletion cleanupAfterCompletion}
doCommit --> DataSourceTransactionManager
cleanupAfterCompletion --> DataSourceTransactionManager --> doCleanupAfterCompletion
==========================================================
咱最近用的github:https://github.com/saaavsaaa
微信公众号:

Spring事务执行过程的更多相关文章
- 调试:Spring AOP执行过程
调试项目:https://github.com/1367356/laboratoryWeb 断点位置 点击查询:http://localhost:9002/queryNews?htmlid=15318 ...
- 深入理解 Spring 事务:入门、使用、原理
大家好,我是树哥. Spring 事务是复杂一致性业务必备的知识点,掌握好 Spring 事务可以让我们写出更好地代码.这篇文章我们将介绍 Spring 事务的诞生背景,从而让我们可以更清晰地了解 S ...
- (4.1)Spring MVC执行原理和基于Java的配置过程
一.Spring MVC执行原理和基于Java配置的配置过程 (一)Spring MVC执行过程,大致为7步. 所有的请求都会经过Spring的一个单例的DispacherServlet. Dispa ...
- Spring MVC执行原理和基于Java的配置过程
一.Spring MVC执行原理和基于Java配置的配置过程 (一)Spring MVC执行过程,大致为7步. 所有的请求都会经过Spring的一个单例的DispacherServlet. Dispa ...
- spring事务详解(一)初探事务
系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 引子 很多 ...
- spring事务详解(一)初探讨
一.什么是事务 维基百科:数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成.理解:事务(Transaction)是数据库区别于文件系统的重要特性之一.传 ...
- Spring事务用法示例与实现原理
关于Java中的事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性.对于Spring事务,其最终还是在数据库层面实现的,而Spring只是以一种比 ...
- 2018.11.12 Spring事务的实现和原理
Spring事务用法示例与实现原理 关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性.一致性.隔离性和持久性.对于Spring事务,其最终还是在数据库层面实现的,而 ...
- Spring系列(六):Spring事务源码解析
一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...
随机推荐
- Strtus2 S2-045漏洞
S2-045漏洞已经爆发几天了,但还很多网址都存在此漏洞,很多金融类网站也收到了保护费通知...唉,收保护费少,报警无效!只能酌情处理了!做黑产的,还是少做为秒,常在河边站哪有不湿鞋,劝各位早日金盆洗 ...
- KoaHub平台基于Node.js开发的Koa router路由插件代码信息详情
koa-router Router middleware for koa. Provides RESTful resource routing. koa-router Router mid ...
- 《Django By Example》第九章 中文 翻译 (个人学习,渣翻)
书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者@ucag 注:哈哈哈,第九章终于来啦 ...
- SQL注入相关的知识【Mysql为例子】
以DVWA的sql注入初级为例,结合网上搜索的相关利用方式,总结成这一篇文章,内容会有点跳跃. 大纲: 1.初级手工注入 2.order by的使用 3.union查询注意点 4.Mysql相关的注释 ...
- Exchange无法发送邮件 未找到匹配的连接器来路由外部收件人解决办法
使用命令行管理程序创建发送连接器 本示例创建发送连接器,用于集线器传输服务器 HubA 向 Internet 发送电子邮件. 复制 New-SendConnector -Name "In ...
- RAC OCR盘故障导致的集群重启恢复
一.事故说明 最近出现了一次OCR盘的故障导致Oracle集群件宕机的事故,后以独占模式启动集群,并使用ocr备份恢复了OCR文件以及重新设置了vote disk,然后关闭集群,重启成功. 因此在此处 ...
- 关于连接数据库的T-SQL语句中的一种小技巧
(编程生活中,我们经常会用到数据库.然后在通过T-SQL语句来对数据库进行操作的时候,遇到很多麻烦.话说昨天我就被困扰了一天.明明这个T-sql插数据的语句放在数据库运行的时候没有问题,到了java代 ...
- 一次安装rpcbind失败引发的思考
问题: yum install rpcbind -y 出现如下错误: Error -.el6.x86_64 error: %pre(rpcbind--.el6.x86_64) scriptlet fa ...
- 记一次搭建SS服务器,完整的过程。
前言 这几天为了登录外网查询资料,谷歌经常抽风,国外的博客也打不开,都要整崩溃了.我以前(直到昨天以前)都是使用修改hosts来访问外网的,用的是 老D(https://laod.cn/hosts) ...
- win8效果的横向布局
有一个月没写过博客了,自己的博客也没有看过,前段时间一直在忙着写代码,公司有一个制漆的产品,与传统纵向布局不一样,要求页面横向布局,类似win8的那种布局效果,最开始,我也没有什么头绪,然后硬着头皮做 ...