1. 程式人生 > 實用技巧 >Spring 與 MyBatis 事務管理原始碼解析

Spring 與 MyBatis 事務管理原始碼解析

  • 用到mybatis便由spring和myabtis整合,SqlSessionFactoryBean(直接負責對mybatis所需環境的建立) ,配置相應的datasource到springConfig檔案中,並將datasource注入到SqlSessionFactoryBean的例項化到容器中,依據他建立sqlsession到spring容器中,便可呼叫sqlssion執行對資料庫的操作(以下的三個都由Spring 的容器ClassPathXmlApplicationContext進行管理
<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm"/>
        <property name="user" value="root"/>
        <property name="password" value="123456"/>
    </bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="comboPooledDataSource"/>
</bean>

<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="cn.itcast.dao"/>
</bean>
  • 在對@Repository的類的介面類呼叫時,MapperScannerConfigurer將Dao層加到Spring容器中,以便Service層呼叫
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

	//介面包
	
    private String basePackage;
    private boolean addToConfig = true;
    private SqlSessionFactory sqlSessionFactory;
    private SqlSessionTemplate sqlSessionTemplate;
    private String sqlSessionFactoryBeanName;
    private String sqlSessionTemplateBeanName;
    private Class<? extends Annotation> annotationClass;
    private Class<?> markerInterface;
    private ApplicationContext applicationContext;
    private String beanName;
    private boolean processPropertyPlaceHolders;
    private BeanNameGenerator nameGenerator;
     ···
     get/set方法用以注入
     ···
}
  • 獲取事務屬性物件(TransactionAttributes若無設定attributes則預設為DefaultTransactionDefinition),放置在容器中,transactionManager呼叫getTransaction時作為引數傳入

    事務屬性物件持有事務的相關配置,比如事務的隔離級別,傳播行為,是否只讀等。我們開啟spring事務管理時,通常都會在配置檔案里加入這樣一段配置。

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    
    • TransactionDefinition主要定義了有哪些事務屬性可以指定:

      • 事務的隔離級別(Isolation)

      • 事務的傳播行為(Propagation Behavior)

        預設為PROPAGATION_REQUIRED ,若存在事務則加入當前事務,若沒有則自己新建一個事務。

        Propagation Behavior注意PROPAGATION_REQUIRES_NEW和PROPAGATION_NESTED的區別,前者將當前事務掛起,建立新的事務執行;後者,則是在當前事務種的一個巢狀事務中執行

      • 事務的超時時間(Timeout)

      • 是否為只讀事務(ReadOnly)

      • SQL標準定義了4類隔離級別,包括了一些具體規則,用來限定事務內外的哪些改變是可見的,哪些是不可見的。低級別的隔離級一般支援更高的併發處理,並擁有更低的系統開銷。

      • ISOLATION_DEFAULT 資料庫的預設級別:mysql為Repeatable Read(可重讀),其他的一般為Read Committed(讀取提交內容)

      • Read Uncommitted(讀取未提交內容) ISOLATION_READ_UNCOMITTED

        在該隔離級別,所有事務都可以看到其他未提交事務的執行結果。本隔離級別很少用於實際應用,因為它的效能也不比其他級別好多少。讀取未提交的資料,也被稱之為髒讀(Dirty Read)。

      • Read Committed(讀取提交內容)ISOLATION_READ_COMITTED

      ​ 這是大多數資料庫系統的預設隔離級別(但不是MySQL預設的)。它滿足了隔離的簡單定義:一個事務只能看見已經提交事務所做的改變。這種隔離級別 也支援所謂的**不可重複讀(Nonrepeatable Read),如果一個使用者在一個事務中多次讀取一條資料,而另外一個使用者則同時更新啦這條資料,造成第一個使用者多次讀取資料不一致。****

      • Repeatable Read(可重讀)ISOLATION_REPEATABLE_READ

      這是MySQL的預設事務隔離級別,它確保同一事務的多個例項在併發讀取資料時,會看到同樣的資料行。不過理論上,這會導致另一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指第一個事務讀取一個結果集後,第二個事務,對這個結果集經行增刪操作(第一個事務暫時沒用到,沒新增行鎖),然而第一個事務中再次對這個結果集進行查詢時,資料發現丟失或新增,出現了“幻影”。通過加表級鎖解決,如間隙鎖InnoDB和Falcon儲存引擎通過多版本併發控制(MVCC,Multiversion Concurrency Control)機制解決了該問題。

      Serializable(可序列化)ISOLATION_SERIALIZABLE

      這是最高的隔離級別,它通過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。簡言之,它是在每個讀的資料行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。

    • TransactionAttribute在TransactionDefinition的基礎上添加了rollbackon方法,可以通過宣告的方式指定業務方法在丟擲的哪些的異常的情況下可以回滾事務。(該介面主要面向使用Spring AOP進行宣告式事務管理的場合)

  • spring提供程式設計式的事務管理(自己建立事務管理器,由自己呼叫管理器的方法建立事務或處理事務,可以使用動態代理減少代理類的編寫,但是還是要編寫很多事務的程式碼在業務程式碼的前後,不是很方便管理,對於事務管理這種特性基本一致的操作用宣告反而更利於維護)和宣告式事務處理(聲明後由容器建立,由宣告來呼叫相應的方法,宣告式事務管理用AOP避免了我們為每一個需要事務管理的類建立一個代理類)。

  • 若是宣告式事務管理,我們會發現,我們沒有自己操作commit的空間,他會在你事務宣告的方法結束時就commit,你可以把很多業務程式碼放在一個事務service方法中實現同一事務,但若是不想則可以編碼式事務控制。

    宣告式事務管理中我們用到org.springframework.transaction.interceptor.TransactionInterceptor幫我們攔截業務方法,使得我們在springMVc中呼叫serviece中的方法時(我們從容器拿到的Service是經過AOP(也就是動態代理)處理的了),需要先經過它。

    需要一個容器去放置用不同ORM框架時,在TransactionInterceptor幫我們攔截後,將service增強成我們想要的模板,加入到像JdbcTempalte/SqlsessionTemplate這樣不同的模板中,以便在呼叫service的方法時,自動去呼叫相應的資料庫框架執行sql語句時需要的流程,如SqlsessionTemplate(它的原理跟sqlsession.getMapper相似)會自動對sqlsession進行建立,再進行該框架對sql語句執行流程,這個容器可以是ProxyFactory(ProxyFactoryBean)或者其他的interceptor的實現類

    而sqlsessionTemplate所需要的springMapConfig已經用SqlSessionFactoryBean建立的SqlSessionFactory中了,sqlsessionTemplate會將SqlSessionFactory注入其中,便可以擁有需要的Dao層資訊和需要的sql語句等了

    在spring 1.x到2.x中我們可以使用4種配置的方式在IoC容器的配置檔案種指定事務需要的元資料

    1. 使用ProxyFactory(ProxyFactoryBean)+TransactionIntercepter

      <!-- TransactionInterceptor -->//攔截我們需要的service方法
       <bean id="transactionInterceptor"
           class="org.springframework.transaction.interceptor.TransactionInterceptor">
           <property name="transactionManager">
               <ref bean="transactionManager" />
           </property>
           <property name="transactionAttributeSource">
               <value>
                   org.springframework.prospring.ticket.service.PaymentService.transfer=PROPAGATION_REQUIRED,ISOLATION_SERIALIZABLE,timeout_30,-Exception
               </value>
           </property>
       </bean>
      <!-- Business Object -->
       <bean id="paymentServiceTarget"
           class="org.springframework.prospring.ticket.service.PaymentServiceImpl">
           <property name="paymentDao">
               <ref local="paymentDao" />
           </property>
       </bean>
       
      // 將需要的框架Template/相應的dao實現,與Service層介面以及transactionInterceptor整合一個Bean方便Client呼叫
      
       <!-- Transactional proxy for the primary business object -->
       <bean id="paymentService" class="org.springframework.aop.framework.ProxyFactoryBean">
           <property name="target">
               <ref local="paymentServiceTarget" />
           </property>
           <property name="proxyInterfaces">
               <value>org.springframework.prospring.ticket.service.PaymentService
               </value>
           </property>
           <property name="interceptorNames">
             <list>
               <value>transactionInterceptor</value>
             </list>
           </property>
       </bean>
       
       
      
    2. 使用“一站式”的TransactionProxyFactoryBean

    3. 使用BeanNameAutoProxyCreater

    4. 使用Spring2.x的宣告事務配置方式(我們使用的)

    <context:component-scan base-package="cn.itcast">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
    <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm"/>
        <property name="user" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="comboPooledDataSource"/>
    </bean>
    
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.itcast.dao"/>
    </bean>
    
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="comboPooledDataSource"></property>
    </bean>
    
    // <tx:advice>專門為宣告事務Advice而設定的配置元素,底層還是TransactionInterceptor
    		若不指定<tx:attributes>,則採用DefaultTransactionDefinition
    
    <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
        <tx:attributes>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>
    
    
    // 作用如同ProxyFactoryBean一直,不過使用了aop實現,只需指定ServiceTarget的class,之後的便依靠動態代理實現
    
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.itcast.service.impl.*ServiceImpl.*(..))"/>
    </aop:config>
    
    //這個實現使得我們在MVC層呼叫的service物件已經是經過代理實現了的,我們可以直接用其中的方法,底層都會依據流程自己完成,我們會發現,我們沒有自己操作commit的空間,他會在你事務宣告的方法結束時就commit,你可以把很多業務程式碼放在一個事務service方法中實現同一事務,但若是不想則可以編碼式事務控制。
    
  • 我們一般通過TransactionTemplate來對PlatformTransactionManager的事務進行封裝,使得出現異常需要callback時,將其依據callback介面的實現可以在TransactionTemplate類的excute方法中便處理成功,我們就只需要注意call back介面的設計與實現,就能將PlatformTransactionManager的callback都由該模板實現,減少重複程式碼的編寫。當然小型測試直接用也可以。

  • Spring的事務處理中,通用的事務處理流程是由抽象事務管理器AbstractPlatformTransactionManager來提供的,而具體的底層事務處理實現,由PlatformTransactionManager的具體實現類來實現,如 DataSourceTransactionManager 、JtaTransactionManager和 HibernateTransactionManager等。(我們通常使用的是DataSourceTransactionManager來與mybatis整合

  • spring事務處理的一個關鍵是保證在整個事務的生命週期裡所有執行sql的jdbc connection和處理事務的jdbc connection始終是同一個(不然事務通過不同connection commit的時候可能會相互覆蓋,順序也難以確定)。然後執行sql的業務程式碼一般都分散在程式的不同地方,如何讓它們共享一個jdbc connection呢?

  • 這裡spring做了一個前提假設:即一個事務的操作一定是在一個thread中執行,在事務未結束前該事務一直存在於該thread中,且一個thread中如果有多個事務在不同的jdbc connection的話,他們必須順序執行(在上一個結束的情況下),不能同時存在,此時若是將connection也繫結到執行緒中,那(這個假設在絕大多數情況下都是成立的,mybatis自身的事務處理中,sqlsession也可以多次執行commit,connection的獲取是在sqlsession執行sql時進行的,因此sqlsession也可有多個不同jdbc connection生成的事務,必須順序執行)。

  • 基於這個假設,spring在transaction建立時,會用ThreadLocal把建立這個事務的jdbc connection繫結到當前thread,接下來在事務的整個生命週期中都會從ThreadLocal中獲取同一個jdbc connection(只要沒有主動斷開connection)。

  • Spring本身的事務流程(配合JdbcTemplate)

  • 從上面可以看出,Spring事務管理一共可分為三個步驟,分別是初始化事務、提交事務、回滾事務,然後每個步驟又可細分為若干小步驟。spring事務工作流相當於為使用者遮蔽了具體orm框架的底層處理邏輯,基於spring開發的程式,即便更換了orm框架也是跟換了呼叫的sqlTemplate,我們的事務管理器更換成適合於他的便可,基本不用改變其他的實現。這是Spring的優點

  • Spring控制datasourcetransaction ,mybaitis用springManagedTransaction對資料庫進行sql操作,但commit等都是最後由datasourceTransaction 進行.

    <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"/>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm"/>
            <property name="user" value="root"/>
            <property name="password" value="123456"/>
        </bean>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="comboPooledDataSource"/>
    </bean>
    
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.itcast.dao"/>
    </bean>
        <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="comboPooledDataSource"></property>
        </bean>
    
    
  • 再看這個容器中的引數,無論如何我們都需要SqlSessionFactoryBean(無論是否有用Spring的事務管理都需要)和DataSourceTransactionManager

  • 由SqlSessionFactoryBean建立工廠,此工廠將生成SpringManagedTransactionFactory(可能有人會好奇為什麼要大費周章重新實現一個TransactionFactory,到下面進入sqlsession的部分就可以知道答案了),再封裝成 Environment 物件。最後封裝成configuration物件來呼叫sqlSessionFactoryBuilder.build(configuration);生成sqlsqlSessionFactory,由XMLConfigBuilder讀取parse成一個sqlsqlSessionFactory

  • 交由SqlsessionTemplate使用(其實現了 SqlSession 介面,並不直接呼叫具體的 SqlSession 的方法,而是委託給一個動態代理,通過代理 SqlSessionInterceptor 對方法呼叫進行攔截,用呼叫介面的方法時,會去尋找是否有了停留在resources中的未關閉的sqlsession)

    若是程式設計式事務控制的話我們應該先建立SqlSessionFactoryBean建立sqlsqlSessionFactory,再將其注入自己建立的SqlsessionTemplate物件中,再利用SqlsessionTemplate進行增刪查改

    public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    
    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        XMLConfigBuilder xmlConfigBuilder = null;
        Configuration configuration;
        ···
        各種configuration的引數判斷
        ····
         //生成SpringManagedTransactionFactory
        if (this.transactionFactory == null) {
            this.transactionFactory = new SpringManagedTransactionFactory();
           
        }
        
    	//封裝成 Environment 物件
    	//封裝成configuration物件
        configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
        if (!ObjectUtils.isEmpty(this.mapperLocations)) {
            Resource[] var29 = this.mapperLocations;
            var27 = var29.length;
    
            for(var5 = 0; var5 < var27; ++var5) {
                Resource mapperLocation = var29[var5];
                if (mapperLocation != null) {
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                        xmlMapperBuilder.parse();
                    } catch (Exception var20) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
                    } finally {
                        ErrorContext.instance().reset();
                    }
    
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
                    }
                }
            }
        } else if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
        }
    
    	//呼叫sqlSessionFactoryBuilder.build(configuration);生成sqlsqlSessionFactory,
        return this.sqlSessionFactoryBuilder.build(configuration);
    }
    }
    
  • 若要使用spring的事務,使用sqlsessionTemplate前,我們要分析DataSourceTransactionManager整個流程,我們需要一步一步來,從最頂層開始

  • Spring的PlatformTransactionManager是事務管理的頂層介面,其中定義的三個方法對應的就是初始化事務、提交事務、回滾事務,,然後AbstractPlatformTransactionManager抽象類(作為模板)給出了三個步驟的具體實現。

  • 但對諸如doGetTransaction()之類的和doBegin等還是弄成了抽象方法由DataSourceTransactionManager等實現,以便自己決定對 TransactionSynchronization介面的實現類進行呼叫,自己實現執行緒安全、獲得ConnectionHolder和建立新事務時按何種方式dobgin,連線方式和注入各種引數。

