在Service中处理StaleObjectException

时间:2015-08-08 09:09:53

标签: hibernate grails gorm optimistic-locking

我有一个场景,当一个对象被2个不同的线程更新。下面是grails服务类中的代码。我能够捕获StaleObject异常,但是当我尝试从数据库再次获取它并重试保存它不起作用的值时。

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
}

如果在控制器/服务中抛出并处理它,它就无法工作。它在投入使用并在控制器中处理时有效。怎么可能?

3 个答案:

答案 0 :(得分:2)

重点是您应该始终重试整个交易。让事务回滚并重复新事务,因为旧事务是脏的(Hibernate会话无效,可能有一些未提交的更改已经刷新到数据库中)。

答案 1 :(得分:1)

当您在catch块中执行refresh()然后save()时,可能会在刷新和保存之间更改testAttempted的实例,因此它会失败并出现相同的异常,现在你没有抓住,因为它已经在catch区块中了。

域名' {af}的get()方法缓存在会话中,因此TestAttempted.get(id)会从会话中返回实例,而不是db。

在这种情况下,

Merge()不是必需的,因为您在刷新后和保存之前手动设置值。

使用Domain.lock()可以是一个解决方案,但它会影响您在代码的其他部分处理TesttAttempted的方式,因为现在您可能会在您尝试的地方遇到CannotAcquireLock异常检索一个实例,它被这部分代码锁定。

问题是 - 什么是冲突解决策略?如果它是最后一位作家赢了' - 然后只为域名设置version= false。或者,您可以使用TestAttemted.executeUpdate('set timer = .. where id = ..')进行更新,而不会增加版本。

在更复杂的场景中,请参阅Mark Palmer对问题的深入报道。 http://www.anyware.co.uk/2005/2012/11/12/the-false-optimism-of-gorm-and-hibernate/

答案 2 :(得分: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