重拾-Spring Transaction

問題

面試中是否是有時常常會被問到 「Spring 事務如何管理的瞭解嗎?」 ,「Spring 事務的傳播性有哪些,能聊聊它們的使用場景嗎?」, 「事務回滾的時候是全部異常下都會回滾嗎?」; 下面咱們就帶着這些問題來看看 Spring 事務是如何實現的吧。java

實現分析

首先咱們仍是先經過一個使用示例,先看下 Spring 事務是如何工做的。mysql

使用示例

本文咱們先採用 TransactionProxyFactoryBean 配置的方式來看下, Spring 事務如何實現
<beans>
    <!-- 配置數據源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" >
        <property name="driverClassName">
            <value>com.mysql.jdbc.Driver</value>
        </property>
        <property name="url">
            <value>url</value>
        </property>
        <property name="username">
            <value>username</value>
        </property>
        <property name="password">
            <value>password</value>
        </property>
    </bean>

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

    <!-- 配置 jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource">
            <ref bean="dataSource"/>
        </property>
    </bean>

    <bean id="userServiceTarget" class="org.springframework.transaction.UserServiceImpl">
        <property name="jdbcTemplate">
            <ref bean="jdbcTemplate"></ref>
        </property>
    </bean>

    <!--
        TransactionProxyFactoryBean 實現了接口 InitializingBean,在初始化過程當中會調用 afterPropertiesSet
        1 : 建立事務攔截器
        2 : 建立事務 advisor (事務的攔截切面)
        3 : 建立代理
    -->
    <bean id="userService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref bean="transactionManager" />
        </property>

        <property name="target">
            <ref bean="userServiceTarget"></ref>
        </property>

        <property name="proxyInterfaces">
            <value>org.springframework.transaction.UserService</value>
        </property>
        <!--
            配置事務屬性 傳播行爲, 事務隔離級別, 是否只讀, 回滾規則(哪些異常下執行回滾),key 爲配置須要事務管理的方法名;
            在代理目標執行的時候會經過該屬性判斷方法是否須要事務管理
        -->
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
</beans>

TransactionProxyFactoryBean 的屬性配置中若是您對 transactionAttributes 屬性不熟悉的話,是否是會感受一頭霧水呢? 這個玩意怎麼配置的? 配置格式又是什麼樣的呢? 配置值有哪些呢 ?; 下面將會經過對 TransactionProxyFactoryBean 的源碼分析來一一解答。面試

源碼分析

類結構

TransactionProxyFactoryBean

TransactionProxyFactoryBean 類結構咱們知道,其實現了接口 InitializingBeanFactoryBean; 那麼也就是在 TransactionProxyFactoryBean 實例化後會調用方法 afterPropertiesSet, 在獲取目標對象實例時會調用方法 getObject; 下面將主要看下這兩個方法的實現。spring

afterPropertiesSet-建立目標代理對象

