Spring 事务原理总结五

很抱歉,Spring事务本来应该在上一篇就结束的,但因为梳理过程中发现了更多的未知知识,所以便再啰嗦几篇。本篇主要针对前一篇文章——《Spring 事务原理总结四》——末尾提到的几个问题进行梳理,这里再回顾一下这几个问题:

  1. 本篇文章中我们梳理了完整的流程,但是还有一个地方梳理的不够完整,即调用PlatformTransactionManager对象的getTransaction()方法(该方法需要一个TransactionAttribute对象,具体逻辑可以参考JdbcTransactionManager类的父类AbstractPlatformTransactionManager中的源码)获取TransactionStatus对象这个地方
  2. 为什么这里没看到conn.setAutoCommit(false)?
  3. Spring事务异常回滚的执行流程是什么?
  4. Spring事务失效的场景有那些?

通过前一篇文章,我们可以很确定Spring事务是通过代理方式实现的,其最终的处理类为TransactionInterceptor,而这个类中的invoke(MethodInvocation)方法是执行事务的起点,这个方法又继续调用了本类(TransactionInterceptor)的父类——TransactionAspectSupport——中的invokeWithinTransaction(Method, Class<?>, InvocationCallback)方法,这个方法是实现事务控制逻辑的核心,继续跟踪会看到上节提到的创建TransactionInfo对象的代码,即:

TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

之后我们继续进入该方法(其位于TransactionAspectSupport类中),这个方法内部有一段创建TransactionStatus对象的逻辑:

TransactionStatus status = null;
if (txAttr != null) {if (tm != null) {status = tm.getTransaction(txAttr);}else {if (logger.isDebugEnabled()) {logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +"] because no transaction manager has been configured");}}
}

从此处,我们进入到AbstractPlatformTransactionManager#getTransaction(TransactionDefinition)方法内部(注意:AbstractPlatformTransactionManager抽象类实现了PlatformTransactionManager接口,如果想了解其继承结构,可以看一下《Spring 事务原理总结三》这篇文章;还有上篇博客《Spring 事务原理总结四》中说这个方法接收的参数的类型为TransactionAttribute,这里想纠正一下,实际类型为TransactionDefinition),这里我们再贴一下该方法的源码:

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException {// Use defaults if no transaction definition given.TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());Object transaction = doGetTransaction();boolean debugEnabled = logger.isDebugEnabled();if (isExistingTransaction(transaction)) {// Existing transaction found -> check propagation behavior to find out how to behave.return handleExistingTransaction(def, transaction, debugEnabled);}// Check definition settings for new transaction.if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());}// No existing transaction found -> check propagation behavior to find out how to proceed.if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");}else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {SuspendedResourcesHolder suspendedResources = suspend(null);if (debugEnabled) {logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);}try {return startTransaction(def, transaction, false, debugEnabled, suspendedResources);}catch (RuntimeException | Error ex) {resume(null, suspendedResources);throw ex;}}else {// Create "empty" transaction: no actual transaction, but potentially synchronization.if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {logger.warn("Custom isolation level specified but no actual transaction initiated; " +"isolation level will effectively be ignored: " + def);}boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);}
}

下面让我们一起来梳理一下这个方法的具体处理逻辑吧!首先让我们一起来看一下下面这幅图:

从图中不难发现,这个方法会首先获得一个TransactionDefinition对象(如果传递进来的对象为空则会返回一个StaticTransactionDefinition类型的TransactionDefinition对象),接下来会调用DataSourceTransactionManager#doGetTransaction(),创建一个DataSourceTransactionObject类型的对象(该类是DataSourceTransactionManager中定义的一个静态内部类),关于该类的继承结构如下图所示:

下面看一下doGetTransaction()方法的源码,具体如下所示:

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

下面再来看一下ConnectionHolder这个类,它位于org.springframework.jdbc.datasource中,其继承结构如下所示:

