前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Transaction,从入门到上瘾

Spring Transaction,从入门到上瘾

作者头像
程序猿杜小头
发布2022-12-01 21:43:17
5640
发布2022-12-01 21:43:17
举报
文章被收录于专栏:程序猿杜小头

Running with Spring Boot v2.5.7, MySQL 8.0

Spring Transaction 针对JDBC API中关于事务管理这一部分进行了高级抽象,它支持两种方式的事务管理,分别是:声明式事务管理 (Declarative Transaction Management) 与 编程式事务管理 (Programmatic Transaction Management) 。声明式事务管理由@Transactional注解来承载,而编程式事务管理则由TransactionManagerTransactionTemplate来实现 (推荐使用后者) 。基于注解的声明式事务管理方式既简洁又优雅,可以有效收敛横切关注逻辑,但极尽简洁的背后却也暗藏陷阱,比如:大事务、事务未正常回滚等。相较于声明式事务管理,编程式事务管理方式对事务粒度的把控更为灵活,这往往很有必要!

本文将重点从源码层面为大家解读声明式事务管理的实现原理,从而帮助大家在工作中更好、更准确地使用声明式事务管理。

写在前面

在数据库系统中,一个事务往往由一组被视为原子单元的数据操作语句(DML)与查询语句组成,要么所有语句全部提交,要么所有语句全部回滚。数据库事务一般要遵循ACID原则,ACID分别由Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性) 和Durability(持久性)这四个单词的首字母拼接而成。通俗来说:1) 原子性,一个事务内的数据操作语句要么全部提交,要么全部回滚;2) 一致性,一个事务执行完毕后,数据库中的数据要么处于该事务前的状态,要么处于该事务后的状态;3) 隔离性,在并发环境下,当多个不同的事务同时操作相同的数据时,每个事务都有各自完整的数据空间;4) 持久性,一个事务成功提交后,数据库中相关数据的变更必须永久保存,即使因数据库系统崩溃而重启,数据库依然可以恢复到该事务成功提交后的状态。

在ACID规则中,隔离性有必要单独拿出来聊聊!在多个不同事务同时执行的情况下,就可能出现脏读 (dirty read)、不可重复读 (non-repeatable read) 和 幻读 (phantom read) 的问题。为了解决这些问题,隔离级别的概念随之而来:隔离级别是事务隔离程度的度量。在SQL-92标准中,明确定义了四种隔离级别,分别是:READ_UNCOMMITTED(读未提交)、READ_COMMITTED(读已提交)、REPEATABLE_READ(可重复读) 和SERIALIZABLE(串行化)。在这四种隔离级别中,READ_UNCOMMITTED对事务的隔离级别最低,甚至压根谈不上隔离;而SERIALIZABLE对事务的隔离程度最高,它既能预防脏读与不可重复读,还能避免幻读。注意:隔离级别的选择往往需要在性能与可靠性、一致性之间做出平衡

代码语言:javascript
复制
| Isolation Level  | Dirty Reads | Non-repeatable Reads | Phantom Reads |
|------------------|-------------|----------------------|---------------|
| READ_UNCOMMITTED | √           | √                    | √             |
| READ_COMMITTED   | ×           | √                    | √             |
| REPEATABLE_READ  | ×           | ×                    | √             |
| SERIALIZABLE     | ×           | ×                    | ×             | 

脏读指的是在一个事务中读取到了另一事务中未提交的一行记录;不可重复读指的是在同一事务中多次读取同一行记录,但每次所读取到的内容都不一样;幻读指的是在同一事务中多次读取同一条件下的多行记录,但相较于上一次的读取结果来看,每次所读取到的内容不是多了若干行记录,就是少了若干行记录,就好像产生了幻觉。脏读

代码语言:javascript
复制
| Time Series | Transaction A                              | Transaction B                      |
|-------------|--------------------------------------------|------------------------------------|
| T1          | UPDATE user SET name = 'java' WHERE id = 1 |                                    |
| T2          |                                            | SELECT name FROM user WHERE id = 1 |
| T3          | ROLLBACK                                   |                                    |

假定当前隔离级别为READ_UNCOMMITTED。在事务A中,更新了ID为1的用户名;尽管事务A还未提交,但事务B中依然读取到了更新后的用户名;若事务A回滚,那事务B所读取到的用户名也就烟消云散了,用户表中压根就不存在该用户!不可重复读

代码语言:javascript
复制
| Time Series | Transaction A                      | Transaction B                              |
|-------------|------------------------------------|--------------------------------------------|
| T1          | SELECT name FROM user WHERE id = 1 |                                            |
| T2          |                                    | UPDATE user SET name = 'java' WHERE id = 1 |
| T3          |                                    | COMMIT                                     |
| T4          | SELECT name FROM user WHERE id = 1 |                                            |

假定当前隔离级别为READ_COMMITTED。首先在事务A中,读取ID为1的用户名;然后在事务B中更新该用户的用户名并且提交变更;接着在事务A中,再次读取该用户的用户名,但此次与上一次所读取到的用户名竟然不一样了。幻读

代码语言:javascript
复制
| Time Series | Transaction A                      | Transaction B                                  |
|-------------|------------------------------------|------------------------------------------------|
| T1          | SELECT name FROM user WHERE id < 5 |                                                |
| T2          |                                    | INSERT INTO user(id, name) VALUE (4, 'golang') |
| T3          |                                    | COMMIT                                         |
| T4          | SELECT name FROM user WHERE id < 5 |                                                |

假定当前隔离级别为REPEATABLE_READ。首先在事务A中,读取ID小于5的用户名集合;然后在事务B中插入ID为4、用户名为golang的一行记录并且成功提交;接着在事务A中,再次读取ID小于5的用户名集合,但此次相较于上一次所读取到的用户名集合,竟然多出了一行记录,这难道是幻觉吗?幻读与不可重复读有点类似,但从结果来看,幻读所读取到的内容倾向于多行记录集;而从数据操纵语句(DML)来看,不可重复读多与UPDATE/DELETE语句有关,幻读却多与INSERT/DELETE语句有关。

MySQL提供了多种存储引擎(storage engine),其中只有InnoDB存储引擎支持事务,且完全遵循ACID原则。在5.5版本之后,InnoDB取代MyISAM成为了默认的存储引擎。我们可以通过SHOW ENGINES来查看这些存储引擎以及各自对事务的支持情况,如下所示。