public void afterPropertiesSet() throws AopConfigException {
    // 校驗 Target 目標對象
    if (this.target == null) {
        throw new AopConfigException("Target must be set");
    }

    // 校驗事務屬性定義,從拋出的異常信息能夠看出 Spring 在此作了強校驗;
    // 也就是說若是沒有須要 Spring 事務管理的方法,就不要採用 TransactionProxyFactoryBean 了
    // 那麼 transactionAttributeSource 是怎麼來的呢? 見下文分析
    if (this.transactionAttributeSource == null) {
        throw new AopConfigException("Either 'transactionAttributeSource' or 'transactionAttributes' is required: " +
                                                                 "If there are no transactional methods, don't use a transactional proxy.");
    }

    // 建立事務攔截器 transactionInterceptor
    TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
    transactionInterceptor.setTransactionManager(this.transactionManager);
    transactionInterceptor.setTransactionAttributeSource(this.transactionAttributeSource);
    transactionInterceptor.afterPropertiesSet();

    ProxyFactory proxyFactory = new ProxyFactory();

    // 是否配置了前置攔截
    if (this.preInterceptors != null) {
        for (int i = 0; i < this.preInterceptors.length; i++) {
            proxyFactory.addAdvisor(GlobalAdvisorAdapterRegistry.getInstance().wrap(this.preInterceptors[i]));
        }
    }

    if (this.pointcut != null) {
        // 若是配置了 pointcut 切入點,則按配置的 pointcut 建立 advisor
        Advisor advice = new DefaultPointcutAdvisor(this.pointcut, transactionInterceptor);
        proxyFactory.addAdvisor(advice);
    }
    else {
        // rely on default pointcut
        // 建立事務攔截切面 advisor
        proxyFactory.addAdvisor(new TransactionAttributeSourceAdvisor(transactionInterceptor));
        // could just do the following, but it's usually less efficient because of AOP advice chain caching
        // proxyFactory.addInterceptor(transactionInterceptor);
    }

    // 是否配置了後置攔截
    if (this.postInterceptors != null) {
        for (int i = 0; i < this.postInterceptors.length; i++) {
            proxyFactory.addAdvisor(GlobalAdvisorAdapterRegistry.getInstance().wrap(this.postInterceptors[i]));
        }
    }

    proxyFactory.copyFrom(this);

    proxyFactory.setTargetSource(createTargetSource(this.target));
    // 設置代理的接口
    if (this.proxyInterfaces != null) {
        proxyFactory.setInterfaces(this.proxyInterfaces);
    }
    else if (!getProxyTargetClass()) {
        // rely on AOP infrastructure to tell us what interfaces to proxy
        proxyFactory.setInterfaces(AopUtils.getAllInterfaces(this.target));
    }
    // 建立目標對象的代理對象
    this.proxy = proxyFactory.getProxy();
}

從源碼中咱們知道 afterPropertiesSet 主要作如下幾件事:sql

  • 參數有效性校驗; 校驗目標對象,事務屬性定義
  • 設置代理的 advisor chain, 包括用戶自定義的前置攔截, 內置的事務攔截器,用戶自定義的後置攔截
  • 建立目標代理對象
afterPropertiesSet 的實現中有個針對 transactionAttributeSource 的非空校驗,那麼這個變量是什麼時候賦值的呢 ? 還記得使用示例中的關於事務屬性的定義 transactionAttributes 嗎 ?

setTransactionAttributes-設置事務屬性定義

public void setTransactionAttributes(Properties transactionAttributes) {
    NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
    tas.setProperties(transactionAttributes);
    this.transactionAttributeSource = tas;
}

public void setProperties(Properties transactionAttributes) {
    TransactionAttributeEditor tae = new TransactionAttributeEditor();
    // 遍歷 properties
    for (Iterator it = transactionAttributes.keySet().iterator(); it.hasNext(); ) {
        // key 爲匹配的方法名
        String methodName = (String) it.next();
        String value = transactionAttributes.getProperty(methodName);
        // 解析 value
        tae.setAsText(value);
        TransactionAttribute attr = (TransactionAttribute) tae.getValue();
        // 將方法名與事務屬性定義匹配關聯
        addTransactionalMethod(methodName, attr);
    }
}

下面咱們就看下 setAsText 方法是如何解析事務屬性的配置數據庫

/**
 * Format is PROPAGATION_NAME,ISOLATION_NAME,readOnly,+Exception1,-Exception2.
 * Null or the empty string means that the method is non transactional.
 * @see java.beans.PropertyEditor#setAsText(java.lang.String)
 */
