Spring事务超时时间可能存在的错误认识
摘自:http://jinnianshilongnian.iteye.com/blog/1986023, 感谢作者。
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事务超时时间可能存在的错误认识
1.先看代码 1.1.spring-config.xml <bean id="dataSource" class="org.springframework.jdbc ...
- Spring事务超时、回滚的相关说明
事务超时: @Transactional(timeout = 60) 如果用这个注解描述一个方法的话,线程已经跑到方法里面,如果已经过去60秒了还没跑完这个方法并且线程在这个方法中的后面还有涉及到对数 ...
- spring cloud 超时时间
zuul.host.socket-timeout-millis=60000 #zuul socket连接超时zuul.host.connect-timeout-millis=60000 #zull 请 ...
- Spring事务隔离级别与传播机制详解,spring+mybatis+atomikos实现分布式事务管理
原创说明:本文为本人原创作品,绝非他处转载,转账请注明出处 1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). ...
- spring事务中隔离级别和spring的事务传播机制
Transaction 也就是所谓的事务了,通俗理解就是一件事情.从小,父母就教育我们,做事情要有始有终,不能半途而废. 事务也是这样,不能做一般就不做了,要么做完,要 么就不做.也就是说,事务必须是 ...
- spring事务:事务控制方式,使用AOP控制事务,七种事务传播行为,声明事务,模板对象,模板对象原理分析
知识点梳理 课堂讲义 1)事务回顾 1.1)什么是事务-视频01 事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败. 1.2)事务的作用 事务特征(ACID) 原子 ...
- spring事务详解(基于注解和声明的两种实现方式)
Spring事务( Transaction ) 事务的概念 事务是一些sql语句的集合,作为一个整体执行,一起成功或者一起失败. 使用事务的时机 一个操作需要多天sql语句一起完成才能成功 程序中事务 ...
- MyBatis6:MyBatis集成Spring事务管理(下篇)
前言 前一篇文章<MyBatis5:MyBatis集成Spring事务管理(上篇)>复习了MyBatis的基本使用以及使用Spring管理MyBatis的事务的做法,本文的目的是在这个的基 ...
- MyBatis(6):MyBatis集成Spring事务管理(下)
前一篇文章复习了MyBatis的基本使用以及使用Spring管理MyBatis的事务的做法,本文的目的是在这个的基础上稍微做一点点的进阶:多数据的事务处理.文章内容主要包含两方面: 1.单表多数据的事 ...
随机推荐
- sprinvmvc整合swagger实现实时接口信息展示
1.pom.xml引入swagger插件 <dependency> <groupId>io.springfox</groupId> <artifactId&g ...
- 关于js对象的基础使用方法-《javascript设计模式》读书笔记
一.利用对象收编变量 当我们决定实现某一项功能的时候最简单的其实就是写一个命名函数,然后调用来实现,就像这样: function checkName(){ //验证姓名 } function chec ...
- mongodb读写分离的一些选项的理解
默认情况下 驱动程序会将所有的请求路由到主节点 这通常也是你需要的 但是也可以通过设置驱动程序的读取首选项(read preferences)配置其他选项 可以在读选项中设置需要将查询路由到的服务器的 ...
- 【APT】NodeJS 应用仓库钓鱼,大规模入侵开发人员电脑,批量渗透各大公司内网
APT][社工]NodeJS 应用仓库钓鱼,大规模入侵开发人员电脑,批量渗透各大公司内网 前言 城堡总是从内部攻破的.再强大的系统,也得通过人来控制.如果将入侵直接从人这个环节发起,那么再坚固的防线, ...
- 微信小程序-获取用户信息(getUserInfo)
当小程序抹杀掉这个接口的时候,多少人心凉了.. 作为一个初级web前端开发,我是更加懵逼,小程序员跑路了... 当时以及现在用的办法就是: 1.增加一个登陆或授权页 2.上线以后自动获取 3.增加一个 ...
- vue1和vue2获取dom元素的方法 及 nextTick() 、$nextTick()
vue1.*版本中 在标签中加上el='dom',然后在代码中this.$els.dom这样就拿到了页面元素 例如:<div class='box' el='myBox'>你好</d ...
- 【LeetCode】Search in Rotated Sorted Array——旋转有序数列找目标值
[题目] Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 ...
- struts2获取服务器临时目录
CreateTime--2017年9月7日08:57:39 Author:Marydon struts2获取服务器(tomcat.WebLogic)的临时目录 需要导入: import java. ...
- cartographer Ubuntu16.04 ros环境配置
首先要正确安装 ROS ,然后第12步应注意,proto的版本是个关键容易出错. 1.添加ROS源http:/packages.ros.org/ros/ubuntu xenial main ( ...
- npm常用命令(转)
npm install <name>安装nodejs的依赖包 例如npm install express 就会默认安装express的最新版本,也可以通过在后面加版本号的方式安装指定版本, ...