Spring 4.3 源码分析之 编程/声明式事务

1. Spring Tx 编程

整个Spring事务可以通过编程与声明两种方式进行操作:

1. AbstractPlatformTransactionManager: 通过其子类来自己控制事务开始, 提交, 回滚等操作
2. TransactionTemplate: 通过传入一个回调函数来控制程序的执行, 其内部还是通过 AbstractPlatformTransactionManager的子类
3. TransactionProxyFactoryBean: 通过在配置文件中配置 transactionAttributes 来决定对哪些方法进行事务的作用, 此时的 TransactionAttributes获取器是 NameMatchTransactionAttributeSource <-- 这站方式使用得比较少了
4. 通过 Tx namespace 的方式来声明事务
5. 通过在方法上标注 @Transactional 注解的方式来实现声明式事务
2. Spring Tx 编程式事务 AbstractPlatformTransactionManager

AbstractPlatformTransactionManager 是事务操作的基础类, 它具有 begin(事务开启), suspend(挂起事务, 比如 PROPAGATION_REQUIRES_NEW 级别), resume (将挂起的事务重用, 往往在上个事务提交会回滚后), commit(事务的提交), rollback(事务的回滚, 默认我们遇到 RuntimeException | Error 时回滚), 当然这些方法都是模版方法, 都是留给对应ORM的子类去实现的, 比如我们最常用的 DataSourceTransactionManager, 下面的 Demo 就是基于 DataSourceTransactionManager :

private static final String CREATE_TABLE_SQL = "create table test" +
        "(id int(10) AUTO_INCREMENT PRIMARY KEY, " +
        "name varchar(100))";
private static final String DROP_TABLE_SQL = "drop table test";
private static final String INSERT_SQL = "insert into test(name) values(?)";
private static final String COUNT_SQL = "select count(*) from test";

public static void main(String[] args) {
    // 定义连接池
    DataSource dataSource = getDataSource();
    // 先建 PlatformTransactionManager
    DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    // 定义 TransactionDefinition
    DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
    // 获取 TransactionStatus
    TransactionStatus transactionStatus = transactionManager.getTransaction(definition);
    // 从连接池中获取 <- 这里其实是将 DataSource <--> Connection 放到 ThreadLocal 里面
    Connection con = DataSourceUtils.getConnection(dataSource);
    try {
        // 执行 SQL
        con.prepareStatement(CREATE_TABLE_SQL).execute();
        PreparedStatement pstmt = con.prepareStatement(INSERT_SQL);
        pstmt.setString(1, "test");
        pstmt.execute();
        ResultSet resultSet = con.prepareStatement(COUNT_SQL).executeQuery();
        // 打印查询结果
        while(resultSet.next()){ System.out.println(resultSet.getString(1));}
        con.prepareStatement(DROP_TABLE_SQL).execute();
        transactionManager.commit(transactionStatus);
    } catch (Exception e) { // 遇到异常就直接回滚
        transactionManager.rollback(transactionStatus);
    } 
}

// 设置数据库连接池
public static DataSource getDataSource(){
    org.apache.commons.dbcp.BasicDataSource basicDataSource = new org.apache.commons.dbcp.BasicDataSource();
    basicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
    basicDataSource.setUrl("jdbc:mysql://localhost:3306/tuomatuo?useUnicode=true&characterEncoding=UTF8");
    basicDataSource.setUsername("root");
    basicDataSource.setPassword("123456");
    return basicDataSource;
}

在上面的代码中出现了一个新的角色 DataSourceUtils, 这个类主要是通过 TransactionSynchronizationManager 对 ThreadLocal 中存储的 DataSource 的操作; 以上代码主要是如下几步:

1. 新建数据库连接 DataSource
2. 新建 DataSourceTransactionManager, 将 DataSource 赋值到其中
3. 构建默认的事务配置器 TransactionDefinition
4. 通过 DataSourceTransactionManager 获取事务状态器 TransactionStatus (此时已经将 Connection 等事务配置信息存储到 ThreadLocal 中)
5. 通过 DataSourceUtils.getConnection(dataSource) 获取存储在 ThreadLocal 中的 Connection, 接着就是直接对 数据库的操作
6. 直接成功提交事务, 失败的话就会直接回滚事务

