關於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事務超時時間的設置,要格外的注意,能夠參考事務超時這篇文章,對各層的超時時間的介紹及做用至關清楚。