2015-08-08 32 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

是啊!这说得通。我也是这么想的。感谢您的回复。 –