Spring 事务原理总结五

news2024/11/24 14:47:09

很抱歉,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.coloradmin.cn/o/1449748.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Maven 跳过单元测试

文章目录 方法一&#xff1a;命令行跳过执行测试用例方式二&#xff1a;命令行跳过编译与执行测试用例方式三&#xff1a;通过 POM 文件配置默认跳过测试方式四&#xff1a;IDEA 配置 VM OPtions 在软件开发过程中&#xff0c;Maven 自动化构建工具扮演着关键角色。尤其是&…

Nodejs 第三十七章(连表and子查询)

子查询 子查询&#xff08;Subquery&#xff09;&#xff0c;也被称为嵌套查询&#xff08;Nested Query&#xff09;&#xff0c;是指在一个查询语句中嵌套使用另一个完整的查询语句。子查询可以被视为一个查询的结果集&#xff0c;它可以作为外层查询的一部分&#xff0c;用…

【牛客面试必刷TOP101】Day21.BM11 链表相加(二)和BM12 单链表的排序

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;牛客面试必刷TOP101 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&…

【微服务】skywalking自定义告警规则使用详解

目录 一、前言 二、SkyWalking告警功能介绍 2.1 SkyWalking告警是什么 2.2 为什么需要SkyWalking告警功能 2.2.1 及时发现系统异常 2.2.2 保障和提升系统稳定性 2.2.3 避免数据丢失 2.2.4 提高故障处理效率 三、 SkyWalking告警规则 3.1 SkyWalking告警规则配置 3.2 …

【Visual Studio】使用空格替换制表符

环境 VS版本&#xff1a;VS2013 问题 如何生成空格替换制表符&#xff1f; 步骤 1、菜单 工具->选项&#xff0c;文本编辑器->C/C->制表符&#xff0c;选择【插入空格】。

【数据结构】数组、双链表代码实现

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

类和对象的内存分配机制

一、类和对象的内存分配机制 二、分配机制(总结) 三、内存图分析题

git stash 正确用法

目录 一、背景 二、使用 2.1 使用之前&#xff0c;先简单了解下 git stash 干了什么&#xff1a; 2.2 git stash 相关命令 2.3 使用流程 1. 执行 git stash 2. 查看刚才保存的工作进度 git stash list 3. 这时候在看分支已经是干净无修改的(改动都有暂存到 stash) 4. 现在…

数字的魅力之情有独钟的素数

情有独钟的素数 什么是素数 素数&#xff08;Prime number&#xff09;也称为质数&#xff0c;是指在非0自然数中&#xff0c;除了1与其本身之外不拥有其他因数的自然数。也就是说&#xff0c;素数需要满足两个条件&#xff1a; 大于1的整数&#xff1b;只拥有1和其自身两个…

LeetCode “AddressSanitizer:heat-use-after-free on address“问题解决方法

heat-use-after-free &#xff1a; 访问堆上已经被释放的内存地址 现象&#xff1a;同样代码在LeetCode上报错&#xff0c;但是自己在IDE手动打印并不会报错 个人猜测&#xff0c;这个bug可能来源于LeetCode后台输出打印链表的代码逻辑问题。 问题描述 题目来自LeetCode的8…

红队打靶练习:Alfa:1

下载连接点击此处即可&#xff01; 目录 信息收集 1、arp 2、nmap 3、gobuster WEB web信息收集 FTP登录 smaba服务 crunch密码生成 提权 系统信息收集 权限提升 信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# arp-scan -l Interface: eth0, type: EN10MB, …

【C++航海王:追寻罗杰的编程之路】关于模板,你知道哪些?

目录 1 -> 泛型编程 2 -> 函数模板 2.1 -> 函数模板概念 2.2 -> 函数模板格式 2.3 -> 函数模板的原理 2.4 -> 函数模板的实例化 2.5 -> 函数参数的匹配原则 3 -> 类模板 3.1 -> 类模板的定义格式 3.2 -> 类模板的实例化 1 -> 泛型编…

深度学习疆界:探索基本原理与算法,揭秘应用力量,展望未来发展与智能交互的新纪元

目录 什么是深度学习 深度学习的基本原理和算法 深度学习的应用实例 深度学习的挑战和未来发展方向 挑战 未来发展方向 深度学习与机器学习的关系 深度学习与人类的智能交互 什么是深度学习 深度学习是一种基于神经网络的机器学习方法&#xff0c;旨在模仿人类大脑分析…

C语言学习day14:跳转语句

今天学习的跳转语句主要是三种&#xff1a; break continue goto 上一篇文章已经说过了break和continue break&#xff1a;结束这个循环 continue&#xff1a;结束当前的循环迭代&#xff0c;进行下一次的迭代 看看二者代码的区别 代码&#xff08;break&#xff09;&am…

php基础学习之文件包含

描述 在一个php脚本中&#xff0c;将另一个php文件包含进来&#xff0c;合作实现某种功能 这个描述看起来似乎和C/Java等语言的头文件/包有点类似&#xff0c;但本质是不一样的 打个比方&#xff1a; C/Java的头文件/包更像是一个工具箱&#xff0c;存放各种很完善的工具&#…

C++集群聊天服务器 muduo+nginx+redis+mysql数据库连接池 笔记 (下)

C集群聊天服务器 网络模块业务模块CMake构建项目 笔记 &#xff08;上&#xff09;-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/135991635?spm1001.2014.3001.5501C集群聊天服务器 数据模块业务模块CMake构建项目 笔记 &#xff08;上&#xff09;-CSDN博…

TMGM官网平台开户运作流程如下:

TMGM官网平台开户运作流程如下&#xff1a; 首先&#xff0c;投资者需要注册并登录TMGM官网平台。在平台上&#xff0c;投资者可以选择适合自己的交易账户类型&#xff0c;包括标准账户、高级账户等。 然后&#xff0c;投资者需要进行身份验证和资金入账操作。TMGM会要求投资…

AcWing 122 糖果传递(贪心)

[题目概述] 有 n 个小朋友坐成一圈&#xff0c;每人有 a[i] 个糖果。 每人只能给左右两人传递糖果。 每人每次传递一个糖果代价为 1。 求使所有人获得均等糖果的最小代价。 输入格式 第一行输入一个正整数 n&#xff0c;表示小朋友的个数。 接下来 n 行&#xff0c;每行一个…

【51单片机】一个简单的例子TMOD&TCON带你永远理解【(不)可位寻址】

前言 大家好吖&#xff0c;欢迎来到 YY 滴单片机系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Linux》专栏YY的《数据…

Vulhub kali 环境安装教程

进入 root 权限 sudo su 更新软件 apt-get update 安装 HTTPS 协议和 CA 证书 apt-get install -y apt-transport-https ca-certificates 安装 docker apt install docker.io 查看 docker 是否安装完成 docker -v 安装 pip apt-get install python3-pip 安装 docker-compose do…