代码语言:javascript
复制
| Engine  | Support | Transactions | XA  | Savepoints |
|---------|---------|--------------|-----|------------|
| InnoDB  | DEFAULT | YES          | YES | YES        |
| MyISAM  | YES     | NO           | NO  | NO         |
| MEMORY  | YES     | NO           | NO  | NO         |
| CSV     | YES     | NO           | NO  | NO         |
| ARCHIVE | YES     | NO           | NO  | NO         |

对于SQL-92标准中所定义的四种隔离级别,InnoDB毫无保留地予以支持。其中REPEATABLE_READ是其默认的隔离级别。一般,InnoDB所提供的隔离级别有三种作用域,分别是:globalsessionnext transaction only。下面介绍每一种作用域的设定方式。global

代码语言:javascript
复制
[mysqld]
transaction-isolation = REPEATABLE-READ
transaction-read-only = OFF

session

代码语言:javascript
复制
| Statements                                              | Legal Transaction Isolations                                 |
|---------------------------------------------------------|--------------------------------------------------------------|
| SET @@SESSION.transaction_isolation = 'REPEATABLE-READ' | READ-UNCOMMITTED\READ-COMMITTED\REPEATABLE-READ\SERIALIZABLE |
| SET SESSION transaction_isolation = 'REPEATABLE-READ'   | READ UNCOMMITTED\READ COMMITTED\REPEATABLE READ\SERIALIZABLE |
| SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ | READ UNCOMMITTED\READ COMMITTED\REPEATABLE READ\SERIALIZABLE |

next transaction only

代码语言:javascript
复制
| Statements                                      | Legal Transaction Isolations                                 |
|-------------------------------------------------|--------------------------------------------------------------|
| SET @@transaction_isolation = 'REPEATABLE-READ' | READ-UNCOMMITTED\READ-COMMITTED\REPEATABLE-READ\SERIALIZABLE |
| SET TRANSACTION ISOLATION LEVEL REPEATABLE READ | READ UNCOMMITTED\READ COMMITTED\REPEATABLE READ\SERIALIZABLE |

仅仅掌握隔离级别的设定方法是不够的,还需要知晓如何查看每种作用域下的隔离级别:

代码语言:javascript
复制
select @@GLOBAL.transaction_isolation;
select @@SESSION.transaction_isolation;
select @@transaction_isolation;

备注:global作用域下隔离级别的设定方式也可以由SET语句完成,但请忘记它

接下来,简单介绍一下事务提交(commit)与事务回滚(rollback)的概念。提交意味着当前事务对数据所做的变更将永久保存,而且这一变更对其他事务是可见的;相反地,回滚则会取消当前事务对数据所做的变更。此外,如果在当前事务中设定了InnoDB锁,那么提交与回滚会毫不犹豫地释放这些锁。

对于InnoDB中的数据表,我们经常在DataGrip或Navicat的控制台中通过INSERTUPDATEDELETE语句进行数据操作,往往也不会显式地将这些数据操作语句放置在START TRANSACTIONCOMMIT语句之间,可为何依然可以自动提交呢?MySQL默认为每一个新建立的连接启用autocommit模式。在该模式下,所有发送到MySQL的SQL语句都将单独在自己的事务中运行;换句话说,每一条SQL语句都会被包围在START TRANSACTIONCOMMIT语句之间。尽管MySQL连接默认开启autocommit模式,但我们依然可以通过START TRANSACTIONCOMMIT语句来显式地实现多语句事务,压根不需要显式地通过SET autocommit = 0语句来关闭autocommit模式哦!一旦autocommit模式处于关闭状态,你就必须使用COMMITROLLBACK语句来关闭该事务,否则事务会一直处于开启状态。此外,当autocommit模式由关闭转为开启状态时,MySQL会自动COMMIT所有处于开启状态的事务 (If autocommit is 0 and you change it to 1, MySQL performs an automatic COMMIT of any open transaction) 。

总的来说,在MYSQL中关于多语句事务有两种模式,而Spring Transaction是基于MODE I的,如下所示:

代码语言:javascript
复制
+--------------------+  +--------------------+
|       MODE I       |  |      MODE II       |
+--------------------+  +--------------------+
+--------------------+  +--------------------+
| SET autocommit = 0 |  | START TRANSACTION  |
+--------------------+  +--------------------+
+--------------------+  +--------------------+
|    BUSINESS SQL    |  |    BUSINESS SQL    |
+--------------------+  +--------------------+
+--------------------+  +--------------------+
|  COMMIT/ROLLBACK   |  |  COMMIT/ROLLBACK   |
+--------------------+  +--------------------+

1 开启事务管理机制

在工作中,笔者经常在Spring Boot工程的启动类上发现@EnableTransactionManagement注解的身影。事实上,压根不再需要通过该注解显式地开启Spring事务管理机制了,这主要得益于Spring Boot的自动配置特性。Spring事务中核心自动配置类是TransactionAutoConfiguration,它位于spring-boot-autoconfigure模块下transaction包内,主要逻辑如下:

代码语言:javascript
复制
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PlatformTransactionManager.class)
@AutoConfigureAfter({DataSourceTransactionManagerAutoConfiguration.class})
@EnableConfigurationProperties(TransactionProperties.class)
public class TransactionAutoConfiguration {
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnSingleCandidate(PlatformTransactionManager.class)
    public static class TransactionTemplateConfiguration {
        @Bean
        @ConditionalOnMissingBean(TransactionOperations.class)
        public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
            return new TransactionTemplate(transactionManager);
        }
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnBean(TransactionManager.class)
    @ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
    public static class EnableTransactionManagementConfiguration {
        @Configuration(proxyBeanMethods = false)
        @EnableTransactionManagement(proxyTargetClass = false)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
        public static class JdkDynamicAutoProxyConfiguration { }

        @Configuration(proxyBeanMethods = false)
        @EnableTransactionManagement(proxyTargetClass = true)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
        public static class CglibAutoProxyConfiguration { }
    }
}