public void setAsText(String s) throws IllegalArgumentException {
    if (s == null || "".equals(s)) {
        setValue(null);
    }
    else {
        // tokenize it with ","
        // 按 , 分割配置信息
        String[] tokens = StringUtils.commaDelimitedListToStringArray(s);
        RuleBasedTransactionAttribute attr = new RuleBasedTransactionAttribute();

        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i];
            // 以 PROPAGATION 開頭,則配置事務傳播性
            if (token.startsWith(TransactionDefinition.PROPAGATION_CONSTANT_PREFIX)) {
                attr.setPropagationBehaviorName(tokens[i]);
            }
            // 以 ISOLATION 開頭,則配置事務隔離級別
            else if (token.startsWith(TransactionDefinition.ISOLATION_CONSTANT_PREFIX)) {
                attr.setIsolationLevelName(tokens[i]);
            }
            // 以 timeout_ 開頭,則設置事務超時時間
            else if (token.startsWith(DefaultTransactionAttribute.TIMEOUT_PREFIX)) {
                String value = token.substring(DefaultTransactionAttribute.TIMEOUT_PREFIX.length());
                attr.setTimeout(Integer.parseInt(value));
            }
            // 若等於 readOnly 則配置事務只讀
            else if (token.equals(DefaultTransactionAttribute.READ_ONLY_MARKER)) {
                attr.setReadOnly(true);
            }
            // 以 + 開頭,則配置哪些異常下不回滾
            else if (token.startsWith(DefaultTransactionAttribute.COMMIT_RULE_PREFIX)) {
                attr.getRollbackRules().add(new NoRollbackRuleAttribute(token.substring(1)));
            }
            // 以 - 開頭,則配置哪些異常下回滾
            else if (token.startsWith(DefaultTransactionAttribute.ROLLBACK_RULE_PREFIX)) {
                attr.getRollbackRules().add(new RollbackRuleAttribute(token.substring(1)));
            }
            else {
                throw new IllegalArgumentException("Illegal transaction token: " + token);
            }
        }

        setValue(attr);
    }
}

setAsText 方法的實現咱們就能夠搞明白在配置文件中 transactionAttributes 如何配置了,譬如:apache

<property name="transactionAttributes">
    <props>
        <prop key="*">PROPAGATION_REQUIRED, ISOLATION_DEFAULT, readOnly</prop>
    </props>
</property>

也能夠這樣配置:app

<property name="transactionAttributes">
    <props>
        <prop key="*">readOnly, ISOLATION_DEFAULT, PROPAGATION_REQUIRED</prop>
    </props>
</property>

也就是說 transactionAttributes 的配置只要保證 token 格式正確便可,順序無關;可是從規範來說建議仍是保持 PROPAGATION_NAME,ISOLATION_NAME,readOnly,+Exception1,-Exception2. 的格式。less

getObject-獲取代理對象

public Object getObject() {
    // proxy 對象在 afterPropertiesSet 方法執行時產生
    return this.proxy;
}

代理執行

是否支持事務

重拾-Spring AOP 中咱們知道,當代理對象在執行的時候會先獲取當前方法所匹配的 advisor (參見類 JdkDynamicAopProxy); 而 TransactionProxyFactoryBean 在建立代理對象的時候會將 TransactionInterceptor 綁定到 TransactionAttributeSourceAdvisor 上,那麼我就看下 TransactionAttributeSourceAdvisor 是如何匹配方法的。工具

public class TransactionAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {

    private TransactionAttributeSource transactionAttributeSource;

    public TransactionAttributeSourceAdvisor(TransactionInterceptor ti) {
        super(ti);
        if (ti.getTransactionAttributeSource() == null) {
            throw new AopConfigException("Cannot construct a TransactionAttributeSourceAdvisor using a " +
                                                                     "TransactionInterceptor that has no TransactionAttributeSource configured");
        }
        this.transactionAttributeSource = ti.getTransactionAttributeSource();
    }

    public boolean matches(Method m, Class targetClass) {
        return (this.transactionAttributeSource.getTransactionAttribute(m, targetClass) != null);
    }
}

TransactionAttributeSourceAdvisor 判斷方法是否匹配時,實際是由 NameMatchTransactionAttributeSource 的方法 getTransactionAttribute 來處理。