总之,结合doGetTransaction()方法源码,我们可以清楚看到,其主要作用就是创建一个DataSourceTransactionObject对象。回到AbstractPlatformTransactionManager中的getTransaction()方法中,继续向下走,见下图:

下面让我们一起看一下DataSourceTransactionManager类中的isExistingTransaction()方法的源码,具体如下所示:

@Override
protected boolean isExistingTransaction(Object transaction) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}

这个方法的主要作用就是判断当前是否存在存活的事务,如果存在事务,则处理之,否则继续。接着就是判断事务是否超时,如果超时,直接抛出超时异常,否则继续。之后就是对事务传播行为的判断,首先就是判断当前的事务传播行为是否为PROPAGATION_MANDATORY,根据《Spring 事务原理总结一》这篇文章的介绍,这个事务传播属性的作用是如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。所以这里有这样一段代码:

if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
}

接着判断当前的事务传播行为是否是PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED,根据《Spring 事务原理总结一》这篇文章的介绍,它们三个的作用分别为:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务创建一个新的事务,如果当前存在事务,则把当前事务挂起如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。这个判断逻辑的详细代码如下所示:

else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {SuspendedResourcesHolder suspendedResources = suspend(null);if (debugEnabled) {logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);}try {return startTransaction(def, transaction, false, debugEnabled, suspendedResources);}catch (RuntimeException | Error ex) {resume(null, suspendedResources);throw ex;}
}

由于我们使用的是默认配置,即PROPAGATION_REQUIRED,所以我们跟踪的案例会走到这段分支。这个分支中的suspend(null)表示暂停某某某(关于这部分暂时不做解释)。先来看下面这幅图:

接着看一下startTransaction(TransactionDefinition definition, Object transaction, boolean nested, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources)这个方法(注意这个方法位于AbstractPlatformTransactionManager类中)的源码(根据该方法的方法名可知该方法的主要作用是开始一个新事务,这是个人理解,后续会根据理解的深入而进行变更),具体如下所示:

private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,boolean nested, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, nested, debugEnabled, suspendedResources);this.transactionExecutionListeners.forEach(listener -> listener.beforeBegin(status));try {doBegin(transaction, definition);}catch (RuntimeException | Error ex) {this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, ex));throw ex;}prepareSynchronization(status, definition);this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, null));return status;
}

这段逻辑中我们主要看一下DataSourceTransactionManager#doBegin(Object transaction, TransactionDefinition definition)方法,其源码如下所示:

protected void doBegin(Object transaction, TransactionDefinition definition) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;Connection con = null;try {if (!txObject.hasConnectionHolder() ||txObject.getConnectionHolder().isSynchronizedWithTransaction()) {Connection newCon = obtainDataSource().getConnection();if (logger.isDebugEnabled()) {logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");}txObject.setConnectionHolder(new ConnectionHolder(newCon), true);}txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con = txObject.getConnectionHolder().getConnection();Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);txObject.setReadOnly(definition.isReadOnly());// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,// so we don't want to do it unnecessarily (for example if we've explicitly// configured the connection pool to set it already).if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);if (logger.isDebugEnabled()) {logger.debug("Switching JDBC Connection [" + con + "] to manual commit");}con.setAutoCommit(false);}prepareTransactionalConnection(con, definition);txObject.getConnectionHolder().setTransactionActive(true);int timeout = determineTimeout(definition);if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}// Bind the connection holder to the thread.if (txObject.isNewConnectionHolder()) {TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());}}catch (Throwable ex) {if (txObject.isNewConnectionHolder()) {DataSourceUtils.releaseConnection(con, obtainDataSource());txObject.setConnectionHolder(null, false);}throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);}
}

注意这段逻辑中有这样一句代码:con.setAutoCommit(false)。这就解释了上篇文章末尾和本篇文章开头提到的问题:为什么这里没看到conn.setAutoCommit(false)。这个逻辑写在了DataSourceTransactionManager类的doBegin()方法中。关于这个方法中的其他细节,这里不过多解释,后期如果理解透彻了,我会继续添加。最后让我们一起回到AbstractPlatformTransactionManager的getTransaction(@Nullable TransactionDefinition definition)方法中,然后继续返回一直返回到最初的调用者,即TransactionAspectSupport的createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification)方法中,然后继续向上返回,直到TransactionAspectSupport类的invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation)方法中。这样我们就创建了一个TransactionInfo对象,然后就可以按照前面一章——《Spring 事务原理总结四》——描述的流程继续向下走了。