PS: 在运行 Demo 时数据库的连接替换成本地环境的参数

3. Spring Tx 编程式事务 TransactionTemplate

TransactionTemplate其实只是在 AbstractPlatformTransactionManager上面做了一个简单的封装, 通过传递一个回调函数 TransactionCallback 来执行数据库的操作, 其他部分的操作与直接使用 AbstractPlatformTransactionManager 是一样的

<!-- 设置目标 -->
<bean id="testBeanTarget" class="org.springframework.tests.sample.beans.TestBean">
    <property name="name"><value>dependency</value></property>
</bean>

<!-- 配置 dataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="validationQuery">
        <value>select 1 from dual</value>
    </property>
</bean>

<!-- 配置 DataSourceTransactionManager -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置 TransactionProxyFactoryBean -->
<bean id="transactionProxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <!-- 代理的目标类 -->
    <property name="target"><ref local="testBeanTarget"/></property>
    <!-- 配置 transactionManager -->
    <property name="transactionManager"><ref local="transactionManager"/></property>
    <!-- 设置 transactionAttributes, 这里使用 NameMatchTransactionAttributeSource 来获取 transactionAttribute 信息 -->
    <!-- 在父类 TransactionAspectSupport.invokeWithinTransaction 中会用到 -->
    <property name="transactionAttributes">
        <props>
            <prop key="s*">PROPAGATION_MANDATORY</prop>
            <prop key="setAg*">  PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="set*">PROPAGATION_SUPPORTS</prop>
        </props>
    </property>
</bean>

从上面的 Demo 中发现, 其实整个配置信息不复杂, 缺点是 TransactionProxyFactoryBean 是针对单个 Target, 若项目中出现非常多需要事务控制的方法, 则需要配置非常多的 TransactionProxyFactoryBean, 所以出现了基于 Tx NameSpace 与 基于注解 @Transactional 的事务

4. Spring Tx 基于 Tx Namespace 的事务配置

基于 Tx nameSpace 的事务配置, 其实就是在 BeanFactory 中注入 TransactionInterceptor, NameMatchTransactionAttributeSource , 其中 TransactionInterceptor 是个 MethodInterceptor, 通过对目标对象代理, 而拦截器 TransactionInterceptor 中就是完成事务的操作, 先看一个对应的事务配置信息:

<!-- 配置拦截器 -->
<tx:advice id="txAdvice"> <!-- TxAdviceBeanDefinitionParser 解析标签成 org.springframework.transaction.interceptor.TransactionInterceptor -->
    <tx:attributes>       <!-- TxAdviceBeanDefinitionParser 解析标签成 NameMatchTransactionAttributeSource -> String, TransactionAttribute -->
        <tx:method name="get*" read-only="true"/>
        <tx:method name="set*"/>
        <tx:method name="exceptional"/>
    </tx:attributes>
</tx:advice>

<!-- 配置 AspectJAwareAdvisorAutoProxyCreator 与 DefaultBeanFactoryPointcutAdvisor -->
<aop:config> <!-- 解析成 DefaultBeanFactoryPointcutAdvisor 与 AspectJExpressionPointcut <- 对应的表达式是 "execution (* com.lami.mhao.service.*.add*(..))" -->
    <aop:advisor pointcut="execution (* com.lami.mhao.service.*.add*(..))" advice-ref="txAdvice"/>
</aop:config>