public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) {
    // 獲取目標方法名
    String methodName = method.getName();
    // 獲取目標方法匹配的事務屬性定義
    TransactionAttribute attr = (TransactionAttribute) this.nameMap.get(methodName);
    // 若是 attr 不爲空說明當前方法配置了事務屬性定義,也就是當前方法須要事務管理
    if (attr != null) {
        return attr;
    }
    else {
        // look up most specific name match
        String bestNameMatch = null;
        for (Iterator it = this.nameMap.keySet().iterator(); it.hasNext();) {
            // 判斷當前方法是否匹配通配符的方式
            String mappedName = (String) it.next();
            if (isMatch(methodName, mappedName) &&
                    (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
                attr = (TransactionAttribute) this.nameMap.get(mappedName);
                bestNameMatch = mappedName;
            }
        }
        return attr;
    }
}

protected boolean isMatch(String methodName, String mappedName) {
    return (mappedName.endsWith("*") && methodName.startsWith(mappedName.substring(0, mappedName.length() - 1))) ||
            (mappedName.startsWith("*") && methodName.endsWith(mappedName.substring(1, mappedName.length())));
}
TransactionInterceptor-事務攔截

在完成判斷當前方法是否須要事務管理後,若是須要事務管理最終會調用 TransactionInterceptor 執行事務攔截的處理。

public final Object invoke(MethodInvocation invocation) throws Throwable {
    Class targetClass = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;
    // if the transaction attribute is null, the method is non-transactional
    // 獲取當前方法所支持的事務配置屬性,若不存在則說明當前方法不須要事務管理
    TransactionAttribute transAtt = this.transactionAttributeSource.getTransactionAttribute(invocation.getMethod(), targetClass);
    TransactionStatus status = null;
    TransactionStatus oldTransactionStatus = null;
    
    // create transaction if necessary
    if (transAtt != null) {        
        // the transaction manager will flag an error if an incompatible tx already exists
        // 經過事務管理獲取事務,該事務多是新建立的也多是當前上下文已存在的事務
        // 返回事務狀態
        status = this.transactionManager.getTransaction(transAtt);
        
        // make the TransactionStatus available to callees
        oldTransactionStatus = (TransactionStatus) currentTransactionStatus.get();
        currentTransactionStatus.set(status);
    }
    else {
        // it isn't a transactional method
        
    }

    Object retVal = null;
    try {
        // 目標方法執行
        retVal = invocation.proceed();
    }
    catch (Throwable ex) {
        // target invocation exception
        if (status != null) {
            // 異常處理 可能會執行事務的回滾
            onThrowable(invocation, transAtt, status, ex);
        }
        throw ex;
    }
    finally {
        if (transAtt != null) {
            // use stack to restore old transaction status if one was set
            currentTransactionStatus.set(oldTransactionStatus);
        }
    }
    if (status != null) {
        // 經過事務管理執行事務提交
        this.transactionManager.commit(status);
    }
    return retVal;
}
private void onThrowable(MethodInvocation invocation, TransactionAttribute txAtt,
                             TransactionStatus status, Throwable ex) {
    // 判斷異常是否須要回滾
    if (txAtt.rollbackOn(ex)) {
        try {
            // 經過事務管理執行回滾
            this.transactionManager.rollback(status);
        }
        catch (TransactionException tex) {
            logger.error("Application exception overridden by rollback exception", ex);
            throw tex;
        }
    }
    else {
        // Will still roll back if rollbackOnly is true
        // 異常不須要回滾的話 則提交事務
        this.transactionManager.commit(status);
    }
}

TransactionInterceptor 的處理邏輯來看,咱們知道其主要作如下事情:

  • 獲取當前方法所定義的事務屬性
  • 經過事務管理器 Transaction Manager 來獲取事務
  • 目標方法執行
  • 執行異常處理,如異常須要回滾則經過事務管理器執行事務 rollback,反之執行事務 commit
  • 方法執行成功則執行事務 commit
也就是說 TransactionInterceptor (事務攔截器) 主要是將事務相關的動做委託給 TransactionManager (事務管理器)處理
TransactionManager-事務管理
本文是以 DataSourceTransactionManager 爲例來分析事務的管理實現
getTransaction-獲取事務
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
    // 獲取事務
    Object transaction = doGetTransaction();
    if (definition == null) {
        // 若 definition == null 則採用默認的事務定義
        definition = new DefaultTransactionDefinition();
    }

    // 判斷當前上下文是否開啓過事務
    if (isExistingTransaction(transaction)) {
        // 當前上下文開啓過事務
        // 若是當前方法匹配的事務傳播性爲 PROPAGATION_NEVER 說明不須要事務則拋出異常
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
            throw new IllegalTransactionStateException("Transaction propagation 'never' but existing transaction found");
        }

        // 若是當前方法匹配的事務傳播性爲 PROPAGATION_NOT_SUPPORTED 說明該方法不該該運行在事務中,則將當前事務掛起
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
            // 將當前事務掛起
            Object suspendedResources = suspend(transaction);
            boolean newSynchronization = (this.transactionSynchronization == SYNCHRONIZATION_ALWAYS);
            // 返回的事務狀態爲 不須要事務
            return newTransactionStatus(null, false, newSynchronization,
                                        definition.isReadOnly(), debugEnabled, suspendedResources);
        }
        // 若是當前方法匹配的事務傳播性爲 PROPAGATION_REQUIRES_NEW 表示當前方法必須運行在它本身的事務中;將已存在的事務掛起,從新開啓事務
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
            if (debugEnabled) {
                logger.debug("Creating new transaction, suspending current one");
            }
            // 掛起當前事務
            Object suspendedResources = suspend(transaction);
            // 從新開啓個事務
            doBegin(transaction, definition);
            boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);
            // 返回的事務狀態爲 新建事務
            return newTransactionStatus(transaction, true, newSynchronization,
                                        definition.isReadOnly(), debugEnabled, suspendedResources);
        }
        else {
            boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);
            // 其餘的傳播行爲 表示在已存在的事務中執行
            return newTransactionStatus(transaction, false, newSynchronization,
                                        definition.isReadOnly(), debugEnabled, null);
        }
    }

    if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
    }

    // 若是傳播性爲 PROPAGATION_MANDATORY 說明必須在事務中執行,若當前沒有事務的話則拋出異常
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
        throw new IllegalTransactionStateException("Transaction propagation 'mandatory' but no existing transaction found");
    }

    // 當前上下文不存在事務
    // 若傳播性爲 PROPAGATION_REQUIRED 或 PROPAGATION_REQUIRES_NEW 則開啓新的事務執行
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
        definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
        // 開啓新的 connection 並取消自動提交,將 connection 綁定當前線程
        doBegin(transaction, definition);
        boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);
        return newTransactionStatus(transaction, true, newSynchronization,
                                    definition.isReadOnly(), debugEnabled, null);
    }
    else {
        // "empty" (-> no) transaction
        boolean newSynchronization = (this.transactionSynchronization == SYNCHRONIZATION_ALWAYS);
        // 返回事務狀態爲 不須要事務
        return newTransactionStatus(null, false, newSynchronization,
                                    definition.isReadOnly(), debugEnabled, null);
    }
}

