• Spring5源码-事务的创建、回滚、提交


    1. 前言

    1.1 TransactionSynchronizationManager

    TransactionSynchronizationManager 中使用 ThreadLocal 保存了在不同线程中不同事务的信息。

    public abstract class TransactionSynchronizationManager {
    
    	private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
    
    	private static final ThreadLocal> resources =
    			new NamedThreadLocal<>("Transactional resources");
    
    	private static final ThreadLocal> synchronizations =
    			new NamedThreadLocal<>("Transaction synchronizations");
    
    	private static final ThreadLocal currentTransactionName =
    			new NamedThreadLocal<>("Current transaction name");
    
    	private static final ThreadLocal currentTransactionReadOnly =
    			new NamedThreadLocal<>("Current transaction read-only status");
    
    	private static final ThreadLocal currentTransactionIsolationLevel =
    			new NamedThreadLocal<>("Current transaction isolation level");
    
    	private static final ThreadLocal actualTransactionActive =
    			new NamedThreadLocal<>("Actual transaction active");
    	...
    }
    复制代码

    我们从上面的部分代码可以看到, TransactionSynchronizationManager 中保存的是各个线程中的事务信息。

    1.2 事务属性

    1.2.1 只读

    对一个查询操作来说,如果我们把它设置成 只读 ,就能够明确告诉数据库,这个操作不涉及写操作。这 样数据库就能够针对查询操作来进行优化。

    @Transactional(readOnly = true)
    复制代码

    注意: 对增删改操作设置只读会抛出下面异常 : Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

    1.2.2 超时

    事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间 占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。 此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常 程序可以执行。概括来说就是一句话: 超时回滚,释放资源 。

    @Transactional(timeout = 3)
    复制代码

    执行过程中抛出异常: org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022

    1.2.3 回滚策略

    声明式事务 默认只针对运行时异常回滚,编译时异常不回滚 。可以通过@Transactional中相关属性设置回滚策略:

    rollbackFor属性
    rollbackForClassName属性
    noRollbackFor属性
    noRollbackForClassName属性
    
    @Transactional(noRollbackFor = ArithmeticException.class)
    @Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
    复制代码

    1.2.4 事务隔离级别

    数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事 务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同 的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。隔离级别一共有四种:

    READ UNCOMMITTED
    READ COMMITTED
    REPEATABLE READ
    SERIALIZABLE
    

    各个隔离级别解决并发问题的能力见下表:

    各种数据库产品对事务隔离级别的支持程度:

    使用方式如下:

    @Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
    @Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
    @Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
    @Transactional(isolation = Isolation.SERIALIZABLE)//串行化
    复制代码

    1.2.5 事务传播行为

    当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如: 方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行 。

    事务传播属性 解释
    PROPAGATION_REQUIRED 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。即如果上级具有事务,则使用上级的事务,不具备则自己新建一个事务
    PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。即如果上级存在事务,则挂起上级事务,使用自己新创建的事务
    PROPAGATION_MANDATORY 支持当前事务,如果当前没有事务,就抛出异常。即如果上级具有事务,则使用上级的事务,上级没有事务,则抛出异常
    PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。即如果上级具有事务,则使用上级的事务,如果上级没有事务,则不开启事务
    PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。即如果上级具有事务,则使用挂起上级事务,使用非事务方式。
    PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常
    PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

    这里解释一下 PROPAGATION_NESTED 和 PROPAGATION_REQUIRES_NEW 的区别:

    • PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolledback 而不依赖于外部事务,它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内部事务结束时, 外部事务将继续执行。 PROPAGATION_REQUIRES_NEW 常用于日志记录,或者交易失败仍需要留痕

    • PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint.如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分,只有外部事务结束后它才会被提交.

    由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于:

    • PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 rollback.

    2. 事务的创建 - createTransactionIfNecessary

    上篇中,我们分析到 TransactionAspectSupport#invokeWithinTransaction 方法完成了事务的增强调用。而其中 createTransactionIfNecessary 方法则是在需要的时候创建了事务,之所以说需要的时候而不是说直接创建,是因为这里要考虑到事务的传播属性。

    createTransactionIfNecessary 的实现是在 TransactionAspectSupport#createTransactionIfNecessary 中,完成了事务的创建,这里面考虑了事务的传播属性的处理,所以并不是一定会创建事务,根据传播属性的不同会有不同的处理。

    详细代码如下:

    // TransactionAttribute 是解析出来的事务
    protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
                    @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
    
            // If no name specified, apply method identification as transaction name.
            // 如果没有名称指定则使用方法唯一标识,并使用  DelegatingTransactionAttribute 封装 txAttr
            if (txAttr != null && txAttr.getName() == null) {
                    txAttr = new DelegatingTransactionAttribute(txAttr) {
                            @Override
                            public String getName() {
                                    return joinpointIdentification;
                            }
                    };
            }
    
            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");
                            }
                    }
            }
            // 构建事务信息
            return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }
    复制代码

    这里我们可以看到其基本逻辑如下:

    1. 使用 DelegatingTransactionAttribute 封装传入的 TransactionAttribute 实例。对于传入的 TransactionAttribute 类型的参数 txAttr ,当前实际类型是 RuleBasedTransactionAttribute ,是由获取事务属性时生成的,主要用于数据承载,而这里之所以使用 DelegatingTransactionAttribute 进行封装,也是为了提供更多的功能。
    2. 获取事务。即 tm.getTransaction(txAttr); ,事务处理的核心当然是事务,这里获取到了事务。实际上 getTransaction 方法返回的是 TransactionStatus (实现类是 DefaultTransactionStatus )。 DefaultTransactionStatus 是对事务的进一步封装,包含了当前事务信息、挂起事务信息(如果有),保存点等信息。
    3. 构建事务信息。即 prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); 。对上面几个步骤获取的信息构建 TransactionInfo 并返回。 TransactionInfo 是 DefaultTransactionStatus 更进一步的封装。

    我们来详细看看几个类的具体内容:

    • 关于事务管理器 TransactionManager ,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 JpaTransactionManager 实例。

    • TransactionStatus (实际上的实现是 DefaultTransactionStatus) 里面包含的内容:

      • 这里注意 suspendedResources 实际上保存了是挂起的上层事务的信息。如果没有上层事务(也就是没嵌套事务),就是null,这里是通过 UserProxyServiceImpl#findAll (事务传播属性是REQUIRED) 调用 UserServiceImpl#finaAll (事务传播属性是 REQUIRES_NEW ) 的方式挂起来了一个来自 com.kingfish.springjdbcdemo.service.UserProxyServiceImpl.findAll 方法的事务信息。 savepoint 只有在内嵌事务的隔离级别是 PROPAGATION_NESTED 才有可能会保存。
    • TransactionInfo 里面包含的内容:

      • 可以看到 TransactionInfo 是 TransactionStatus 、 TransactionAttribute 、 TransactionManager 等属性更进一步封装。
    • 关于事务挂起封装成的 SuspendedResourcesHolder 。

    了解完上述一些类的保存内容后,下面我们来详细分析 createTransactionIfNecessary 中的几个方法

    2.1 获取事务 - tm.getTransaction(txAttr)

    实际上调用的是 AbstractPlatformTransactionManager#getTransaction 方法,在这里面获取了事务(可能是创建新事物,也可能不是),返回的类型是 TransactionStatus 。

    下面我们来看看其代码:

    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
                    throws TransactionException {
    
            // Use defaults if no transaction definition given.
            TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
            // 1. 获取事务
            Object transaction = doGetTransaction();
            boolean debugEnabled = logger.isDebugEnabled();
            // 2. 判断当前线程是否存在事务,判断依据是当前线程记录的数据库连接不为空,且连接(connectionHolder)中的 transactionActive 属性 为true;
            // 这个方法的实现在 DataSourceTransactionManager#isExistingTransaction。
            if (isExistingTransaction(transaction)) {
                    // Existing transaction found -> check propagation behavior to find out how to behave.	
                    // 3.当前线程已经存在事务,则按照嵌套事务的逻辑处理
                    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.
            // 下面是针对事务传播属性进行处理了
            // 4. 如果传播属性是 PROPAGATION_MANDATORY 。但是当前线程又不存在事务,则抛出异常
            if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
                    throw new IllegalTransactionStateException(
                                    "No existing transaction found for transaction marked with propagation 'mandatory'");
            }
            // 5. 如果传播属性是PROPAGATION_REQUIRED 、PROPAGATION_REQUIRES_NEW 、PROPAGATION_NESTED 都需要新建事务
            else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                
  • 相关阅读:
    【每日一题】补档 ABC309F - Box in Box | 三维偏序 | 树状数组 | 中等
    高效率开发Web安全扫描器之路(一)
    CCC数字钥匙设计【NFC】--NFC卡相关基础知识
    SpringMVC如何处理表单提交与文件上传
    The following method did not exist (maven依赖冲突解决全记录)
    代码随想录算法训练营第五十九天| 503.下一个更大元素II,42. 接雨水
    C++入门(2):缺省参数、函数重载、引用
    Ubuntu Kafka开机自启动服务
    结构化编程(SP,structured programming)
    crash handler
  • 原文地址:https://blog.csdn.net/AS011x/article/details/126884603