1、先看代码
1.1、spring-config.xml
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value=""/> </bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
1.2、测试用例
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-config.xml") @TransactionConfiguration(transactionManager = "txManager", defaultRollback = false) @Transactional(timeout = 2) public class Timeout1Test { @Autowired private DataSource ds; @Test public void testTimeout() throws InterruptedException { System.out.println(System.currentTimeMillis()); JdbcTemplate jdbcTemplate = new JdbcTemplate(ds); jdbcTemplate.execute(" update test set name = name || '1'"); System.out.println(System.currentTimeMillis()); Thread.sleep(3000L); } }
我设置事务超时时间是2秒;但我事务肯定执行3秒以上;为什么没有起作用呢? 这其实是对Spring实现的事务超时的错误认识。那首先分析下Spring事务超时实现吧。
2、分析
2.1、在此我们分析下DataSourceTransactionManager;首先开启事物会调用其doBegin方法:
………… int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } …………
其中determineTimeout用来获取我们设置的事务超时时间;然后设置到ConnectionHolder对象上(其是ResourceHolder子类),接着看ResourceHolderSupport的setTimeoutInSeconds实现:
public void setTimeoutInSeconds(int seconds) { setTimeoutInMillis(seconds * 1000); } public void setTimeoutInMillis(long millis) { this.deadline = new Date(System.currentTimeMillis() + millis); }
大家可以看到,其会设置一个deadline时间;用来判断事务超时时间的;那什么时候调用呢?首先检查该类中的代码,会发现:
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; } private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException { if (deadlineReached) { setRollbackOnly(); throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline); } }
会发现在调用getTimeToLiveInSeconds和getTimeToLiveInMillis,会检查是否超时,如果超时设置事务回滚,并抛出TransactionTimedOutException异常。到此我们只要找到调用它们的位置就好了,那什么地方调用的它们呢? 最简单的办法使用如“IntelliJ IDEA”中的“Find Usages”找到get***的使用地方;会发现:
DataSourceUtils.applyTransactionTimeout会调用DataSourceUtils.applyTimeout,DataSourceUtils.applyTimeout代码如下:
public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException { Assert.notNull(stmt, "No Statement specified"); Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder 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); } }
其中其在stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());中会调用getTimeToLiveInSeconds,此时就会检查事务是否超时;
然后在JdbcTemplate中,执行sql之前,会调用其applyStatementSettings:其会调用DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());设置超时时间;具体可以看其源码;
到此我们知道了在JdbcTemplate拿到Statement之后,执行之前会设置其queryTimeout,具体意思参考Javadoc:
3、结论
4、因此
假设事务超时时间设置为2秒;假设sql执行时间为1秒;
如下调用是事务不超时的
public void testTimeout() throws InterruptedException { System.out.println(System.currentTimeMillis()); JdbcTemplate jdbcTemplate = new JdbcTemplate(ds); jdbcTemplate.execute(" update test set hobby = hobby || '1'"); System.out.println(System.currentTimeMillis()); Thread.sleep(3000L); }
而如下事务超时是起作用的;
public void testTimeout() throws InterruptedException { Thread.sleep(3000L); System.out.println(System.currentTimeMillis()); JdbcTemplate jdbcTemplate = new JdbcTemplate(ds); jdbcTemplate.execute(" update test set hobby = hobby || '1'"); System.out.println(System.currentTimeMillis()); }
因此,不要忽略应用中如远程调用产生的事务时间和这个事务时间是否对您的事务产生影响。
另外:
1、事务超时不起作用,您要首先检查您的事务起作用了没:可以参考使用Aop工具类诊断常见问题
2、如果您用的JPA,且spring版本低于3.0,可能您的事务超时不起作用:https://jira.springsource.org/browse/SPR-5195
3、如果您用JDBC,但没有用JdbcTemplate,直接使用DateSourceUtils进行事务控制时,要么自己设置Statement的queryTimeout超时时间,要么使用TransactionAwareDataSourceProxy,其在创建Statement时会自动设置其queryTimeout。
4、关于JDBC超时时间设置一篇不错的翻译:深入理解JDBC的超时设置
http://www.cubrid.org/blog/dev-platform/understanding-jdbc-internals-and-timeout-configuration/
相关推荐
spring事务与数据库操作
Spring事务管理Demo
Spring事务流程图时序图Spring事务流程图时序图Spring事务流程图时序图Spring事务流程图时序图
此ppt中前半部分通过spring事务的60道题的测试,摸底对事务的掌握情况,后半部分,对spring中的事务属性(传播行为、隔离级别、回滚规则、事务超时、是否只读)进行说明
spring事务配置详解 spring事务配置详解
spring 事务spring 事务spring 事务spring 事务spring 事务
Spring事务原理、Spring事务配置的五种方式
spring学习事务源码spring学习事务源码spring学习事务源码spring学习事务源码spring学习事务源码spring学习事务源码spring学习事务源码spring学习事务源码
Spring中事务的传播属性详解,Spring中事务的传播属性详解
spring 事务传播 demo
spring事务的底层实现流程图 spring事务的底层实现流程图 spring事务的底层实现流程图 spring事务的底层实现流程图 spring事务的底层实现流程图 spring事务的底层实现流程图 spring事务的底层实现流程图 spring事务...
Spring事务操作示例(四种方式),包含完整代码和数据库文件(基于MySQL,在项目sql文件夹中),可运行,学习Spring事务详见博客:http://blog.csdn.net/daijin888888/article/details/51822257
spring事务源码解析
Java高级编程 实验报告 spring 声明事务 实验目的 掌握spring 声明式事务管理配置 实验环境 本实验采用本实验采用的eclipse或者 Myeclpse开发工具。Spring 4.0以上 Jdk1.7以上、oracle/mysql。
Spring的事务框架将开发过程中事务管理相关的关注点进行适当的分离,并对这些关注点进行合 理的抽象,最终打造了一套使用方便,却功能强大的事务管理“利器”。通过Spring的事务框架,我 们可以按照统一的编程模型来...
通过代码解析spring传播特性,包括 ... 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。需要JDBC3.0以上支持。
Spring事务管理教程,详细讲解了Spring中的事务管理,包括声明式事务,注解式事务,以及事务配置等等
spring事务控制jar包,请网上自寻下载
Spring事务失效Spring事务失效
Spring事务管理.pdf 1.资料 2.本地事务与分布式事务 3.编程式模型 4.宣告式模型