Spring事務管理(五)-超時時間

關於Spring事務超時時間的實現,一直都沒太弄清楚,終於在看到一篇事務超時文章後,經過測試用例證實一般狀況下@Transactional中配置的timeout都是無效的。mysql

首先說明下測試的注意事項,就是除了@Transactional的timeout配置外,不要配置其餘超時時間,好比mybatis xml中sql的timeout,jdbc properties中的socket timeout(connectionTimeout和socketTimeout)以及mysql的innodb_lock_wait_timeout(默認50s)。spring

測試方法以下,超時時間爲1s,線程中等待3s,使用Mybatis方式配置sqlsql

@Transactional(timeout=1)
public void batchUpdate(){
    
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    
    int updateRow = userMapper.batchUpdate(userList);
    System.out.println("updateRow:" + updateRow);
}

測試結果,事務執行成功了,第一次測試的時候,我也是驚呆了。什麼鬼??項目裏配置的timeout居然都是擺設。mybatis

再來測試JdbcTemplate直接執行sql的方法app

public class UserJdbcTemplateMapper {

	private JdbcTemplate jdbcTemplate;
	
	public int updateUser(){
		return jdbcTemplate.update("update user set age = 10 where id = 1");
	}
}

@Transactional仍是上面的配置,測試結果爲socket

org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Feb 02 20:48:43 CST 2018

建議你們本身測試一下,親自體驗的感受特別好。下面根據DataSourceTransactionManager來分析其原理。ide

抽象AbstractPlatformTransactionManager對@Transactional的超時時間沒有任何處理,而在DataSourceTransactionManager的doBegin方法中將其設置到ConnectionHolder中。測試

if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
    txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}

實際上就是設置了一個deadlinethis

public void setTimeoutInSeconds(int seconds) {
    setTimeoutInMillis(seconds * 1000);
}

public void setTimeoutInMillis(long millis) {
    this.deadline = new Date(System.currentTimeMillis() + millis);
}

而對deadline的校驗的方法就是checkTransactionTimeout,在獲取超時時間的方法裏被執行線程

// 獲取剩餘時間(單位爲秒)
public int getTimeToLiveInSeconds() {
    double diff = ((double) getTimeToLiveInMillis()) / 1000;
    int secs = (int) Math.ceil(diff);
    checkTransactionTimeout(secs <= 0);
    return secs;
}

// 獲取剩餘時間(單位爲毫秒)
public long getTimeToLiveInMillis() throws TransactionTimedOutException{
    if (this.deadline == null) {
        throw new IllegalStateException("No timeout specified for this resource holder");
    }
    long timeToLive = this.deadline.getTime() - System.currentTimeMillis();
    checkTransactionTimeout(timeToLive <= 0);
    return timeToLive;
}

// 校驗是否超時,拋出TransactionTimedOutException異常
private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {
    if (deadlineReached) {
        setRollbackOnly();
        throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);
    }
}

而對獲取剩餘時間方法的調用爲DataSourceUtils的applyTimeout,將超時時間轉換爲JDBC的Statement的queryTimeout,於是Spring事務的超時時間也就是經過Statement的超時來實現。

public static void applyTimeout(Statement stmt, @Nullable DataSource dataSource, int timeout) throws SQLException {
    Assert.notNull(stmt, "No Statement specified");
    ConnectionHolder holder = null;
    if (dataSource != null) {
        holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    }
    if (holder != null && holder.hasTimeout()) {
        // Remaining transaction timeout overrides specified value.
        stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());
    }
    else if (timeout >= 0) {
        // No current transaction timeout -> apply specified value.
        stmt.setQueryTimeout(timeout);
    }
}

但是applyTimeout的調用者只有JdbcTemplate和TransactionAwareDataSourceProxy。在JdbcTemplate的execute方法中經過applyStatementSettings方法設置了超時時間。而TransactionAwareDataSourceProxy則是JDK代理的InvocationHandler的實現類,感受應該是DataSource的代理類。可是縱觀Spring事務管理的核心實現方法中獲取DataSource的操做,都沒有對原始DataSource進行代理的操做,甚至在DataSourceTransactionManager的setDataSource方法中,判斷若是DataSource爲TransactionAwareDataSourceProxy類型,則獲取其原始DataSource。

public void setDataSource(@Nullable DataSource dataSource) {
    if (dataSource instanceof TransactionAwareDataSourceProxy) {
        this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
    }
    else {
        this.dataSource = dataSource;
    }
}

所以就算在xml中配置了TransactionAwareDataSourceProxy來代理原始DataSource,對於DataSourceTransactionManager來講,只是虛設。

<bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
    <constructor-arg index="0" ref="basicDataSource"></constructor-arg>
</bean>

所以對於Spring事務超時時間的設置,要格外的注意,能夠參考事務超時這篇文章,對各層的超時時間的介紹及做用至關清楚。

相關文章
相關標籤/搜索