public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

public final TransactionzhuangtStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {

//在DataSourceTransactionManager中重寫了的類,查詢當前執行緒中是否有傳入的datasource對應的ConnectionHolder,有則依據他建立transaction

 Object transaction = this.doGetTransaction();
 boolean debugEnabled = this.logger.isDebugEnabled();
 if (definition == null) {
     definition = new DefaultTransactionDefinition();
 }

 if (this.isExistingTransaction(transaction)) {
     return this.handleExistingTransaction((TransactionDefinition)definition, transaction, debugEnabled);
 }
···
各種條件判斷
···
     try {
         boolean newSynchronization = this.getTransactionSynchronization() != 2;
         
         //被建立的事務狀態物件型別是DefaultTransactionStatus,它持有上述建立的事務物件。事務狀態物件主要用於獲取當前事務物件的狀態,比如事務是否被標記了回滾,是否是一個新事務等等。
         DefaultTransactionStatus status = this.newTransactionStatus((TransactionDefinition)definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
         
         //事務開始處理,進行促使化,連接獲取
         this.doBegin(transaction, (TransactionDefinition)definition);
         
         //物件都交由TransactionSynchronizationManager管理,TransactionSynchronizationManager把這些物件都儲存在ThreadLocal中。在該transaction未commit/關閉前,該執行緒就會與該connection一直繫結在一起,通過只能同時繫結一個connection的原理,實現事務管理。
         
         this.prepareSynchronization(status, (TransactionDefinition)definition);
         
         返回事務狀態物件:(主要進行:查詢事務狀態、通過setRollbackOnly()方法標記當前事務使其回滾,根據事務引數建立內嵌事務),statu的意思是方便先對事務進行判斷再獲取transaction進行操作。
         return status;
     } catch (Error | RuntimeException var7) {
         this.resume((Object)null, suspendedResources);
         throw var7;
     }
 }
}
}
  • DataSourceTransactionManager(繼承了AbstractPlatformTransactionManager)的主要作用是建立transaction和對transaction的初始化
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
		implements ResourceTransactionManager, InitializingBean {

@Override//主要增加了對TransactionSynchronizationManager中當前執行緒是否有connectionHolder
	protected Object doGetTransaction() {
	
		// 建立事務物件
		
		DataSourceTransactionObject txObject = new DataSourceTransactionObject();
		txObject.setSavepointAllowed(isNestedTransactionAllowed());
		ConnectionHolder conHolder =
				(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
		//ConnectionHolder賦值給事務物件txObject
		txObject.setConnectionHolder(conHolder, false);
		return txObject;		
		}
}

//處理事務開始的方法  
    protected void doBegin(Object transaction, TransactionDefinition definition) {  
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;  
        Connection con = null;  
        try {  
            //如果資料來源事務物件的ConnectionHolder為null或者是事務同步的  
            if (txObject.getConnectionHolder() == null ||  
        txObject.getConnectionHolder().isSynchronizedWithTransaction()) {  
                //獲取當前資料來源的資料庫連線  
                Connection newCon = this.dataSource.getConnection();  
                if (logger.isDebugEnabled()) {  
                    logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");  
                }  
                //為資料來源事務物件設定ConnectionHolder  
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);  
            }  
    //設定資料來源事務物件的事務同步    txObject.getConnectionHolder().setSynchronizedWithTransaction(true);  
            //獲取資料來源事務物件的資料庫連線  
            con = txObject.getConnectionHolder().getConnection();  
            //根據資料連線和事務屬性,獲取資料庫連線的事務隔離級別  
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);  
    //為資料來源事務物件設定事務隔離級別  
    txObject.setPreviousIsolationLevel(previousIsolationLevel);  
            //如果資料庫連線設定了自動事務提交屬性,則關閉自動提交  
            if (con.getAutoCommit()) {  
                //儲存資料庫連線設定的自動連線到資料來源事務物件中  
                txObject.setMustRestoreAutoCommit(true);  
                if (logger.isDebugEnabled()) {  
                    logger.debug("Switching JDBC Connection [" + con + "] to manual commit");  
                }  
                //設定資料庫連線自動事務提交屬性為false,即禁止自動事務提交  
                con.setAutoCommit(false);  
            }  
            //啟用當前資料來源事務物件的事務配置  
            txObject.getConnectionHolder().setTransactionActive(true);  
            //獲取事務配置的超時時長  
int timeout = determineTimeout(definition);  
//如果事務配置的超時時長不等於事務的預設超時時長  
            if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {  
        //資料來源事務物件設定超時時長  
        txObject.getConnectionHolder().setTimeoutInSeconds(timeout);  
            }  
            //把當前資料庫Connection和執行緒ThreadLocal繫結(key為DataSource,value為getConnectionHolder)spring事務管理的關鍵一步
            if (txObject.isNewConnectionHolder()) {  
        TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());  
            }  
        }  
        catch (Exception ex) {  
            DataSourceUtils.releaseConnection(con, this.dataSource);  
            throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);  
        }  
    }  
  • 此時若是註釋執行的便需要把PlatformTransactionManager,他生成的TransactionStatus、TransactionAttribute等封裝到TransactionInfo中以便註解能自己呼叫

  • 完成了對事務的建立,並將其用TransactionSynchronizationManager繫結到了thread local上,接著便是對sqlsessionTemplate的呼叫了即對mybatis框架的sqlsession的整合了

  • Spring+mybatis的事務控制流程

  • SqlsessionTemplate(如同sqlsession.getmapper獲得的代理物件)的使用並不是直接用sqlsession進行增刪查改(實際上是sqlsession的一個代理類,其實現了 SqlSession 介面,並不直接呼叫具體的 SqlSession 的方法,而是委託給一個動態代理,通過代理 SqlSessionInterceptor 對方法呼叫進行攔截,用呼叫介面的方法時,會去尋找是否有了停留在resources中的未關閉的sqlsession)