protected Object doGetTransaction() {
    // 判斷當前線程是否開啓過事務
    if (TransactionSynchronizationManager.hasResource(this.dataSource)) {
        // 獲取當前已存在的 connectoin holder
        ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
        return new DataSourceTransactionObject(holder);
    }
    else {
        return new DataSourceTransactionObject();
    }
}

看到了這裏,是否是忽然明白 PROPAGATION (事務傳播性) 是幹什麼的了;

簡單來講, PROPAGATION 就是爲了告訴 Spring 當前方法需不須要事務,是在已存在的事務中執行,仍是新開啓事務執行;也能夠認爲是繼承上個方法棧的事務,仍是擁有本身的事務。

TransactionManager 獲取事務的過程實際就是經過當前方法定義的 PROPAGATION (事務傳播性) 和當前上下文是否存在事務來判斷是否須要事務,是否須要開啓新的事務或者是使用當前已存在的事務。

下面看下如何開啓新的事務 doBegin

protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;

    // cache to avoid repeated checks
    boolean debugEnabled = logger.isDebugEnabled();

    // 判斷 connection holder 是否爲空
    // 兩種場景下可能爲空:
    // 1. 上下文不存在事務的時候
    // 2. 上下文已存在的事務被掛起的時候
    if (txObject.getConnectionHolder() == null) {
        if (debugEnabled) {
            logger.debug("Opening new connection for JDBC transaction");
        }
        // 開啓新的 connection
        Connection con = DataSourceUtils.getConnection(this.dataSource, false);
        txObject.setConnectionHolder(new ConnectionHolder(con));
    }

    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        // apply read-only
        if (definition.isReadOnly()) {
            try {
                // 若是定義了只讀,設置 connection 爲只讀
                con.setReadOnly(true);
            }
            catch (Exception ex) {
                // SQLException or UnsupportedOperationException
                logger.warn("Could not set JDBC connection read-only", ex);
            }
        }

        // apply isolation level
        // 設置事務隔離級別
        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
            txObject.setPreviousIsolationLevel(new Integer(con.getTransactionIsolation()));
            con.setTransactionIsolation(definition.getIsolationLevel());
        }

        // 若 connection 爲自動提交則取消
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            con.setAutoCommit(false);
        }

        // 設置超時時間
        if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
            txObject.getConnectionHolder().setTimeoutInSeconds(definition.getTimeout());
        }

        // 將當前 connection holder 綁定到當前上下文
        TransactionSynchronizationManager.bindResource(this.dataSource, txObject.getConnectionHolder());
    }
    catch (SQLException ex) {
        throw new CannotCreateTransactionException("Could not configure connection", ex);
    }
}

