Spring事务超时时间可能存在的错误认识
2017-12-17 11:11
501 查看
1、先看代码
1.1、spring-config.xml
Java代码
<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、测试用例
Java代码
@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方法:
Java代码
…………
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
…………
其中determineTimeout用来获取我们设置的事务超时时间;然后设置到ConnectionHolder对象上(其是ResourceHolder子类),接着看ResourceHolderSupport的setTimeoutInSeconds实现:
Java代码
public void setTimeoutInSeconds(int seconds) {
setTimeoutInMillis(seconds * 1000);
}
public void setTimeoutInMillis(long millis) {
this.deadline = new Date(System.currentTimeMillis() + millis);
}
大家可以看到,其会设置一个deadline时间;用来判断事务超时时间的;那什么时候调用呢?首先检查该类中的代码,会发现:
Java代码
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代码如下:
Java代码
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、结论
写道
Spring事务超时 = 事务开始时到最后一个Statement创建时时间 + 最后一个Statement的执行时超时时间(即其queryTimeout)。
4、因此
假设事务超时时间设置为2秒;假设sql执行时间为1秒;
如下调用是事务不超时的
Java代码
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);
}
而如下事务超时是起作用的;
Java代码
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/
1.1、spring-config.xml
Java代码
<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、测试用例
Java代码
@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方法:
Java代码
…………
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
…………
其中determineTimeout用来获取我们设置的事务超时时间;然后设置到ConnectionHolder对象上(其是ResourceHolder子类),接着看ResourceHolderSupport的setTimeoutInSeconds实现:
Java代码
public void setTimeoutInSeconds(int seconds) {
setTimeoutInMillis(seconds * 1000);
}
public void setTimeoutInMillis(long millis) {
this.deadline = new Date(System.currentTimeMillis() + millis);
}
大家可以看到,其会设置一个deadline时间;用来判断事务超时时间的;那什么时候调用呢?首先检查该类中的代码,会发现:
Java代码
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代码如下:
Java代码
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、结论
写道
Spring事务超时 = 事务开始时到最后一个Statement创建时时间 + 最后一个Statement的执行时超时时间(即其queryTimeout)。
4、因此
假设事务超时时间设置为2秒;假设sql执行时间为1秒;
如下调用是事务不超时的
Java代码
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);
}
而如下事务超时是起作用的;
Java代码
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事务超时时间可能存在的错误认识
- Spring事务超时时间可能存在的错误认识
- Spring事务超时时间可能存在的错误认识
- Spring事务超时时间可能存在的错误认识
- Spring事务超时时间可能存在的错误认识
- Spring事务超时时间可能存在的错误认识
- 刷新共享对象管理器缓存时发生意外错误。System.InvalidOperationException:超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并
- .net WCF 套接字连接中断,可能是由于消息处理错误,或者远程宿主接受超时引起,或者是底层网络资源问题导致,本地套接字时间
- spring 事务超时时间不起作用
- 连接字符串中Min Pool Size的理解是错误,超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。
- 20并发下支付接口存在响应时间超时问题及排查思路
- Timeout expired 超时时间已到. 达到了最大池大小 错误及Max Pool Size设置
- WCF 添加服务引用 HTTP 请求已超过为 00:00:00 分配的超时。为此操作分配的时间可能是较长超时
- Timeout expired 超时时间已到. 达到了最大池大小 错误及Max Pool Size设置
- HTTP 请求已超过分配的超时。为此操作分配的时间可能是较长超时的一部分
- 元素“UpdateProgress”不是已知元素。原因可能是网站中存在编译错误
- 超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。
- 超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小
- [转]超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。