至此我们解决了昨天遗留问题中的两个,即本篇篇首所列的问题一和问题二。那其他两个问题呢?首先第三个问题个人觉得是流程梳理性质的问题,所以本篇不再过分着墨,我会在新篇章中对这个问题进行详尽的梳理。接着第四个问题属于面试性质的考题,可以在本篇文章中进行梳理。因此接下来我将对第四个问题进行梳理,如果有不对的地方,还请大家多多指教,谢谢!常见的造成事务失效的场景有以下几种

  1. 服务未托管给Spring:如果带有@Transactional注解的方法所在的类没有被Spring容器管理,即该类实例不是通过Spring IOC容器创建的,那么Spring将无法对这个方法应用事务管理。
  2. 受检异常处理不当Spring默认只回滚运行时异常(继承自RuntimeException的异常)和声明在@Transactional(rollbackFor=...)中的异常类型。若抛出了检查型异常(非运行时异常),而没有在注解中明确配置为回滚,则事务可能不会回滚。
  3. 异常捕获但未传播:当业务逻辑中捕获了异常并自行处理,而没有再次抛出或重新抛出到Spring事务代理可以感知的地方,事务将无法正确执行回滚操作。
  4. 切面顺序问题由于AOP代理的特性,如果在同一个类内部方法之间进行相互调用,并且调用的是未经过代理的对象上的事务方法,事务将不会生效。解决办法是通过注入服务的方式间接调用,确保经过代理。
  5. 非public方法Spring AOP代理默认仅作用于public方法上,如果将带有@Transactional注解的方法设置为protected、private或default访问权限,事务将不会生效
  6. 自调用事务方法在一个类内部,一个@Transactional方法直接调用本类内的另一个@Transactional方法,由于绕过了Spring AOP代理,会导致事务失效
  7. 异步调用场景:在Spring事务环境下,如果在事务方法中启动新的线程或者使用消息队列等异步机制调用事务性方法,由于线程切换,原事务上下文将不会被传递,导致新线程中的事务失效。
  8. 注解配置错误:如@Transactional注解没有正确配置在类或方法上,或者传播行为设置不当,可能导致事务失效。
  9. 未满足开启事务的条件:Spring事务需要由动态代理对象来调用带有@Transactional注解的方法,如果不是由Spring生成的代理对象调用,事务管理将不起作用。

好了,今天就这样吧,虽然没能兑现自己的诺言,但总算弄懂了几个问题(剩余的那个问题我将在下一篇博文中重点介绍)。回头看看那遍布荆棘的来路,不知道自己是怎么一步一步走来的,抬头望着前方那泥泞不堪的陡峭绝路,我的内心毫无波澜,不是什么都不会导致的麻木不仁,更不是破罐子破摔的自弃心理作祟,只是我知道急躁会使事情更加糟糕。所以与其手忙脚乱,不如一步一个脚印的慢慢前行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/469149.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaWeb学习|i18n

学习材料声明 所有知识点都来自互联网&#xff0c;进行总结和梳理&#xff0c;侵权必删。 引用来源&#xff1a;尚硅谷最新版JavaWeb全套教程,java web零基础入门完整版 i18n 国际化&#xff08;Internationalization&#xff09;指的是同一个网站可以支持多种不同的语言&…

数据库从入门到精通(一)数据库基础操作

