处理服务中的StaleObjectException
我有一个场景,当一个对象被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
}
它不工作,如果它被抛出并在控制器/服务中处理。它在投入使用并在控制器中处理时起作用。这怎么可能 ?
重试方法的问题是,有多少次重试就足够了?试试这个:
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。
当您在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/
问题是你应该总是重试整个事务。让事务回滚并重新创建一个新事物,因为旧事务是肮脏的(Hibernate会话无效,并且可能有一些未提交的更改已经刷新到数据库)。
是啊!这说得通。我也是这么想的。感谢您的回复。 –
感谢您的回复。实际上这两个线程都会更新不同的字段。他们从不更新公共领域。所以我认为放下版本领域应该更合适。不是吗? –
如果您没有任何其他代码可以更新此域并依赖乐观锁定,那么是的 - 只需关闭域的版本控制即可。 – Yaro