如何克服 grails 服务中的 StaleObjectStateException
我引入了一个 TransactionService,我在控制器中使用它来执行乐观事务。它应该
- 尝试执行给定的事务(=闭包)
- ,如果失败则回滚,
- 如果失败则重试
它基本上看起来像这样:
class TransactionService {
transactional = false // Because withTransaction is used below anyway
def executeOptimisticTransaction(Closure transaction) {
def success = false
while (!success) {
anyDomainClass.withTransaction { status ->
try {
transaction()
success = true
} catch(Exception e) {
status.setRollbackOnly()
}
}
}
}
}
它有点复杂,例如,它在重试和中止之前使用不同的 Thread.sleeps在某个阶段,但这并不重要。它是由控制器调用的,控制器传递事务以作为闭包安全执行。
我的问题:当服务由于并发更新而遇到 org.hibernate.StaleObjectStateException 时,它会不断重试,但异常永远不会消失。
我已经尝试了不同的方法,例如在控制器传递的事务中重新附加域类,清除服务或控制器中的会话,但它没有帮助。我缺少什么?
我应该注意到,当我尝试在使用 status.createSavepoint() 调用 transaction() 之前插入 savePoint 时,出现“事务管理器不允许嵌套事务”的错误。我尝试这样做是因为我还怀疑存在错误,因为事务是从控制器传递到服务的,并且我需要启动一个新的/嵌套事务来避免它,但正如错误所示,这在我的情况下是不可能的。
或者也许将事务作为闭包传递是问题所在?
我认为 .withTransaction 之前使用的域类并不重要,是吗?
I've introduced a TransactionService that I use in my controllers to execute optimistic transactions. It should
- try to execute a given transaction (= closure)
- roll it back if it fails and
- try it again if it fails
It basically looks like this:
class TransactionService {
transactional = false // Because withTransaction is used below anyway
def executeOptimisticTransaction(Closure transaction) {
def success = false
while (!success) {
anyDomainClass.withTransaction { status ->
try {
transaction()
success = true
} catch(Exception e) {
status.setRollbackOnly()
}
}
}
}
}
It is a little more complex, e.g. it uses different Thread.sleeps before trying again and aborts at some stage, but that doesn't matter here. It's called from controllers who pass the transaction to be safely executed as a closure.
My Problem: When the service hits a org.hibernate.StaleObjectStateException due to concurrent updates, it keeps trying again but the Exception never disappears.
I already tried different things like re-attaching domain classes in the transaction passed by the controller, clearing the session in the service or in the controller, but it didn't help. What am I missing?
I should note that I got an error that my "Transaction Manager does not allow nested transactions" when I tried to insert a savePoint before transaction() is called using status.createSavepoint(). I tried this because I also suspected that the error exists because the transaction is passed from the controller to the service and that I needed to start a new / nested transaction to avoid it, but as the error shows this is not possible in my case.
Or maybe is passing the transaction as a closure the problem?
I assume that the domain class used before the .withTransaction doesn't matter, or does it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
它本身不是闭包,但我相信
transaction
内部有一些过时的变量引用。如果您尝试仅传递在执行时重新读取其对象的闭包该怎么办?就像
我不认为在 Hibernate 中不重新读取对象的情况下“刷新”对象是不可能的。
是的,您在哪个类上调用 .withTransaction() 并不重要。
对于更新计算的总计/评级的示例,数据重复本身就是一个问题。我宁愿:
Book.executeQuery('update Rating set rating=xxx')
将使用最新的评级。如果您正在针对重负载进行优化,那么您无论如何都不会以 Groovy 方式完成所有操作。不要在 Grails 中保存评级对象,而只能读取它们。It is not closure itself, but I believe
transaction
has some stale variable reference inside.What if you try to only pass closures that re-read their objects on execution? Like
I don't think it's possbile to "refresh" an object without re-reading it in Hibernate.
Yes, it doesn't matter what class you call .withTransaction() on.
For the example updating calculated totals/ratings, that's a data duplication that's a problem itself. I'd rather either:
Book.executeQuery('update Rating set rating=xxx')
that's going to use latest Rating. If you're optimizing for heavy load, you're anyway not going to do everything Groovy-way. Don't save Rating objects in Grails, only read them.