从上述自动配置逻辑中,大致可以得出以下结论:

  1. TransactionAutoConfiguration 由 ConditionalOnClass(PlatformTransactionManager.class) 标注,说明该自动配置类生效的前提:在当前classpath下必须要有PlatformTransactionManager.class的身影;换句话说,当前classpath下必须要有spring-tx依赖包,这往往很容易满足,因为spring-boot-starter-jdbc依赖spring-jdbc,而 spring-jdbc 又依赖于 spring-tx。
  2. TransactionTemplateConfiguration是 TransactionAutoConfiguration 中一静态内部类,会声明一个默认的TransactionTemplate类型Bean;其由 ConditionalOnSingleCandidate(PlatformTransactionManager.class) 标注,说明该静态配置类生效的前提:在BeanFactory中有一PlatformTransactionManager类型的Bean;该Bean会由spring-boot-autoconfigure模块下jdbc包内的DataSourceTransactionManagerAutoConfiguration自动配置类所声明。
  3. EnableTransactionManagementConfiguration同样是 TransactionAutoConfiguration 中一静态内部类,在该静态内部类中又定义了两个静态内部类:JdkDynamicAutoProxyConfigurationCglibAutoProxyConfiguration,由于spring.aop.proxy-target-class属性值默认为true,因此 CglibAutoProxyConfiguration 会最终生效;注意:CglibAutoProxyConfiguration 由@EnableTransactionManagement(proxyTargetClass = true)注解标注,这就是“在Spring Boot工程中不再需要手动通过 @EnableTransactionManagement注解开启事务管理机制”的原因,它自动帮我们隐式开启了事务管理机制。

2 深入理解@EnableTransactionManagement注解

@EnableTransactionManagement注解接口内容如下:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
}
  1. mode属性用于指定切面逻辑的织入模式,只有两种选择:PROXYASPECTJPROXY,首先为目标对象创建代理对象,然后将切面逻辑应用于代理;ASPECTJ,是一种非运行时织入模式,直接将切面逻辑织入到目标对象中。
  2. 只有当织入模式为PROXY时,proxyTargetClass才有意义;若proxyTargetClass值为true,则使用CGLIB代理在运行时生成代理类,否则使用JDK动态代理在运行时生成代理类。

更为重要的一点:@EnableTransactionManagement注解通过Import注解引入了一个ImportSelector实现类,即TransactionManagementConfigurationSelector。众所周知,无论Import注解引入的是由@Configuration修饰的普通配置类,亦或是ImportSelector实现类,还是ImportBeanDefinitionRegistrar实现类,最终都是向BeanFactory中注册相关Bean。

代码语言:javascript
复制
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return new String[] {AutoProxyRegistrar.class.getName(),
                        ProxyTransactionManagementConfiguration.class.getName()};
            case ASPECTJ:
                return new String[] {determineTransactionAspectClass()};
            default:
                return null;
        }
    }
}

从上面内容来看,由于切面逻辑的织入模式默认为PROXY,因此selectImports()方法直接返回了一个由AutoProxyRegistrarProxyTransactionManagementConfiguration类名组成的字符串数组。下面逐一分析!

AutoProxyRegistrarAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,如下所示:

代码语言:javascript
复制
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    private final Log logger = LogFactory.getLog(getClass());
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean candidateFound = false;
        Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
        for (String annType : annTypes) {
            AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
            if (candidate == null) {
                continue;
            }
            Object mode = candidate.get("mode");
            Object proxyTargetClass = candidate.get("proxyTargetClass");
            if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() && Boolean.class == proxyTargetClass.getClass()) {
                candidateFound = true;
                if (mode == AdviceMode.PROXY) {
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    if ((Boolean) proxyTargetClass) {
                        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                        return;
                    }
                }
            }
        }
    }
}

看到上述registerBeanDefinitions()方法中的参数后,我们应该很容易想到,registerBeanDefinitions()方法的核心逻辑肯定是根据AnnotationMetadata中的内容向BeanDefinitionRegistry中注册BeanDefinition实例。要想搞清楚所注册BeanDefinition实例的具体信息,只能从第一个方法参数入手了;那么AnnotationMetadata是什么?它封装了一个类上所有Spring注解的元数据,一般可以通过AnnotationMetadata.introspect(Class<?> type)这种方式来获取目标类上所有Spring注解的元数据。经过这些知识的铺垫之后,我们应该意识到:registerBeanDefinitions()方法中第一个参数所封装的就是CglibAutoProxyConfiguration这一内部静态配置类头上的所有Spring注解的元数据,既然有了注解元数据,那就可以拿到@EnableTransactionManagement注解中modeproxyTargetClass这俩属性值,由于mode默认值为PROXY,最终借助AopConfigUtilsBeanDefinitionRegistry中注册一个beanClass属性值为InfrastructureAdvisorAutoProxyCreator.class的BeanDefinition实例,当后期通过该BeanDefinition实例完成实例化、属性填充和初始化之后,IoC容器中就有了一个名称为org.springframework.aop.config.internalAutoProxyCreator、类型为InfrastructureAdvisorAutoProxyCreator的Bean。大家可以在各自Spring Boot工程中,借助下面代码来看一下该Bean的最终类型:

代码语言:javascript
复制
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(DemoApplication.class, args);
        String AUTO_PROXY_CREATOR_BEAN_NAME = 
                "org.springframework.aop.config.internalAutoProxyCreator";
        Object internalAutoProxyCreator = configurableApplicationContext.getBean(AUTO_PROXY_CREATOR_BEAN_NAME);
        System.out.println(internalAutoProxyCreator.getClass());
    }
}

打印结果如下:

代码语言:javascript
复制
class org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator

咦?当前IoC容器中确实存在一名称为org.springframework.aop.config.internalAutoProxyCreator的Bean实例,但并不是刚才说的InfrastructureAdvisorAutoProxyCreator类型,这是咋回事?其实,在Spring AOP中关于AbstractAutoProxyCreator有三个重要的子类,按优先级从高到低排序分别是:AnnotationAwareAspectJAutoProxyCreatorAspectJAwareAdvisorAutoProxyCreatorInfrastructureAdvisorAutoProxyCreator;如果同时向BeanDefinitionRegistry中注册这三种AbstractAutoProxyCreator,那最后将只有AnnotationAwareAspectJAutoProxyCreator驻留在BeanDefinitionRegistry中,感兴趣的读者可以自行研读AopConfigUtils中的registerOrEscalateApcAsRequired()方法,其内会涉及一个优先级提升的操作。大家可以通过下面这种方式来验证三者的优先级:

代码语言:javascript
复制
public class AutoProxyCreatorPriorityApplication {
    public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
            "org.springframework.aop.config.internalAutoProxyCreator";
    public static void main(String[] args) {
        // STEP 1:构造 BeanDefinitionRegistry
        // STEP 2 3 4 依次向 BeanDefinitionRegistry 中注册三种 AbstractAdvisorAutoProxyCreator BeanDefinition 实例,
        // 但 name 完全一致,即 AUTO_PROXY_CREATOR_BEAN_NAME
        BeanDefinitionRegistry beanDefinitionRegistry = new SimpleBeanDefinitionRegistry();

        // STEP 2:注册 InfrastructureAdvisorAutoProxyCreator BeanDefinition
        AopConfigUtils.registerAutoProxyCreatorIfNecessary(beanDefinitionRegistry);
        BeanDefinition infrastructureAdvisorCreatorBeanDefinition = beanDefinitionRegistry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        System.out.println(infrastructureAdvisorCreatorBeanDefinition.getBeanClassName());

        // STEP 3:注册 AspectJAwareAdvisorAutoProxyCreator BeanDefinition
        AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(beanDefinitionRegistry);
        BeanDefinition aspectJAwareAdvisorCreatorBeanDefinition = beanDefinitionRegistry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        System.out.println(aspectJAwareAdvisorCreatorBeanDefinition.getBeanClassName());

        // STEP 4:注册 AnnotationAwareAspectJAutoProxyCreator BeanDefinition
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(beanDefinitionRegistry);
        BeanDefinition annotationAwareAspectJCreatorBeanDefinition = beanDefinitionRegistry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        System.out.println(annotationAwareAspectJCreatorBeanDefinition.getBeanClassName());
    }
}

可AnnotationAwareAspectJAutoProxyCreator又是在哪里注册到BeanDefinitionRegistry中去的呢?进入spring-boot-autoconfigure模块下aop包内有一名称为AopAutoConfiguration的自动配置类,其主要内容有:

代码语言:javascript
复制
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Advice.class)
    static class AspectJAutoProxyingConfiguration {
        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = false)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
        static class JdkDynamicAutoProxyConfiguration { }

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
        static class CglibAutoProxyConfiguration { }
    }
}

接着,进入EnableAspectJAutoProxy注解一探究竟:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;
    boolean exposeProxy() default false;
}

继续,进入AspectJAutoProxyRegistrar中一探究竟:

代码语言:javascript
复制
public class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
}

终于要见到庐山真面目了,如下所示:

代码语言:javascript
复制
public abstract class AopConfigUtils {
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
        return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
    }

    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }
}

ProxyTransactionManagementConfigurationProxyTransactionManagementConfiguration是一个由@Configuration注解修饰的普通配置类,主要声明了一个BeanFactoryTransactionAttributeSourceAdvisor类型的Bean。内容如下:

代码语言:javascript
复制
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
            TransactionAttributeSource transactionAttributeSource, 
            TransactionInterceptor transactionInterceptor) {
        BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        advisor.setTransactionAttributeSource(transactionAttributeSource);
        advisor.setAdvice(transactionInterceptor);
        return advisor;
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionAttributeSource transactionAttributeSource() {
        return new AnnotationTransactionAttributeSource();
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionAttributeSource(transactionAttributeSource);
        if (this.txManager != null) {
            interceptor.setTransactionManager(this.txManager);
        }
        return interceptor;
    }
}

在Spring AOP中关于Advisor有两个分支,即PointcutAdvisorIntroductionAdvisor。PointcutAdvisor持有一个Advice和一个Pointcut,Spring AOP将Advice建模为org.aopalliance.intercept.MethodInterctptor拦截器,Pointcut用于声明应该在哪些Joinpoint(连接点)处应用切面逻辑,而Joinpoint在SpringAOP中专指方法的执行,因此,PointcutAdvisor中的Advice是方法级的拦截器;IntroductionAdvisor仅持有一个Advice和一个ClassFilter,显然,IntroductionAdvisor中的Advice是类级的拦截器。显然,PointcutAdvisor更为常用,因为PointcutAdvisor中的Pointcut不仅包含ClassFilter,还额外持有MethodMatcher,从而可以借助MethodMatcher中的matches()方法准确拦截持有@Transactional注解的目标方法了。

从BeanFactoryTransactionAttributeSourceAdvisor继承关系图来看,它属于PointcutAdvisor分支。从上述ProxyTransactionManagementConfiguration中transactionAdvisor()方法内容来看,创建BeanFactoryTransactionAttributeSourceAdvisor实例之后,紧接着将TransactionInterceptorTransactionAttributeSource置入到该实例中;其中,TransactionInterceptor即充当PointcutAdvisor中Advice的角色,而TransactionAttributeSource肯定与Pointcut有关。BeanFactoryTransactionAttributeSourceAdvisor源码必然可以验证这一说法:

代码语言:javascript
复制
public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {

    private TransactionAttributeSource transactionAttributeSource;

    private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
        @Override
        protected TransactionAttributeSource getTransactionAttributeSource() {
            return transactionAttributeSource;
        }
    };

    public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
        this.transactionAttributeSource = transactionAttributeSource;
    }
}

上述源码表明TransactionAttributeSource是Pointcut的一大臂膀,也揭开了Pointcut的庐山真面目:TransactionAttributeSourcePointcut,其主要内容如下:

代码语言:javascript
复制
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut {
    protected TransactionAttributeSourcePointcut() {
        setClassFilter(new TransactionAttributeSourceClassFilter());
    }
    
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        TransactionAttributeSource tas = getTransactionAttributeSource();
        return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
    }

    protected abstract TransactionAttributeSource getTransactionAttributeSource();

    private class TransactionAttributeSourceClassFilter implements ClassFilter {
        @Override
        public boolean matches(Class<?> clazz) {
            if (TransactionalProxy.class.isAssignableFrom(clazz) ||
                    TransactionManager.class.isAssignableFrom(clazz) ||
                    PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) {
                return false;
            }
            TransactionAttributeSource tas = getTransactionAttributeSource();
            return (tas == null || tas.isCandidateClass(clazz));
        }
    }
}

TransactionAttributeSourcePointcut不仅继承了StaticMethodMatcherPointcut,内部又有一ClassFilter类型的内部类:TransactionAttributeSourceClassFilter,这样这个Pointcut就集齐ClassFilter和MethodMatcher了。此外,仔细阅读上述源码后不难发现:MethodMatchermatches(Method method, Class<?> targetClass)方法和ClassFiltermatches(Class<?> clazz)方法都依赖于getTransactionAttributeSource()方法所返回的TransactionAttributeSource实例,这个实例就是AnnotationTransactionAttributeSource,其主要内容为:

代码语言:javascript
复制
public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource {
    private static final boolean jta12Present;

    private static final boolean ejb3Present;

    static {
        ClassLoader classLoader = AnnotationTransactionAttributeSource.class.getClassLoader();
        jta12Present = ClassUtils.isPresent("javax.transaction.Transactional", classLoader);
        ejb3Present = ClassUtils.isPresent("javax.ejb.TransactionAttribute", classLoader);
    }

    private final boolean publicMethodsOnly;

    private final Set<TransactionAnnotationParser> annotationParsers;

    public AnnotationTransactionAttributeSource() {
        this(true);
    }

    public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
        this.publicMethodsOnly = publicMethodsOnly;
        if (jta12Present || ejb3Present) {
            this.annotationParsers = new LinkedHashSet<>(4);
            this.annotationParsers.add(new SpringTransactionAnnotationParser());
            if (jta12Present) {
                this.annotationParsers.add(new JtaTransactionAnnotationParser());
            }
            if (ejb3Present) {
                this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
            }
        } else {
            this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
        }
    }

    @Override
    public boolean isCandidateClass(Class<?> targetClass) {
        for (TransactionAnnotationParser parser : this.annotationParsers) {
            if (parser.isCandidateClass(targetClass)) {
                return true;
            }
        }
        return false;
    }

    @Override
    protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {
        return determineTransactionAttribute(clazz);
    }

    @Override
    @Nullable
    protected TransactionAttribute findTransactionAttribute(Method method) {
        return determineTransactionAttribute(method);
    }

    protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
        for (TransactionAnnotationParser parser : this.annotationParsers) {
            TransactionAttribute attr = parser.parseTransactionAnnotation(element);
            if (attr != null) {
                return attr;
            }
        }
        return null;
    }

    /**
     * By default, only public methods can be made transactional.
     */
    @Override
    protected boolean allowPublicMethodsOnly() {
        return this.publicMethodsOnly;
    }
}

根据上述AnnotationTransactionAttributeSource的源码内容,可以得出:

  1. 在构造TransactionAttributeSource实例时,会填充其Set<TransactionAnnotationParser>类型的成员变量annotationParsers,一般就是SpringTransactionAnnotationParserJtaTransactionAnnotationParser这俩解析器。
  2. isCandidateClass()方法与findTransactionAttribute()方法具体会委派TransactionAnnotationParser展开具体的逻辑。
  3. publicMethodsOnly默认值为true,即默认@Transactional注解只有标注在访问控制修饰符为public的方法才有效。

JtaTransactionAnnotationParser负责解析javax.transaction.Transactional注解,而SpringTransactionAnnotationParser则负责解析org.springframework.transaction.annotation.Transactional注解,因此后者是我们关注的重点。SpringTransactionAnnotationParser 源码如下:

代码语言:javascript
复制
public class SpringTransactionAnnotationParser implements TransactionAnnotationParser {
    @Override
    public boolean isCandidateClass(Class<?> targetClass) {
        return AnnotationUtils.isCandidateClass(targetClass, Transactional.class);
    }

    @Override
    public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
        AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
                element, Transactional.class, false, false);
        if (attributes != null) {
            return parseTransactionAnnotation(attributes);
        } else {
            return null;
        }
    }

    public TransactionAttribute parseTransactionAnnotation(Transactional ann) {
        return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false));
    }

    protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
        RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();

        Propagation propagation = attributes.getEnum("propagation");
        rbta.setPropagationBehavior(propagation.value());
        Isolation isolation = attributes.getEnum("isolation");
        rbta.setIsolationLevel(isolation.value());

        rbta.setTimeout(attributes.getNumber("timeout").intValue());
        String timeoutString = attributes.getString("timeoutString");
        rbta.setTimeoutString(timeoutString);

        rbta.setReadOnly(attributes.getBoolean("readOnly"));
        rbta.setQualifier(attributes.getString("value"));
        rbta.setLabels(Arrays.asList(attributes.getStringArray("label")));

        List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
        for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
            rollbackRules.add(new RollbackRuleAttribute(rbRule));
        }
        for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
            rollbackRules.add(new RollbackRuleAttribute(rbRule));
        }
        for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
            rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
        }
        for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
            rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
        }
        rbta.setRollbackRules(rollbackRules);

        return rbta;
    }
}

整个逻辑很清晰,主要就是将Spring中@Transactional注解的全部属性值解析出来,然后封装为一个RuleBasedTransactionAttribute实例。

3 事务属性

在Spring Transaction中,TransactionDefinition接口是对事务属性的顶级抽象,RuleBasedTransactionAttribute是其最为常用的实现类。除了隔离级别事务超时时间是否只读事务之外,Spring Transaction对事务属性进一步拓展,衍生出了传播行为回滚规则。在声明式事务场景中,这5大事务属性的值均来源于@Transactional注解。

代码语言:javascript
复制
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    Propagation propagation() default Propagation.REQUIRED;
    Isolation isolation() default Isolation.DEFAULT;
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    boolean readOnly() default false;
    Class<? extends Throwable>[] rollbackFor() default {};
}

3.1 隔离级别

隔离级别的默认值为Isolation.DEFAULT,即默认使用数据库当前的隔离级别;如果数据库所配置的隔离级别为READ-COMMITTED,而isolation属性指定REPEATABLE-READ这一隔离级别,那么该事务的隔离级别最终以REPEATABLE-READ为准。隔离级别完全依赖于数据库本身的实现;对于MySQL,Spring会通过以下语句为当前事务设定隔离级别。

代码语言:javascript
复制
SET SESSION TRANSACTION ISOLATION LEVEL ISOLATION_LEVEL

3.2 是否只读事务

是否只读事务默认值为false;在只读事务中,不允许执行INSERTUPDATEDELETE语句,否则报错:Cannot execute statement in a READ ONLY transaction。同样地,只读事务完全依赖于数据库本身的实现,Spring可没有相关逻辑;在MySQL中,只读事务可以避免生成事务ID所带来的开销;对于MySQL,Spring会通过以下语句为当前事务设定只读模式。

代码语言:javascript
复制
SET SESSION TRANSACTION READ ONLY

3.3 传播行为

事实上,传播行为仅停留在Spring层面,数据库系统中并没有相关概念。