public class SqlSessionTemplate implements SqlSession, DisposableBean {
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    Assert.notNull(executorType, "Property 'executorType' is required");
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}



// 動態代理中的 InvocationHandler類,真正對方法的前後進行通知的類,代理類物件只是提供一個目標物件的封裝以呼叫而已
 private class SqlSessionInterceptor implements InvocationHandler {
        private SqlSessionInterceptor() {
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
            //檢視是否有為關閉的閒置的同個配置的sqlsession,有則呼叫,這也是你用sqlsessionTemplate代理執行增刪查改一直是同一個sqlsession完成事務管理的原因,沒有則生成。我們可以看到是由SqlSessionUtils實現的
            
    SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

            Object unwrapped;
            try {
                Object result = method.invoke(sqlSession, args);
                if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                //查詢是否自動提交
                //若是自動則為一條sql一次commit,是為新手設定的,一般手動提交。
                    sqlSession.commit(true);
                }

                unwrapped = result;
            } catch (Throwable var11) {
                unwrapped = ExceptionUtil.unwrapThrowable(var11);
                if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    sqlSession = null;
                    Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }

                throw (Throwable)unwrapped;
            } finally {
            
            //關閉sqlsession,同一個事務可以有多個sqlsession
            
                if (sqlSession != null) {
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }

            }

            return unwrapped;
        }
    }
}
  • 我們來看下SqlSessionUtils的實現,這裡的sqlsession的建立需要注意transactionFactory是SpringManageTransactionFactory。