doBegin 執行開啓事務的操做,在上下文不存在事務或者上下文事務被掛起的時候會新打開一個 connection, 並按照事務定義設置相關屬性,譬如是否只讀,取消自動提交,設置事務隔離級別,設置超時時間;最後會將 connection 綁定到當前上下文,也即當前線程。

doSuspend-事務掛起
protected Object doSuspend(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    // 將當前事務的 connection holder 置爲空
    txObject.setConnectionHolder(null);
    // 並將當前事務與上下文解綁
    return TransactionSynchronizationManager.unbindResource(this.dataSource);
}

事務掛起既是將當前事務的鏈接持有者清空並與當前上下文解綁,保證後續可以從新開啓事務。

數據庫操做
針對數據庫的操做,本文以 Spring 提供的 jdbcTemplate 工具類進行分析。
public Object execute(final StatementCallback action) {
    // 若當前須要事務管理的話,那麼此時獲取的 connection 則是 transaction manager bind 的 connection
    // 這樣就保證數據庫操做的時候所得到的的 connection 與 事務管理的一致
    Connection con = DataSourceUtils.getConnection(getDataSource());
    Statement stmt = null;
    // 如下代碼省略 此處重點關注如何獲取 connection
}
public static Connection getConnection(DataSource ds, boolean allowSynchronization)
        throws CannotGetJdbcConnectionException {
    // 從當前上下文獲取 connection holder
    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(ds);
    if (conHolder != null) {
        return conHolder.getConnection();
    }
    else {
        try {
            // 反之新打開一個 connection
            Connection con = ds.getConnection();
            if (allowSynchronization && TransactionSynchronizationManager.isSynchronizationActive()) {
                logger.debug("Registering transaction synchronization for JDBC connection");
                // use same Connection for further JDBC actions within the transaction
                // thread object will get removed by synchronization at transaction completion
                conHolder = new ConnectionHolder(con);
                TransactionSynchronizationManager.bindResource(ds, conHolder);
                TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(conHolder, ds));
            }
            return con;
        }
        catch (SQLException ex) {
            throw new CannotGetJdbcConnectionException("Could not get JDBC connection", ex);
        }
    }
}

從上述代碼咱們能夠看到,當經過 jdbcTemplate 操做數據庫時會先從當前上下文中獲取 connection; 這樣就保證了所獲取的事務與事務攔截器的事務爲同一個實例,也就是將事務交給了 Spring 來管理。

commit-事務提交
public final void commit(TransactionStatus status) throws TransactionException {
    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    // 省略
    else {
        try {
            try {
                triggerBeforeCommit(defStatus);
                triggerBeforeCompletion(defStatus);
                if (status.isNewTransaction()) {
                    logger.info("Initiating transaction commit");
                    // 執行事務提交
                    doCommit(defStatus);
                }
            }
            // 省略
        }
        finally {
            cleanupAfterCompletion(defStatus);
        }
    }
}

doCommit 執行事務提交

