关于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事务超时时间的设置,要格外的注意,能够参考事务超时这篇文章,对各层的超时时间的介绍及做用至关清楚。