摘自:http://jinnianshilongnian.iteye.com/blog/1986023, 感谢作者。

1、先看代码

1.1、spring-config.xml

  1. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  2. <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  3. <property name="url" value="jdbc:mysql://localhost:3306/test?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
  4. <property name="username" value="root"/>
  5. <property name="password" value=""/>
  6. </bean>
  7. <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  8. <property name="dataSource" ref="dataSource"/>
  9. </bean>

1.2、测试用例

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(locations = "classpath:spring-config.xml")
  3. @TransactionConfiguration(transactionManager = "txManager", defaultRollback = false)
  4. @Transactional(timeout = 2)
  5. public class Timeout1Test {
  6. @Autowired
  7. private DataSource ds;
  8. @Test
  9. public void testTimeout() throws InterruptedException {
  10. System.out.println(System.currentTimeMillis());
  11. JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);
  12. jdbcTemplate.execute(" update test set name = name || '1'");
  13. System.out.println(System.currentTimeMillis());
  14. Thread.sleep(3000L);
  15. }
  16. }

我设置事务超时时间是2秒;但我事务肯定执行3秒以上;为什么没有起作用呢?  这其实是对Spring实现的事务超时的错误认识。那首先分析下Spring事务超时实现吧。

2、分析

2.1、在此我们分析下DataSourceTransactionManager;首先开启事物会调用其doBegin方法:

  1. …………
  2. int timeout = determineTimeout(definition);
  3. if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
  4. txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
  5. }
  6. …………

其中determineTimeout用来获取我们设置的事务超时时间;然后设置到ConnectionHolder对象上(其是ResourceHolder子类),接着看ResourceHolderSupport的setTimeoutInSeconds实现:

  1. public void setTimeoutInSeconds(int seconds) {
  2. setTimeoutInMillis(seconds * 1000);
  3. }
  4. public void setTimeoutInMillis(long millis) {
  5. this.deadline = new Date(System.currentTimeMillis() + millis);
  6. }

大家可以看到,其会设置一个deadline时间;用来判断事务超时时间的;那什么时候调用呢?首先检查该类中的代码,会发现:

  1. public int getTimeToLiveInSeconds() {
  2. double diff = ((double) getTimeToLiveInMillis()) / 1000;
  3. int secs = (int) Math.ceil(diff);
  4. checkTransactionTimeout(secs <= 0);
  5. return secs;
  6. }
  7. public long getTimeToLiveInMillis() throws TransactionTimedOutException{
  8. if (this.deadline == null) {
  9. throw new IllegalStateException("No timeout specified for this resource holder");
  10. }
  11. long timeToLive = this.deadline.getTime() - System.currentTimeMillis();
  12. checkTransactionTimeout(timeToLive <= 0);
  13. return timeToLive;
  14. }
  15. private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {
  16. if (deadlineReached) {
  17. setRollbackOnly();
  18. throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);
  19. }
  20. }

会发现在调用getTimeToLiveInSeconds和getTimeToLiveInMillis,会检查是否超时,如果超时设置事务回滚,并抛出TransactionTimedOutException异常。到此我们只要找到调用它们的位置就好了,那什么地方调用的它们呢? 最简单的办法使用如“IntelliJ IDEA”中的“Find Usages”找到get***的使用地方;会发现:

DataSourceUtils.applyTransactionTimeout会调用DataSourceUtils.applyTimeout,DataSourceUtils.applyTimeout代码如下:

  1. public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException {
  2. Assert.notNull(stmt, "No Statement specified");
  3. Assert.notNull(dataSource, "No DataSource specified");
  4. ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  5. if (holder != null && holder.hasTimeout()) {
  6. // Remaining transaction timeout overrides specified value.
  7. stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());
  8. }
  9. else if (timeout > 0) {
  10. // No current transaction timeout -> apply specified value.
  11. stmt.setQueryTimeout(timeout);
  12. }
  13. }

其中其在stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());中会调用getTimeToLiveInSeconds,此时就会检查事务是否超时;

然后在JdbcTemplate中,执行sql之前,会调用其applyStatementSettings:其会调用DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());设置超时时间;具体可以看其源码;

到此我们知道了在JdbcTemplate拿到Statement之后,执行之前会设置其queryTimeout,具体意思参考Javadoc:

3、结论

写道
Spring事务超时 = 事务开始时到最后一个Statement创建时时间 + 最后一个Statement的执行时超时时间(即其queryTimeout)。

4、因此

假设事务超时时间设置为2秒;假设sql执行时间为1秒;

如下调用是事务不超时的

  1. public void testTimeout() throws InterruptedException {
  2. System.out.println(System.currentTimeMillis());
  3. JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);
  4. jdbcTemplate.execute(" update test set hobby = hobby || '1'");
  5. System.out.println(System.currentTimeMillis());
  6. Thread.sleep(3000L);
  7. }

而如下事务超时是起作用的;

  1. public void testTimeout() throws InterruptedException {
  2. Thread.sleep(3000L);
  3. System.out.println(System.currentTimeMillis());
  4. JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);
  5. jdbcTemplate.execute(" update test set hobby = hobby || '1'");
  6. System.out.println(System.currentTimeMillis());
  7. }