从上面的配置我们可以看到, 原来 事务命名空间这种配置其实就是在 BeanFactory 中注入 MethodInterceptor <--TransactionInterceptor, 在 TransactionInterceptor 中通过 PlatformTransactionManager 来进行事务操作, 在 TransactionInterceptor 中的操作如下:

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
        throws Throwable {
    // If the transaction attribute is null, the method is non-transactional.
    // 这里读取事务的属性和设置, 通过 TransactionAttributeSource 对象取得
    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    // 获取 beanFactory 中的 transactionManager
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    // 构造方法唯一标识(类, 方法, 如 service.UserServiceImpl.save)
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    /**
     * 这里区分不同类型的 PlatformTransactionManager 因为它们的调用方式不同
     * 对 CallbackPreferringPlatformTransactionManager 来说, 需要回调函数来
     * 实现事务的创建和提交
     * 对于非 CallbackPreferringPlatformTransactionManager 来说, 不需要通过
     * 回调函数来实现事务的创建和提交
     * 像 DataSourceTransactionManager 就不是 CallbackPreferringPlatformTransactionManager
     * 不需要通过回调的方式来使用
     */
    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        // Standard transaction demarcation with getTransaction and commit/rollback calls.
        // 这里创建事务, 同时把创建事务过程中得到的信息放到 TransactionInfo 中去 (创建事务的起点)
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            // This is an around advice: Invoke the next interceptor in the chain.
            // This will normally result in a target object being invoked.
            // 这里的调用使用处理沿着拦截器链进行, 使最后目标对象的方法得到调用
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // target invocation exception
            // 如果在事务处理方法调用中出现异常, 事务处理如何进行需要根据具体的情况考虑回滚或者提交
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            // 这里把与线程绑定的 TransactionInfo 设置为 oldTransactionInfo
            cleanupTransactionInfo(txInfo);
        }
        // 这里通过事务处理器来对事务进行提交
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
}

其中非常重要的 TransactionAttribute 是从 NameMatchTransactionAttributeSource(这个类也是通过解析 xml 时注入的) 中获取的, 主流程如下:

// 对调用的方法进行判断, 判断它是否是事务方法, 如果是事务方法, 那么取出相应的事务配置属性
@Override
public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {
    // 判断当前目标调用的方法与配置的事务方法是否直接匹配
    if (!ClassUtils.isUserLevelMethod(method)) {
        return null;
    }

    // Look for direct name match.
    String methodName = method.getName();
    // 通过方法名从 nameMap 中获取 attr
    TransactionAttribute attr = this.nameMap.get(methodName);
    // 如果不能直接匹配, 就通过 PatternMatchUtils 的 simpleMatch 方法来进行匹配判断
    if (attr == null) {
        // Look for most specific name match.
        String bestNameMatch = null;
        for (String mappedName : this.nameMap.keySet()) {
            if (isMatch(methodName, mappedName) &&          // 方法名称匹配 <- 正则匹配
                    (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
                attr = this.nameMap.get(mappedName);        // 匹配成功, 则直接获取 TransactionAttribute <- 这里的 TransactionAttribute 是在解析 xml 时注入 BeanFactory 的
                bestNameMatch = mappedName;
            }
        }
    }

    return attr;                                            // 返回通过 methodName 获取的 attr
}

PS: 对应的 Tx Namespace 解析器是 TxAdviceBeanDefinitionParser <- 代码量不多

5. Spring Tx 基于注解 @Transactional 的事务

在使用这种形式的事务时, 只需要在配置文件中加上 <tx:annotation-driven />, 接着就可以在方法上加上 @Transactional 就能通过对某个方法内的 SQL 操作进行事务操作, 其中主要的参与者是:

1. AbstractAdvisorAutoProxyCreator: 自动Advisor收集, 自动代理创建器
2. AnnotationTransactionAttributeSource: 通过获取&解析方法上@Transactional 来获取 TransactionAttribute 的 TransactionAttributeSource
3. TransactionInterceptor: 一个 MethodInterceptor, 正真的事务操作其实就是在这个类中, 本质上也是通过 PlatformTransactionManager 来操作的
4. BeanFactoryTransactionAttributeSourceAdvisor: 一个基于TransactionAttributeSourcePointcut 的 Advisor, 这种 Pointcut 是通过 AnnotationTransactionAttributeSource, SpringTransactionAnnotationParser 解析方法上的 @Transactional 注解获取 TransactionAttribute 来确定是否匹配成功的 Pointcut

<tx:annotation-driven /> 的解析器则是: org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser, 主要流程如下:

public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
    // 若 BeanFactory 中没有注入 自动代理创建器, 则将注入 InfrastructureAdvisorAutoProxyCreator
    AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);

    String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
    // 没有注入过 internalTransactionAdvisor
    if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
        Object eleSource = parserContext.extractSource(element);

        // Create the TransactionAttributeSource definition.
        RootBeanDefinition sourceDef = new RootBeanDefinition(      // 注入 AnnotationTransactionAttributeSource (TransactionAttribute 提取器, 其主要是通过注释在方法上的 @Transactional 获取 TransactionAttribute)
                "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
        sourceDef.setSource(eleSource);
        sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);      // 这里标志 AnnotationTransactionAttributeSource 是 IOC 的基础类 <- 针对基础类不会进行 AOP 等操作
        String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);

        // Create the TransactionInterceptor definition.            // 注入 TransactionInterceptor, 正真的事务操作就是在这里拦截器里面 <- 主要还是通过 PlatformTransactionManager
        RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
        interceptorDef.setSource(eleSource);
        interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // 标志这是个基础类 <- 基础类不会被 AOP 等特性操作
        registerTransactionManager(element, interceptorDef);        // 设置 transactionManager 的 beanName
        interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
        // 注册 TransactionInterceptor, 并使用 Spring 中的定义规则生成 beanName
        String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);

        // 注入 BeanFactoryTransactionAttributeSourceAdvisor, 这个类中的 Pointcut 是 TransactionAttributeSourcePointcut, 而
        // 只要 TransactionAttributeSource 通 Method, Class 中获取 TransactionAttribute 就表示这个方法将被事务操作
        // Create the TransactionAttributeSourceAdvisor definition.
        RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
        advisorDef.setSource(eleSource);
        advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);     // 标志这是个基础类 <- 基础类不会被 AOP 等特性操作
        // 将 sourceName 的 bean 注入 advisorDef 的 transactionAttributeSource 属性中
        advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
        advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
        // 如果配置了 order 属性, 则加入到 bean 中
        if (element.hasAttribute("order")) {
            advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
        }
        // 注入 BeanFactoryTransactionAttributeSourceAdvisor
        parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);
        // 创建 CompositeComponentDefinition <- 其中包含上面的需要注入的 BeanDefinition <-- 组合模式
        CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
        compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
        compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
        compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
        parserContext.registerComponent(compositeDef);
    }
}
6. 总结

