天天看點

Spring事務逾時時間可能存在的錯誤認識

1、先看代碼

1.1、spring-config.xml

Spring事務逾時時間可能存在的錯誤認識

<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"/>  

1.2、測試用例

Spring事務逾時時間可能存在的錯誤認識

@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'");  

        thread.sleep(3000l);  

    }  

}  

我設定事務逾時時間是2秒;但我事務肯定執行3秒以上;為什麼沒有起作用呢?  這其實是對spring實作的事務逾時的錯誤認識。那首先分析下spring事務逾時實作吧。

2、分析

2.1、在此我們分析下datasourcetransactionmanager;首先開啟事物會調用其dobegin方法:

Spring事務逾時時間可能存在的錯誤認識

…………  

int timeout = determinetimeout(definition);  

if (timeout != transactiondefinition.timeout_default) {  

    txobject.getconnectionholder().settimeoutinseconds(timeout);  

 其中determinetimeout用來擷取我們設定的事務逾時時間;然後設定到connectionholder對象上(其是resourceholder子類),接着看resourceholdersupport的settimeoutinseconds實作:

Spring事務逾時時間可能存在的錯誤認識

public void settimeoutinseconds(int seconds) {  

    settimeoutinmillis(seconds * 1000);  

public void settimeoutinmillis(long millis) {  

    this.deadline = new date(system.currenttimemillis() + millis);  

大家可以看到,其會設定一個deadline時間;用來判斷事務逾時時間的;那什麼時候調用呢?首先檢查該類中的代碼,會發現:

Spring事務逾時時間可能存在的錯誤認識

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代碼如下:

Spring事務逾時時間可能存在的錯誤認識

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秒;

如下調用是事務不逾時的

Spring事務逾時時間可能存在的錯誤認識

public void testtimeout() throws interruptedexception {  

    system.out.println(system.currenttimemillis());  

    jdbctemplate jdbctemplate = new jdbctemplate(ds);  

    jdbctemplate.execute(" update test set hobby = hobby || '1'");  

    thread.sleep(3000l);  

而如下事務逾時是起作用的;

Spring事務逾時時間可能存在的錯誤認識

是以,不要忽略應用中如遠端調用産生的事務時間和這個事務時間是否對您的事務産生影響。

另外:

3、如果您用jdbc,但沒有用jdbctemplate,直接使用datesourceutils進行事務控制時,要麼自己設定statement的querytimeout逾時時間,要麼使用transactionawaredatasourceproxy,其在建立statement時會自動設定其querytimeout。

原文連結:[http://wely.iteye.com/blog/2311700]