public final class SqlSessionUtils {
	    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
        Assert.notNull(executorType, "No ExecutorType specified");
        
        //有相應的sqlsessionHolder則取出裡面的SqlSession
        
        SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
        SqlSession session = sessionHolder(executorType, holder);
        if (session != null) {
            return session;
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Creating a new SqlSession");
            }
	
			//沒有則用工廠建立sqlsession,注意:此時的sqlsessionfactory是sqlsessionfactoryBean建立的,即他的configuration中的Enviroment中的transactionFactory是SpringManageTransactionFactory。
			
            session = sessionFactory.openSession(executorType);
            
           // 註冊sqlsession到TransactionSyncronizedMager,
           
           
            registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
            return session;
        }
    }
}
  • 關於SqlSessionUtils中的registerSessionHolder方法,註冊sqlsession到TransactionSyncronizedMager中,

  • **包含 **

    bindResource(sessionFactory, holder);
    ​ registerSynchronization(new SqlSessionUtils.SqlSessionSynchronization(holder, sessionFactory));兩步

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
    
    	//從sessionFactory中獲取Environment
    	
        Environment environment = sessionFactory.getConfiguration().getEnvironment();
        
        //判斷environment中封裝的TransactionFactorySpringManagedTransactionFactory,是否用到了Spring的事務管理服務,sqlsession中對TransactionSyncronizedMager的應用只有與Spring整合時才用到,mybatis自身的事務管理並不會用到。
        
        if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
            }

            SqlSessionHolder holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
            
            //繫結、註冊到TransactionSynchronizationManager
            TransactionSynchronizationManager.bindResource(sessionFactory, holder);
            TransactionSynchronizationManager.registerSynchronization(new SqlSessionUtils.SqlSessionSynchronization(holder, sessionFactory));
            
            holder.setSynchronizedWithTransaction(true);
            holder.requested();
        } else {
            if (TransactionSynchronizationManager.getResource(environment.getDataSource()) != null) {
                throw new TransientDataAccessResourceException("SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
            }

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
            }
        }
    } else if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
    }

}
  • 其中SqlSessionSynchronization是一個事務生命週期的callback介面,mybatis-spring通過SqlSessionSynchronization在事務提交和回滾前分別呼叫DefaultSqlSession.commit()和DefaultSqlSession.rollback()

    private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter {
    public void beforeCommit(boolean readOnly) {
                if (TransactionSynchronizationManager.isActualTransactionActive()) {
                    try {
                        if (SqlSessionUtils.LOGGER.isDebugEnabled()) {
                            SqlSessionUtils.LOGGER.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
                        }
    
                        this.holder.getSqlSession().commit();
                    } catch (PersistenceException var4) {
                        if (this.holder.getPersistenceExceptionTranslator() != null) {
                            DataAccessException translated = this.holder.getPersistenceExceptionTranslator().translateExceptionIfPossible(var4);
                            if (translated != null) {
                                throw translated;
                            }
                        }
                        throw var4;
                    }
                }
    
            }
    
            public void beforeCompletion() {
                if (!this.holder.isOpen()) {
                    if (SqlSessionUtils.LOGGER.isDebugEnabled()) {
                        SqlSessionUtils.LOGGER.debug("Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
                    }
    
                    TransactionSynchronizationManager.unbindResource(this.sessionFactory);
                    this.holderActive = false;
                    if (SqlSessionUtils.LOGGER.isDebugEnabled()) {
                        SqlSessionUtils.LOGGER.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
                    }
    
                    this.holder.getSqlSession().close();
                }
    
            }
    
            public void afterCompletion(int status) {
                if (this.holderActive) {
                    if (SqlSessionUtils.LOGGER.isDebugEnabled()) {
                        SqlSessionUtils.LOGGER.debug("Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
                    }
    
                    TransactionSynchronizationManager.unbindResourceIfPossible(this.sessionFactory);
                    this.holderActive = false;
                    if (SqlSessionUtils.LOGGER.isDebugEnabled()) {
                        SqlSessionUtils.LOGGER.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
                    }
    
                    this.holder.getSqlSession().close();
                }
    
                this.holder.reset();
            }
    
  • 一番周折終於拿到了sqlsession到了代理類SqlsessionTemplate中,接著便是目的碼本身(sql語句)的執行了

  • 在此之前我們談及為何要專門用SpringManagedTransaction,現在就要揭曉了,之前我們建立了sqlsession到了SpringTemplate,但是還未獲取connection,所以我們執行語句前就要獲取connection,我們用mybatis的Executor執行語句,會在prepareStatement中獲得connection

protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = this.transaction.getConnection();
    return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}
  • 看到上面的transsaction便可以知道我們需要一個transaction來實現連線的獲取,但是我們的連線是之前繫結到了ThreadLocal中了,我們Spring事務管理的關鍵要讓此時Thread中的connection用上,這時候我們就需要用到DataSourceUtils來獲取了

  • datasourceUtils:也是從TransactionSynchronizationManager獲取connection

  • SqlSessionUtils從TransactionSynchronizationManager獲取ConnectionHolder交給SpringManagedTransaction,並作為引數封裝進Executor,最後封裝進DefaultSqlSession,將Spring的事務管理與它的資料訪問框架是緊密結合的

  • SpringManagedTransaction中保留由datasource等資訊,而且它特別實現了對SqlSessionUtils的呼叫(其他的jdbcTransaction和managedTransaction都沒有對SqlSessionUtils呼叫的方法,這兩者只適用於mybaitis自身事務情況,不適用於整合)

  • 並且可以用來給datasourceUtils提供引數,讓其可以直接從TransactionSynchronizationManager中呼叫相應的connection,因為是同一執行緒且事務沒結束所以能保證是同一個了

  • SpringManagedTransaction的意義便在此,我們的問題也就解決了,最後便是jdbc.connection的excute操作了

public abstract class DataSourceUtils {
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");

ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
   conHolder.requested();
   if (!conHolder.hasConnection()) {
      logger.debug("Fetching resumed JDBC Connection from DataSource");
      conHolder.setConnection(fetchConnection(dataSource));
   }
   return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.

logger.debug("Fetching JDBC Connection from DataSource");
Connection con = fetchConnection(dataSource);

if (TransactionSynchronizationManager.isSynchronizationActive()) {
   logger.debug("Registering transaction synchronization for JDBC Connection");
   // Use same Connection for further JDBC actions within the transaction.
   // Thread-bound object will get removed by synchronization at transaction completion.
   ConnectionHolder holderToUse = conHolder;
   if (holderToUse == null) {
      holderToUse = new ConnectionHolder(con);
   }
   else {
      holderToUse.setConnection(con);
   }
   holderToUse.requested();
   TransactionSynchronizationManager.registerSynchronization(
         new ConnectionSynchronization(holderToUse, dataSource));
   holderToUse.setSynchronizedWithTransaction(true);
   if (holderToUse != conHolder) {
      TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
   }
}
return con;
}
  • 可以看到mybatis-spring處理事務的主要流程和spring jdbc處理事務並沒有什麼區別,都是通過DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事務的生命週期管理,而且jdbc connection的建立也是通過DataSourceTransactionManager.getTransaction()完成,mybatis並沒有參與其中,mybatis只是在執行sql時通過DataSourceUtils.getConnection()獲得當前thread的jdbc connection,然後在其上執行sql。

  • 最後寫一下commit的流程(rollback實現差不多,回到儲存的回撥點而已)

  • 呼叫DataSourceTransactionManager的父類AbstractPlatformTransactionManager的commit

public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
 
 public final void commit(TransactionStatus status) throws TransactionException {
     ···
     status 是否已完成或是否需要rollback
    ···    
        //正常流程會呼叫processCommit         
            this.processCommit(defStatus);
        }   
        }
        }
  }