在 Spring 中整个事务的操作是围绕 PlatformTransactionManager 来进行操作的, PlatformTransactionManager 中封装了 begin(开启事务), suspend(将先前的事务挂起来 传播级别可能就是PROPAGATION_REQUIRES_NEW), resume(重用先前的事务属性), commit(提交事务), 这些方法就是模版方法, 在 AbstractPlatformTransactionManager中有着整个操作的逻辑, 对应的 ORM 只需要实现对应模版方法就可以(比如 我们最常用的 DataSourceTransactionManager) ; 而需要理解我们最常用的, 基于 @Transactional 的事务, 需要先理解以下 Aop 这样就是非常好理解, 对应的 Pointcut 其实就是通过在方法上获取 @Transactional 的信息来决定!

7. 参考:

Spring Aop核心源码分析
Spring技术内幕
Spring 揭秘
Spring 源码深度分析
开涛 Spring 杂谈
伤神 Spring 源码分析
Spring源码情操陶冶

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,355评论 6 343
  • 这部分的参考文档涉及数据访问和数据访问层和业务或服务层之间的交互。 Spring的综合事务管理支持覆盖很多细节,然...
    竹天亮阅读 1,010评论 0 0
  • 很多人喜欢这篇文章,特此同步过来 由浅入深谈论spring事务 前言 这篇其实也要归纳到《常识》系列中,但这重点又...
    码农戏码阅读 4,660评论 2 59
  • 当前,基金作为一种理财工具已经为大多数老百姓所接受。普通老百姓希望通过对基金的投资来分享我国经济高速增长的成果。但...
    中投长城阅读 330评论 0 0