Spring事務傳播特性的淺析——事務方法嵌套調用的迷茫

 

Spring事務傳播機制回顧 


   Spring事務一個被訛傳很廣說法是:一個事務方法不該該調用另外一個事務方法,不然將產生兩個事務。結果形成開發人員在設計事務方法時束手束腳,生怕一不當心就踩到地雷。 
其實這是不認識Spring事務傳播機制而形成的誤解,Spring對事務控制的支持統一在TransactionDefinition類中描述,該類有如下幾個重要的接口方法: mysql

  • int getPropagationBehavior():事務的傳播行爲
  • int getIsolationLevel():事務的隔離級別
  • int getTimeout():事務的過時時間
  • boolean isReadOnly():事務的讀寫特性



   很明顯,除了事務的傳播行爲外,事務的其餘特性Spring是藉助底層資源的功能來完成的,Spring無非只充當個代理的角色。可是事務的傳播行爲倒是Spring憑藉自身的框架提供的功能,是Spring提供給開發者最珍貴的禮物,訛傳的說法玷污了Spring事務框架最美麗的光環。 
   
   所謂事務傳播行爲就是多個事務方法相互調用時,事務如何在這些方法間傳播。Spring支持如下7種事務傳播行爲。 spring

  • PROPAGATION_REQUIRED:若是當前沒有事務,就新建一個事務,若是已經存在一個事務,就加入到這個事務中。這是最多見的選擇。
  • PROPAGATION_SUPPORTS:支持當前事務,若是當前沒有事務,就以非事務方式執行。
  • PROPAGATION_MANDATORY:使用當前的事務,若是當前沒有事務,就拋出異常。
  • PROPAGATION_REQUIRES_NEW:新建事務,若是當前存在事務,把當前事務掛起。
  • PROPAGATION_NOT_SUPPORTED:以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。
  • PROPAGATION_NEVER:以非事務方式執行,若是當前存在事務,則拋出異常。
  • PROPAGATION_NESTED:若是當前存在事務,則在嵌套事務內執行。若是當前沒有事務,則執行與PROPAGATION_REQUIRED相似的操做。



   Spring默認的事務傳播行爲是PROPAGATION_REQUIRED,它適合絕大多數的狀況,若是多個ServiveX#methodX()均工做在事務環境下(即均被Spring事務加強),且程序中存在以下的調用鏈:Service1#method1()->Service2#method2()->Service3#method3(),那麼這3個服務類的3個方法經過Spring的事務傳播機制都工做在同一個事務中。 

相互嵌套的服務方法 
   sql

   咱們來看一下實例,UserService#logon()方法內部調用了UserService#updateLastLogon Time()和ScoreService#addScore()方法,這兩個類都繼承於BaseService。它們之間的類結構以下圖所示:express

   UserService#logon()方法內部調用了ScoreService#addScore()的方法,二者都分別經過Spring AOP進行了事務加強,則它們工做於同一事務中。來看具體的代碼: 框架

 

package com.baobaotao.nestcall;
…
@Service("userService")
public class UserService extends BaseService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private ScoreService scoreService;
    
    //①該方法嵌套調用了本類的其餘方法及其餘服務類的方法
    public void logon(String userName) {
        System.out.println("before userService.updateLastLogonTime...");
        updateLastLogonTime(userName);//①-1本服務類的其餘方法
        System.out.println("after userService.updateLastLogonTime...");
        
        System.out.println("before scoreService.addScore...");
        scoreService.addScore(userName, 20); //①-2其餘服務類的其餘方法
        System.out.println("after scoreService.addScore...");

    }
    public void updateLastLogonTime(String userName) {
        String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
        jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
    }

 

UserService中注入了ScoreService的Bean,而ScoreService的代碼以下所示: ui

 

package com.baobaotao.nestcall;
…
@Service("scoreUserService")
public class ScoreService extends BaseService{

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void addScore(String userName, int toAdd) {
        String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";
        jdbcTemplate.update(sql, toAdd, userName);
    }
}

  經過Spring配置爲ScoreService及UserService中全部公有方法都添加Spring AOP的事務加強,讓UserService的logon()和updateLastLogonTime()及ScoreService的addScore()方法都工做於事務環境下。下面是關鍵的配置代碼: spa

 

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
    <context:component-scan base-package="com.baobaotao.nestcall"/><bean id="jdbcManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
          p:dataSource-ref="dataSource"/>

    <!--①經過如下配置爲全部繼承BaseService類的全部子類的全部public方法都添加事務加強-->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="serviceJdbcMethod"
                      expression="within(com.baobaotao.nestcall.BaseService+)"/>
        <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/>
    </aop:config>
    <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
</beans>

將日誌級別設置爲DEBUG,啓動Spring容器並執行UserService#logon()的方法,仔細觀察以下輸出日誌: .net

 

before userService.logon method... 

     //①建立了一個事務 
Creating new transaction with name [com.baobaotao.nestcall.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT 
Acquired Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction 
Switching JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] to manual commit 
before userService.updateLastLogonTime... 

    <!--②updateLastLogonTime()和logon()在同一個Bean中,並未發生加入已存在事務上下文的 
      動做,而是「自然」地工做於相同的事務上下文--> 
Executing prepared SQL update 
Executing prepared SQL statement [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?] 
SQL update affected 1 rows 
after userService.updateLastLogonTime... 
before scoreService.addScore... 

//③ScoreService#addScore方法加入到①處啓動的事務上下文中 
Participating in existing transaction Executing prepared SQL update 
Executing prepared SQL statement [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?] 
SQL update affected 1 rows 
after scoreService.addScore... 
Initiating transaction commit 
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] 
… 
after userService.logon method... 

 

從上面的輸出日誌中,能夠清楚地看到Spring爲UserService#logon()方法啓動了一個新的事務,而UserSerive#updateLastLogonTime()和UserService#logon()是在相同的類中,沒有觀察到有事務傳播行爲的發生,其代碼塊好像「直接合並」到UserService#logon()中。 設計

然而在執行到ScoreService#addScore()方法時,咱們就觀察到發生一個事務傳播的行爲:" Participating in existing transaction ",
這說明ScoreService#addScore()添加到UserService#logon()的事務上下文中,二者共享同一個事務
因此最終的結果是UserService的logon()、updateLastLogonTime()以及ScoreService的addScore都工做於同一事務中。 代理

 

注:以上內容摘自《Spring 3.x企業應用開發實戰》 

http://blog.csdn.net/hy6688_/article/details/44763869

相關文章
相關標籤/搜索