代码语言:javascript
复制
  PROPAGATION_REQUIRED        如果存在外层事务,则加入该事务;否则创建一个事务。       
  PROPAGATION_SUPPORTS        如果存在外层事务,则加入该事务;否则以非事务方式运行。     
  PROPAGATION_MANDATORY       如果存在外层事务,则加入该事务;否则抛出异常。         
  PROPAGATION_REQUIRES_NEW    如果存在外层事务,则挂起外层事务;否则创建一个事务。      
  PROPAGATION_NOT_SUPPORTED   无论是否存在外层事务,始终以非事务方式运行。          
  PROPAGATION_NEVER           如果存在外层事务,则抛出异常;否则以非事务方式运行。      
  PROPAGATION_NESTED          如果存在外层事务,则以嵌套事务方式运行;否则创建一个事务。   

在这七种传播行为中,PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEWPROPAGATION_NESTED最为常用。对于 PROPAGATION_REQUIRED 与 PROPAGATION_NESTED,内外层事务方法会共享一个java.sql.Connection实例对象;而对于 PROPAGATION_REQUIRES_NEW,内外层事务方法会各自持有一个java.sql.Connection实例对象。此外,借助 PROPAGATION_NESTED,即使内层嵌套事务回滚了,外层事务依然可以正常提交,可以通过如下SQL语句模拟 (如果需要正常提交内层嵌套事务,那ROLLBACK TO SAVEPOINT语句可以省略)。

代码语言:javascript
复制
SET autocommit = 0;
INSERT INTO tbl_a(id, name) VALUE ('1', 'java');
SAVEPOINT `SAVEPOINT_1`;
INSERT INTO tbl_b(id, name) VALUE ('1', 'golang');
ROLLBACK TO SAVEPOINT `SAVEPOINT_1`;
COMMIT;

All savepoints of the current transaction are deleted if you execute a COMMIT, or a ROLLBACK that does not name a savepoint.

3.4 回滚规则

Spring Transaction遵循基于异常的回滚规则,那么基于哪些异常呢?如下:

代码语言:javascript
复制
public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute {
    @Override
    public boolean rollbackOn(Throwable ex) {
        return (ex instanceof RuntimeException || ex instanceof Error);
    }
}

默认地,当在事务方法内抛出了RuntimeExceptionERROR,则将触发事务回滚操作。

4 事务同步

在介绍事务同步前,先一起来看两段代码。

  1. 在注册用户场景中,持久化用户后,往往需要通过消息队列通知消费方来给新用户发放积分、开通邮箱等一系列操作。
代码语言:javascript
复制
@Service
public class DemoService {
    @Transactional
    public void demoMtd() {
        userMaper.insert(user);
        rabbitTemplate.convertAndSend("exchange", "routingKey", user);
    }
}

想象一下,当消费方监听到消息后,顺手去用户表中查询该用户,能查到数据吗?肯定查不到的,因为一个事务是读取不到另一事务中还未提交的数据的;如果读到了,建议把DBA拉去祭天。可是读取不到可能对消费方的业务展开有所影响,有什么办法可以解决呢?把@Transactional去掉的确可以,但很不严谨,咋能这么玩呢。解决方案如下:

代码语言:javascript
复制
@Service
public class DemoService {
    @Transactional
    public void demoMtd() {
        userMaper.insert(user);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                rabbitTemplate.convertAndSend("exchange", "routingKey", user);
            }
        });
    }
}
  1. 在主干线程外,单独开启一线程来处理一些与主干业务逻辑松耦合的任务是比较常规的操作。
代码语言:javascript
复制
@Service
public class DemoService {
    @Transactional
    public void demoMtd() {
        userMaper.insert(user);
        new Thread(new Runnable() {
            @Override
            public void run() {
                User _user = userMapper.selectByPrimaryKey("userId");
            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上述代码中,首先在主干线程插入一条记录,然后在另一线程中试图查询这条记录,这能查的到吗?答案依然是查不到。查不到的根本原因在于两个线程中分别创建了两个java.sql.Connection实例,进而主干线程与另一旁路线程运行在两个事务中;为啥两个线程一定会各自创建一个java.sql.Connection实例呢?记住:JDBC API中的java.sql.ConnectionMybatis中的org.apache.ibatis.session.SqlSessionHibernate中的org.hibernate.Session全部是线程不安全的类,两个线程绝不能共享同一个Connection、SqlSession或Session实例。解决方法类似,如下所示:

代码语言:javascript
复制
@Service
public class DemoService {
    @Transactional
    public void demoMtd() {
        userMaper.insert(user);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        User _user = userMapper.selectByPrimaryKey("userId");
                    }
                }).start();
            }
        });

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

显然,TransactionSynchronization是一个标准的回调接口。Spring 5.3对该接口中的所有方法追加了一个default关键字,那么实现该接口的时候就没必要实现所有方法了,自然也就不再需要TransactionSynchronizationAdapter这一适配器了哈。主要内容如下:

代码语言:javascript
复制
public interface TransactionSynchronization {
    // Suspend this synchronization. Supposed to unbind resources from TransactionSynchronizationManager.
    default void suspend() {}
    // Resume this synchronization. Supposed to rebind resources to TransactionSynchronizationManager.
    default void resume() {}
    
    default void beforeCommit(boolean readOnly) {}
    default void beforeCompletion() {}
    default void afterCommit() {}
    default void afterCompletion(int status) {}
}

TransactionSynchronization,中文译为事务同步。为谁同步?当然是事务。那同步什么东西?一种是TransactionSynchronization本体;另一种是所谓的“资源”,资源可以是JDBC API中的java.sql.Connection、Mybatis中的org.apache.ibatis.session.SqlSession,也可以是Hibernate中的org.hibernate.Session。TransactionSynchronization仅从属于一个事务,Spring Transaction不会将一个TransactionSynchronization同步(绑定)到两个事务中;换句话说,TransactionSynchronization中的回调逻辑只会在一个事务中运行。

对开发人员而言,afterCommit()方法是最常用的。官方建议使用该方法来承载一些发送邮件、发送消息等类型的逻辑,并不适合在其内放置一些面向数据库系统的插入与更新操作。

afterCommit()afterCompletion()执行完毕后,Spring Transaction会为当前连接重新开启autocommit模式。

5 声明式事务之核心源码解读

PlatformTransactionManager是事务管理的顶级抽象,它首先会根据TransactionDefinition中的传播行为获取TransactionStatus,然后根据TransactionStatus中的内容来进行提交或回滚。Spring事务管理核心类图如下:

为了统一事务管理的编程模型,Spring Transaction将PlatformTransactionManager设计为一个SPI,同时为了减轻JDBCHibernateJPAJTA等平台的工作量,方便各平台复用基础逻辑,Spring在此基础上又提供了一个抽象类:AbstractPlatformTransactionManager。看到这里,大家也许意识到接口与抽象类的区别了,即使用接口是为了抽象,而使用抽象类却是为了复用

在本文第二章节曾经提到BeanFactoryTransactionAttributeSourceAdvisor,有了这个PointcutAdvisor就可以为那些含有@Transactional注解的事务性Bean创建代理类,最终IoC中所驻留的就是代理类,这样当其它Bean调用事务性Bean中的事务方法时就会被代理类拦截,继而执行TransactionInterceptor中的增强逻辑。至于代理类的生成逻辑,可以参考下图:

下面一起来看看TransactionInterceptor中的内容,它实现了MethodInterceptor,那代理类的增强逻辑必然落在invoke()方法内,核心内容如下:

代码语言:javascript
复制
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
            @Override
            public Object proceedWithInvocation() throws Throwable {
                return invocation.proceed();
            }
            @Override
            public Object getTarget() {
                return invocation.getThis();
            }
            @Override
            public Object[] getArguments() {
                return invocation.getArguments();
            }
        });
    }
}

