2015-08-08 72 views
0

我有一個場景,當一個對象被2個不同線程更新時。以下是grails服務類中的代碼。我能夠捕獲StaleObject異常,但是當我嘗試從數據庫中再次獲取並重試保存值時,它不起作用。處理服務中的StaleObjectException

public long updateTimer(Long timeLeft, TestAttempted testAttempted){ 
    // Let's say testAttempted.version() is now 5 
    // It is concurrently updated by other thread, version is now 6 
    ........ 
    ............ 
    testAttempted.setTimer(someCalculatedValue) 
    try{ 
     testAttempted.save(failOnError: true,flush:true) // StaleObject exception occurs 
    }catch(OptimisticLockingFailureException e){ 
     testAttempted.refresh() 
     testAttempted.setTimer(someCalculatedValue) 
     testAttempted.save(failOnError:true) 
    } 
} 

爲什麼上面的代碼不會更新/保存catch塊中的值?我也嘗試TestAttempted.get(id)方法從數據庫中獲取最新的一個,但它不起作用。

但是當我嘗試這一點,更新最新的定時器值:

在控制器: -

try{ 
     timeLeft = assessmentService.updateTimer(timeLeft,testAttempted) 
    }catch(OptimisticLockingFailureException e){ 
     testAttempted = TestAttempted.get(session['testAttemptedId']) 
     ........ 
     testAttempted.setTimer(someCalculatedValue) 
     testAttempted.save(failOnError: true) 
    } 

在服務:

public long updateTimer(Long timeLeft, TestAttempted testAttempted){ 
    ........ 
    ................. 
    testAttempted.setTimer(someValue) 
    testAttempted.save(failOnError: true) 
    return timeLeft 
} 

它不工作,如果它被拋出並在控制器/服務中處理。它在投入使用並在控制器中處理時起作用。這怎麼可能 ?

回答

0

重試方法的問題是,有多少次重試就足夠了?試試這個:

class AssessmentService { 

    /* 
    * Services are transactional by default. 
    * I'm just making it explicit here. 
    */ 
    static transactional = true 

    public long updateTimer(Long timeLeft, Long testAttemptedId) { 
     def testAttempted = TestAttempted.lock(testAttemptedId) 

     testAttempted.setTimer(someCalculatedValue) 
     testAttempted.save() 
    } 
} 

傳遞一個TestAttempted ID,而不是一個實例,這樣的服務可以檢索自己的實例,用它自己的事務。

如果您想要傳入TestAttempted實例,我相信您必須調用testAttempted。 merge()在對該實例進行更改之前的服務方法中。

這是類似的question

1

當您在catch塊中執行refresh()然後save()時,testAttempted的實例在刷新和保存之間發生更改,因此失敗時會出現相同的異常,現在您不會捕獲它,因爲它已經在catch塊中了。

域的'get()方法afaik被緩存在會話中,所以TestAttempted.get(id)會從會話中返回實例,而不是數據庫。

Merge()在這種情況下不是必需的,因爲您在刷新之後和保存之前手動設置該值。

使用Domain.lock()可以是一個解決方案,但它會影響你如何處理TesttAttempted在代碼的其他部分,因爲現在你可能會在你想獲得實例的地方CannotAcquireLock例外,它已被鎖定通過這部分代碼。

問題是 - 什麼是衝突解決策略?如果它是'上次作家獲勝' - 那麼只需爲該域設置version= false即可。或者,您可以使用TestAttemted.executeUpdate('set timer = .. where id = ..')進行更新,而不增加版本。

即時更復雜的情況下,請諮詢馬克帕爾默的問題的深入報道。 http://www.anyware.co.uk/2005/2012/11/12/the-false-optimism-of-gorm-and-hibernate/

+0

感謝您的回覆。實際上這兩個線程都會更新不同的字段。他們從不更新公共領域。所以我認爲放下版本領域應該更合適。不是嗎? –

+0

如果您沒有任何其他代碼可以更新此域並依賴樂觀鎖定,那麼是的 - 只需關閉域的版本控制即可。 – Yaro

1

問題是你應該總是重試整個事務。讓事務回滾並重新創建一個新事物,因爲舊事務是骯髒的(Hibernate會話無效,並且可能有一些未提交的更改已經刷新到數據庫)。

+0

是啊!這說得通。我也是這麼想的。感謝您的回覆。 –