因此,不要忽略应用中如远程调用产生的事务时间和这个事务时间是否对您的事务产生影响。

另外:

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事务超时时间可能存在的错误认识的更多相关文章

  1. 【转】Spring事务超时时间可能存在的错误认识

    1.先看代码 1.1.spring-config.xml <bean id="dataSource" class="org.springframework.jdbc ...

  2. Spring事务超时、回滚的相关说明

    事务超时: @Transactional(timeout = 60) 如果用这个注解描述一个方法的话,线程已经跑到方法里面,如果已经过去60秒了还没跑完这个方法并且线程在这个方法中的后面还有涉及到对数 ...

  3. spring cloud 超时时间

    zuul.host.socket-timeout-millis=60000 #zuul socket连接超时zuul.host.connect-timeout-millis=60000 #zull 请 ...

  4. Spring事务隔离级别与传播机制详解,spring+mybatis+atomikos实现分布式事务管理

    原创说明:本文为本人原创作品,绝非他处转载,转账请注明出处 1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). ...

  5. spring事务中隔离级别和spring的事务传播机制

    Transaction 也就是所谓的事务了,通俗理解就是一件事情.从小,父母就教育我们,做事情要有始有终,不能半途而废. 事务也是这样,不能做一般就不做了,要么做完,要 么就不做.也就是说,事务必须是 ...

  6. spring事务:事务控制方式,使用AOP控制事务,七种事务传播行为,声明事务,模板对象,模板对象原理分析

    知识点梳理 课堂讲义 1)事务回顾 1.1)什么是事务-视频01 事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败. 1.2)事务的作用 事务特征(ACID) 原子 ...

  7. spring事务详解(基于注解和声明的两种实现方式)

    Spring事务( Transaction ) 事务的概念 事务是一些sql语句的集合,作为一个整体执行,一起成功或者一起失败. 使用事务的时机 一个操作需要多天sql语句一起完成才能成功 程序中事务 ...

  8. MyBatis6:MyBatis集成Spring事务管理(下篇)

    前言 前一篇文章<MyBatis5:MyBatis集成Spring事务管理(上篇)>复习了MyBatis的基本使用以及使用Spring管理MyBatis的事务的做法,本文的目的是在这个的基 ...

  9. MyBatis(6):MyBatis集成Spring事务管理(下)

    前一篇文章复习了MyBatis的基本使用以及使用Spring管理MyBatis的事务的做法,本文的目的是在这个的基础上稍微做一点点的进阶:多数据的事务处理.文章内容主要包含两方面: 1.单表多数据的事 ...

随机推荐

  1. DIV相对于父DIV底部对齐的实现方法

    代码如下 <style type="text/css"> .box1 {border:1px #cccccc solid; width:500px; height:60 ...

  2. 【性能优化】——前端性能优化之DOM

    前言:本文参考学习自 RenChao Guan的博客,来源FSUX.ME,感谢原作者,本文的思维导图为自己整理 补充: 浏览器工作流程 避免重绘和回流的四种方式的具体实现

  3. phonegap(cordova) 自己定义插件代码篇(三)----支付宝支付工具整合

    建议读者,先阅读官方文档,知晓其支付流程之后再来使用此代码,比方客户须要做什么,服务端须要做什么(非常重要!非常重要! 非常重要!),由于这几个篇幅都是纯代码篇,由于阅读前面的入门篇之后看这些应该毫无 ...

  4. U盘启动盘恢复为普通盘

    U盘启动盘恢复为普通盘 此操作必须借助软件完成. 所用软件:diskgenius 下载地址: https://pan.baidu.com/s/1geDkK7L 密码: 8888   先将u盘中文件拷贝 ...

  5. kettle转换之多线程

    kettle转换之多线程   ETL项目中性能方面的考虑一般是最重要的.特别是所讨论的任务频繁运行,或一些列的任务必须在固定的时间内运行.本文重点介绍利用kettle转换的多线程特性.以优化其性能. ...

  6. frame框架及其实例

    框架概念 : 谓框架便是网页画面分成几个框窗,同时取得多个 URL.只需要 <FRAMESET> <FRAME> 即可,面所有框架标记需要放在一个总起的 html 档,这个档案 ...

  7. MySQL -进阶

    一.视图 视图是一个虚拟表(非真实存在),其本质是[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,并可以将其当作表来使用 SELECT * FROM(SELE ...

  8. 字符串各个字符ASCII值加5

    程序实现目标: 输入一个字符串,将其各个字符对应的ASCII值加5后,输出结果 程序要求:该字符串只包含小写字母,若其值加5后的字符值大于'z',将其转换成从a开始的字符. 分析:问题归结为三点: 1 ...

  9. HTTP状态码中301与302的区别

    301 Moved Permanently 被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个URI之一.如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改 ...

  10. Windows 命令集合

    查看端口占用 查看8080端口使用情况: C:\>netstat -aon|findstr "8080" 结果:TCP    0.0.0.0:8080           0 ...