接着,进入到TransactionAspectSupport中的invokeWithinTransaction()方法:

代码语言:javascript
复制
public abstract class TransactionAspectSupport {
    protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
        // 获取TransactionAttribute解析器
        TransactionAttributeSource tas = getTransactionAttributeSource();
        // 获取TransactionAttribute,不再重新计算,直接从attributeCache这一成员变量中拿到
        final TransactionAttribute txAttr = tas.getTransactionAttribute(method, targetClass);
        // 获取TransactionManager
        final TransactionManager tm = determineTransactionManager(txAttr);
        // 获取事务名称
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
        // 如果非事务方法或非JTA事务
        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            // 根据TransactionAttribute中的事务传播行为创建事务对象
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            // 目标方法执行结果
            Object retVal;
            try {
                // 执行目标方法
                retVal = invocation.proceedWithInvocation();
            } catch (Throwable ex) {
                // 回滚事务
                completeTransactionAfterThrowing(txInfo, ex);
                // 再次抛出异常
                throw ex;
            } finally {
                // 在TransactionAspectSupport中有一ThreadLocal类型的成员变量transactionInfoHolder
                // TransactionInfo中持有一oldTransactionInfo成员变量
                // cleanupTransactionInfo主要就是更新transactionInfoHolder的值
                cleanupTransactionInfo(txInfo);
            }
            // 提交事务
            commitTransactionAfterReturning(txInfo);
            // 返回目标方法执行结果
            return retVal;
        }
    }
}

上述invokeWithinTransaction()方法勾勒出声明式事务的整体逻辑,如下图所示:

特别地,我们需要对createTransactionIfNecessary()commitTransactionAfterReturning()completeTransactionAfterThrowing()这三个方法进行逐一剖析!

5.1 createTransactionIfNecessary()

代码语言:javascript
复制
public abstract class TransactionAspectSupport {
    protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, 
                                                           TransactionAttribute txAttr, 
                                                           String joinpointIdentification) {
        TransactionStatus status = null;
        if (txAttr != null) {
            if (tm != null) {
                // 通过TransactionManager获取TransactionStatus实例
                status = tm.getTransaction(txAttr);
            }
        }
        // 将TransactionStatus、TransactionAttribute封装为TransactionInfo实例
        // TransactionInfo实例将贯穿整个事务管理生命周期
        return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }
}

进入getTransaction()方法:

代码语言:javascript
复制
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager {
    @Override
    public final TransactionStatus getTransaction(TransactionDefinition definition) {
        // 如果definition为null,则使用默认的TransactionDefinition实例
        TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
        // 创建事务对象,一般是DataSourceTransactionObject
        // 注意:即使内外层事务共用一个java.sql.Connection实例,也会创建两个事务对象
        Object transaction = doGetTransaction();
        // 判断当前是否已存在事务
        // 如果definition来源于外层事务方法,不可能存在事务的
        // 如果definition来源于内存事务方法,是可能存在外层事务的
        if (isExistingTransaction(transaction)) {
            // 既然存在外层事务,则需要根据内层事务方法所声明的事务传播行为来采取不同的处理策略
            // 1)对于PROPAGATION_NEVER,直接抛出异常
            // 2)对于PROPAGATION_NOT_SUPPORTED,挂起外层事务
            // 3)对于PROPAGATION_REQUIRES_NEW,挂起外层事务,开启新事务
            // 4)对于PROPAGATION_NESTED,创建savepoint
            // 5)对于PROPAGATION_SUPPORTS和PROPAGATION_REQUIRED,直接加入外层事务
            return handleExistingTransaction(def, transaction, debugEnabled);
        }
        // 逻辑执行至此,说明不存在外层事务,进而推断该definition来源于外层事务方法
        // 如果外层事务方法所声明的事务传播行为是PROPAGATION_MANDATORY,那直接抛出异常
        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) {
            // 逻辑执行至此,说明外层事务方法所声明的事务传播行为一定是PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED
            // 开启新事务
            return startTransaction(def, transaction, debugEnabled, suspendedResources);
        } else {
            // 逻辑执行至此,说明外层事务方法所声明的事务传播行为只能是下面几种了:
            // 1)PROPAGATION_NEVER
            // 2)PROPAGATION_NOT_SUPPORTED
            // 3)PROPAGATION_SUPPORTS
            // 外层事务方法所声明的事务传播行为属于上述三种之一,声明个寂寞啊,有毛线作用
            // 开启空事务
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
        }
    }
}

请大家自行阅读handleExistingTransaction()方法中的内容,这里仅解读startTransaction()方法:

代码语言:javascript
复制
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager {
    private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
                                               boolean debugEnabled, SuspendedResourcesHolder suspendedResources) {
        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
        DefaultTransactionStatus status = newTransactionStatus(
                definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
        doBegin(transaction, definition);
        prepareSynchronization(status, definition);
        return status;
    }
}

进入doBegin()方法:

代码语言:javascript
复制
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager {
    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();
                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());
            
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                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);
        }
    }
}

doBegin() 方法中的重要逻辑有:

  1. 建立连接并将连接关联到事务对象中;
代码语言:javascript
复制
Connection newCon = obtainDataSource().getConnection();
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
  1. 设定事务隔离级别与是否只读事务;
