如何克服 grails 服务中的 StaleObjectStateException

发布于 2024-10-06 17:55:16 字数 1095 浏览 2 评论 0原文

我引入了一个 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

渡你暖光 2024-10-13 17:55:16

它本身不是闭包,但我相信 transaction 内部有一些过时的变量引用。
如果您尝试仅传递在执行时重新读取其对象的闭包该怎么办?就像

executeOptimisticTransaction {
  Something some = Something.get(id)
  some.properties = aMap
  some.save()
}

我不认为在 Hibernate 中不重新读取对象的情况下“刷新”对象是不可能的。

是的,您在哪个类上调用 .withTransaction() 并不重要。

对于更新计算的总计/评级的示例,数据重复本身就是一个问题。我宁愿:

  1. 创建一个(Quartz)作业,该作业将根据某些“脏”标志更新评级 - 这可能会节省一些 DB CPU 以更新时间成本;
  2. 或者用 SQL 或 HQL 执行此操作,例如 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

executeOptimisticTransaction {
  Something some = Something.get(id)
  some.properties = aMap
  some.save()
}

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:

  1. create a (Quartz) job that will update ratings based on some "dirty" flag - that might save some DB CPU for a cost of update time;
  2. or do it in SQL or HQL, like 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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文