我有一个场景,当一个对象被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
}
如果在控制器/服务中抛出并处理它,它就无法工作。它在投入使用并在控制器中处理时有效。怎么可能?
答案 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。