​```


private void processCommit(DefaultTransactionStatus status) throws TransactionException {
    try {
        boolean beforeCompletionInvoked = false;

        try {
            boolean unexpectedRollback = false;         
            //commit準備      
            this.prepareForCommit(status);
            
            //BeforeCommit:commit前對springTransaciton、sqlsession的commit,大部分未實現這個的功能
            
            this.triggerBeforeCommit(status);
            
            //BeforeCompletion:Complete前對springTransaciton、sqlsession的commit
            
            this.triggerBeforeCompletion(status);
            beforeCompletionInvoked = true;
            ···
            是否有記錄回撥點/是否需要回調
            ···
            // 在DataSourceTransactionManager中實現對connection的commit
            this.doCommit(status);

            ···          
           異常處理
            ···
    
        try {
        	
        	//對TransactionSynchronizationManager中的synchronizations用Iterator計數器遍歷刪除
            this.triggerAfterCommit(status);
            
        } finally {
        
        // 若上一步沒清理完成,則再次清理一次,用synchronizations用Iterator計數器遍歷刪除
            this.triggerAfterCompletion(status, 0);
            
        }
    } finally {
    
        // 清空TransactionSynchronizationManager,若有掛起的事務,則恢復執行
        this.cleanupAfterCompletion(status);
    }

}
// 清空TransactionSynchronizationManager,若有掛起的事務,則恢復執行,這和definiton設定的事務級別有關
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
        status.setCompleted();
        if (status.isNewSynchronization()) {
            TransactionSynchronizationManager.clear();
        }

        if (status.isNewTransaction()) {
            this.doCleanupAfterCompletion(status.getTransaction());
        }

        if (status.getSuspendedResources() != null) {
            if (status.isDebug()) {
                this.logger.debug("Resuming suspended transaction after completion of inner transaction");
            }

            Object transaction = status.hasTransaction() ? status.getTransaction() : null;
            
            //SuspendedResourcesHolder用靜態方法放置在JVM的方法區,可以直接呼叫
            
            this.resume(transaction, (AbstractPlatformTransactionManager.SuspendedResourcesHolder)status.getSuspendedResources());
        }
  • 這便是整個spring與mybatis整合的事務控制了。
  • 這裡提個建議,如果有好的借鑑理解的書或其他資源要參照著學習會效果更好,我寫完後發現Spring揭祕一書中有許多細節仍是需要注意,對於Spring的講解也更有系統,可惜自己一直只顧著看原始碼忘了