生活随笔
收集整理的這篇文章主要介紹了
【转】Spring事务超时时间可能存在的错误认识
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
2019獨角獸企業重金招聘Python工程師標準>>>
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/
轉載于:https://my.oschina.net/javahongxi/blog/1523722
總結
以上是生活随笔為你收集整理的【转】Spring事务超时时间可能存在的错误认识的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。