mysql数据库基础操作 cmd下启动mysql数据库操作命令数据库重要的删除操作数据库增删改查操作插入数据更新数据删除数据查询数据查询指定记录in查询满足指定范围之内的条件记录not in查询不在指定范围之内的条件记录带between and 的范围查询带like的字符匹配查询(d%以d开头,%d以…

如何使用idea连通服务器上的Redis(详细版本)

这里我使用的是阿里云的服务器 打开阿里云的安全组&#xff0c;设置端口为6379 在redis.conf文件中&#xff0c;注释bind 127.0.0.1 将protected-mode设置为no&#xff0c;即关闭保护模式 更改服务器中的防火墙&#xff0c;放行6379端口 # 放行端口 firewall-cmd --zo…

【lesson53】线程控制

文章目录 线程控制 线程控制 线程创建 代码&#xff1a; 运行代码&#xff1a; 强调一点&#xff0c;线程和进程不一样&#xff0c;进程有父进程的概念&#xff0c;但在线程组里面&#xff0c;所有的线程都是对等关系。 错误检查: 传统的一些函数是&#xff0c;成功返回0&…

如何用 ChatGPT 做项目管理?

ChatGPT 可以通过创建和维护跨团队项目协作计划&#xff0c;让员工更容易理解他们的角色和职责。 这个协作计划里面会包括每个团队或个人要执行的具体任务&#xff0c;每个任务最后期限和任何事情之 间的依赖关系。 该场景对应的关键词库:(24 个) 项目管理、项目协作计划、跨…

CSRNET图像修复,DNN

CSRNET图像修复 CSRNET图像修复&#xff0c;只需要OPENCV的DNN

cool Node后端 中实现中间件的书写

1.需求 在node后端中&#xff0c;想实现一个专门鉴权的文件配置&#xff0c;可以这样来解释 就是 有些接口需要token调用接口&#xff0c;有些接口不需要使用token 调用 这期来详细说明一下 什么是中间件中间件顾名思义是指在请求和响应中间,进行请求数据的拦截处理&#xf…

2024幻兽帕鲁服务器创建教程_阿里PK腾讯超简单

幻兽帕鲁官方服务器不稳定&#xff1f;自己搭建幻兽帕鲁服务器&#xff0c;低延迟、稳定不卡&#xff0c;目前阿里云和腾讯云均推出幻兽帕鲁专用服务器&#xff0c;腾讯云直接提供幻兽帕鲁镜像系统&#xff0c;阿里云通过计算巢服务&#xff0c;均可以一键部署&#xff0c;鼠标…

FPGA转行ISP的探索之一:行业概览

ISP的行业位置 最近看到一个分析&#xff0c;说FPGA的从业者将来转向ISP&#xff08;Image Signal Process图像信号处理&#xff09;是个不错的选择&#xff0c;可以适应智能汽车、AI等领域。故而我查了一下ISP&#xff0c;对它大致有个概念。 传统的ISP对应的是相机公司&…

Ps:曝光度

曝光度 Exposure命令在处理图像时&#xff0c;尤其是针对 32 位 HDR 图像&#xff0c;通常在线性颜色空间&#xff08;即灰度系数为 1.0&#xff09;中执行计算&#xff0c;这意味着它对图像的亮度进行直接和线性的调整。 这种处理方式特别适合处理高动态范围内容&#xff0c;因…

JavaScript中的常见算法

一.排序算法 1.冒泡排序 冒泡排序比较所有相邻的两个项&#xff0c;如果第一个比第二个大&#xff0c;则交换它们。元素项向上移动至 正确的顺序&#xff0c;就好像气泡升至表面一样。 function bubbleSort(arr) {const { length } arrfor (let i 0; i < length - 1; i)…

算法学习——LeetCode力扣回溯篇2

算法学习——LeetCode力扣回溯篇2 40. 组合总和 II 40. 组合总和 II - 力扣&#xff08;LeetCode&#xff09; 描述 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字…