protected void doCommit(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    if (status.isDebug()) {
        logger.debug("Committing JDBC transaction [" + txObject.getConnectionHolder().getConnection() + "]");
    }
    try {
        // 事務提交
        txObject.getConnectionHolder().getConnection().commit();
    }
    catch (SQLException ex) {
        throw new TransactionSystemException("Could not commit", ex);
    }
}
resume-事務恢復

從上文的 commit 事務提交操做發現,在完成事務提交以後,還有個後置動做 cleanupAfterCompletion, 該方法會對掛起中的事務執行恢復操做。

private void cleanupAfterCompletion(DefaultTransactionStatus status) {
    if (status.isNewSynchronization()) {
        TransactionSynchronizationManager.clearSynchronization();
    }
    if (status.isNewTransaction()) {
        doCleanupAfterCompletion(status.getTransaction());
    }
    // 當存在掛起的事務時,執行恢復掛起的事務
    if (status.getSuspendedResources() != null) {
        if (status.isDebug()) {
            logger.debug("Resuming suspended transaction");
        }
        resume(status.getTransaction(), status.getSuspendedResources());
    }
}
protected void doResume(Object transaction, Object suspendedResources) {
    // 將掛起的事務綁定的 connection 從新綁定到當前上下文
    ConnectionHolder conHolder = (ConnectionHolder) suspendedResources;
    TransactionSynchronizationManager.bindResource(this.dataSource, conHolder);
}

事務的 resume 就是將掛起的事務從新綁定到當前上下文中。

rollback-事務回滾

TransactionInterceptor 調用目標方法執行出現異常的時候,會進行異常處理執行方法 onThrowable

private void onThrowable(MethodInvocation invocation, TransactionAttribute txAtt,
                         TransactionStatus status, Throwable ex) {
    if (txAtt.rollbackOn(ex)) {
        try {
            // 異常須要回滾
            this.transactionManager.rollback(status);
        }
        catch (TransactionException tex) {
            throw tex;
        }
    }
    else {
        // 異常不須要回滾的話 則提交事務
        this.transactionManager.commit(status);
    }
}

onThrowable 方法會經過配置判斷當前異常是否須要回滾。

public final void rollback(TransactionStatus status) throws TransactionException {
    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    try {
        try {
            triggerBeforeCompletion(defStatus);
            if (status.isNewTransaction()) {
                // 執行事務回滾
                logger.info("Initiating transaction rollback");
                doRollback(defStatus);
            }
            else if (defStatus.getTransaction() != null) {
                if (defStatus.isDebug()) {
                    logger.debug("Setting existing transaction rollback-only");
                }
                doSetRollbackOnly(defStatus);
            }
            else {
                logger.info("Should roll back transaction but cannot - no transaction available");
            }
        }
        catch (TransactionException ex) {
            triggerAfterCompletion(defStatus, TransactionSynchronization.STATUS_UNKNOWN, ex);
            throw ex;
        }
        triggerAfterCompletion(defStatus, TransactionSynchronization.STATUS_ROLLED_BACK, null);
    }
    finally {
        cleanupAfterCompletion(defStatus);
    }
}

protected void doRollback(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    try {
        // 執行回滾
        txObject.getConnectionHolder().getConnection().rollback();
    }
    catch (SQLException ex) {
        throw new TransactionSystemException("Could not rollback", ex);
    }
}

小結

此時咱們基本明白了 Spring Transaction 的實現原理,下面對其實現作個小結:

  • Spring Transaction 是基於 Spring AOP 的一種實現
  • Spring Transaction 經過配置建立事務 advisor 並建立目標對象代理類
  • 目標方法執行時將會被 TransactionInterceptor 攔截
  • TransactionInterceptor 會委派 TransactionManager 執行事務的建立,事務提交,事務回滾的動做
  • TransactionManager 會根據當前方法配置的事務傳播性及當前上下文是否存在事務來判斷是否新建事務
  • TransactionManager 當新建事務時會將事務綁定到當前上下文,以保證目標方法執行時獲取的事務爲同一實例
  • TransactionManager 執行事務掛起時會將當前事務與當前上下文解除綁定關係
  • TransactionManager 執行事務恢復時會將已掛起的事務從新與當前上下文綁定

Spring Transaction

相關文章
相關標籤/搜索