Spring 與 MyBatis 事務管理源碼解析

  • 2020 年 7 月 28 日
  • 筆記
  • 用到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)

143421_Bmpa_1452390.png

  • 從上面可以看出,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的事務控制流程

img

  • 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的講解也更有系統,可惜自己一直只顧著看源碼忘了