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閱讀 227,283評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,637評論 2 370

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,775評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,885評論 6 342
  • 這部分的參考文檔涉及數據訪問和數據訪問層和業務或服務層之間的交互。 Spring的綜合事務管理支持覆蓋很多細節,然...
    竹天亮閱讀 1,043評論 0 0
  • 很多人喜歡這篇文章,特此同步過來 由淺入深談論spring事務 前言 這篇其實也要歸納到《常識》系列中,但這重點又...
    碼農戲碼閱讀 4,751評論 2 59
  • 當前,基金作為一種理財工具已經為大多數老百姓所接受。普通老百姓希望通過對基金的投資來分享我國經濟高速增長的成果。但...
    中投長城閱讀 347評論 0 0