代码语言:javascript
复制
public class DataSourceUtils {
    public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition) throws SQLException {
        boolean debugEnabled = logger.isDebugEnabled();
        // Set read-only flag.
        if (definition != null && definition.isReadOnly()) {
            try {
                if (debugEnabled) {
                    logger.debug("Setting JDBC Connection [" + con + "] read-only");
                }
                con.setReadOnly(true);
            } catch (SQLException | RuntimeException ex) {
                // "read-only not supported" SQLException -> ignore, it's just a hint anyway
                logger.debug("Could not set JDBC Connection read-only", ex);
            }
        }

        // Apply specific isolation level, if any.
        Integer previousIsolationLevel = null;
        if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
            if (debugEnabled) {
                logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " + definition.getIsolationLevel());
            }
            int currentIsolation = con.getTransactionIsolation();
            if (currentIsolation != definition.getIsolationLevel()) {
                previousIsolationLevel = currentIsolation;
                con.setTransactionIsolation(definition.getIsolationLevel());
            }
        }
        return previousIsolationLevel;
    }
}
  1. 关闭 autocommit 模式;
代码语言:javascript
复制
con.setAutoCommit(false);
  1. 绑定当前连接对象到当前线程
代码语言:javascript
复制
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());

5.2 commitTransactionAfterReturning()

代码语言:javascript
复制
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager {
    private void processCommit(DefaultTransactionStatus status) throws TransactionException {
        try {
            boolean beforeCompletionInvoked = false;
            try {
                boolean unexpectedRollback = false;
                prepareForCommit(status);
                triggerBeforeCommit(status);
                triggerBeforeCompletion(status);
                beforeCompletionInvoked = true;

                if (status.hasSavepoint()) {
                    if (status.isDebug()) {
                        logger.debug("Releasing transaction savepoint");
                    }
                    unexpectedRollback = status.isGlobalRollbackOnly();
                    status.releaseHeldSavepoint();
                } else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction commit");
                    }
                    unexpectedRollback = status.isGlobalRollbackOnly();
                    doCommit(status);
                } else if (isFailEarlyOnGlobalRollbackOnly()) {
                    unexpectedRollback = status.isGlobalRollbackOnly();
                }

                // Throw UnexpectedRollbackException if we have a global rollback-only
                // marker but still didn't get a corresponding exception from commit.
                if (unexpectedRollback) {
                    throw new UnexpectedRollbackException("Transaction silently rolled back because it has been marked as rollback-only");
                }
            } catch (UnexpectedRollbackException ex) {
                // can only be caused by doCommit
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
                throw ex;
            } catch (TransactionException ex) {
                // can only be caused by doCommit
                if (isRollbackOnCommitFailure()) {
                    doRollbackOnCommitException(status, ex);
                } else {
                    triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                }
                throw ex;
            } catch (RuntimeException | Error ex) {
                if (!beforeCompletionInvoked) {
                    triggerBeforeCompletion(status);
                }
                doRollbackOnCommitException(status, ex);
                throw ex;
            }
            // Trigger afterCommit callbacks, with an exception thrown there
            // propagated to callers but the transaction still considered as committed.
            try {
                triggerAfterCommit(status);
            } finally {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
            }
        } finally {
            cleanupAfterCompletion(status);
        }
    }
}

再看doCommit()方法:

代码语言:javascript
复制
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager {
    @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 translateException("JDBC commit", ex);
        }
    }
} 

5.3 completeTransactionAfterThrowing()

代码语言:javascript
复制
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager {
    private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
        try {
            boolean unexpectedRollback = unexpected;
            try {
                triggerBeforeCompletion(status);

                if (status.hasSavepoint()) {
                    if (status.isDebug()) {
                        logger.debug("Rolling back transaction to savepoint");
                    }
                    status.rollbackToHeldSavepoint();
                } else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction rollback");
                    }
                    doRollback(status);
                } else {
                    // Participating in larger transaction
                    if (status.hasTransaction()) {
                        if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                            if (status.isDebug()) {
                                logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                            }
                            doSetRollbackOnly(status);
                        } else {
                            if (status.isDebug()) {
                                logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                            }
                        }
                    } else {
                        logger.debug("Should roll back transaction but cannot - no transaction available");
                    }
                    // Unexpected rollback only matters here if we're asked to fail early
                    if (!isFailEarlyOnGlobalRollbackOnly()) {
                        unexpectedRollback = false;
                    }
                }
            } catch (RuntimeException | Error ex) {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                throw ex;
            }
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
            // Raise UnexpectedRollbackException if we had a global rollback-only marker
            if (unexpectedRollback) {
                throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");
            }
        } finally {
            cleanupAfterCompletion(status);
        }
    }
}

再看doRollback()方法:

代码语言:javascript
复制
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager {
    @Override
    protected void doRollback(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        if (status.isDebug()) {
            logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
        }
        try {
            con.rollback();
        } catch (SQLException ex) {
            throw translateException("JDBC rollback", ex);
        }
    }
} 

6. 总结

相信大家在看完本文之后,对Spring声明式事务管理的理解更加深入了,但肯定还会有很多疑问,这就需要大家自行研读源码了。最后强调两点,一是要正确理解事务的传播行为;二是要注意声明式事务的粗粒度问题,不可在@Transactional方法内大量使用远程调用,这会造成大事务,大事务可能导致回滚时间变长、数据库连接池耗尽等问题,因此建议搭配使用编程式事务管理。

参考文档

  1. https://dev.mysql.com/doc/refman/8.0/en/set-transaction.html
  2. https://dev.mysql.com/doc/refman/8.0/en/innodb-autocommit-commit-rollback.html
  3. https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
  4. https://docs.spring.io/spring-framework/docs/5.3.13/reference/html/data-access.html#transaction
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿杜小头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 1 开启事务管理机制
  • 2 深入理解@EnableTransactionManagement注解
  • 3 事务属性
    • 3.1 隔离级别
      • 3.2 是否只读事务
        • 3.3 传播行为
          • 3.4 回滚规则
          • 4 事务同步
          • 5 声明式事务之核心源码解读
            • 5.1 createTransactionIfNecessary()
              • 5.2 commitTransactionAfterReturning()
                • 5.3 completeTransactionAfterThrowing()
                • 6. 总结
                • 参考文档
                相关产品与服务
                云数据库 SQL Server
                腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档