【问题标题】:Catching RuntimeExceptions on grails transactions在 grails 事务中捕获 RuntimeExceptions
【发布时间】:2015-10-14 20:18:52
【问题描述】:

目前,我们有一个调用事务服务的 grails 作业。当服务抛出异常时,hibernate 的行为会变得很奇怪。我们正在使用 grails 2.4.4 和 hibernate:3.6.10.18。

所以在我的工作中,我在执行方法上有这个:

Model.withTransaction { ->
    try {
        service.updateDatabase()
        service.method()//throws runtime exception
        } catch(RuntimeException e) {
            //do something
    }
}

奇怪的是,updateDatabase 操作确实会回滚。查看日志,我可以验证它是否在 catch 块中通过,但仍然日志表明仍然抛出异常。我认为这就是交易回滚的原因。

但是,如果我直接在作业中抛出RuntimeException,它不会回滚数据库事务并且会干净地捕获异常。在我的印象中,这应该是正确的行为,应该和从服务内部抛出异常一样。

Model.withTransaction { ->
    try {
        service.updateDatabase()
        throw new RuntimeException()
        } catch(RuntimeException e) {
            //do something
    }
}

这正常吗? 这是一个错误吗?

【问题讨论】:

  • 我不明白你的问题,如果你能帮我解决它,我就能帮你。首先你说“奇怪的是,updateDatabase 操作没有回滚”,然后“我认为这就是事务回滚的原因。”。帮助我了解 updateDatabase 是否回滚。
  • @juandiegoh 抱歉打错字了,我更新了问题。
  • 我回答了我认为是您的问题,但我仍然不明白。您的案例不会回滚,您认为“正确的行为”也不会回滚。
  • @juandiegoh 再次道歉,我的问题已得到更正

标签: hibernate grails grails-orm quartz-scheduler


【解决方案1】:

预期的行为是:

Model.withTransaction { -> // Creates new Transaction
    try {
        service.updateDatabase() // uses the same Transaction that was created before
        service.method() // uses the same Transaction that was created before
                         // throws runtime exception
                         // sets Transaction as rollbackOnly
        } catch(RuntimeException e) {
            //do something
    }
} // as the Transaction was set as rollbackOnly it rollbacks everything that was done before

基本上这是预期的行为,现在是解释。

您的所有服务方法都是事务性的,因为您的服务在其名称上方有一个@Transactional。

@Transactional
class MyTransactionalService {
...
}

默认情况下,每个事务方法都设置了 PROPAGATION.REQUIRED 属性。

/**
* Support a current transaction, create a new one if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>This is the default setting of a transaction annotation.
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)

这意味着当服务方法运行时,它使用在您的作业中创建的当前事务。棘手的部分来了,当一个功能有多个事务部分时,它会评估每个部分的回滚条件,所以当你的 method() 抛出 RuntimeException 时,它会将你的事务设置为 rollbackOnly 但一直持续到该事务结束(当您的 Model.withTransaction.. 完成时)并且就在那一刻它回滚所有内容。

更深入地讲,您可以在三个部分中将 Transaction 设置为 rollbackOnly。

  1. 如果updateDatabase()抛出异常,所有Transaction都会被设置为rollbackOnly
  2. 如果method()抛出异常,所有Transaction都会被设置为rollbackOnly
  3. 如果您传递给 withTransaction{..} 的闭包抛出异常,则所有 Transaction 都将设置为 rollbackOnly。

当事务结束时事务将回滚,而那一刻是在 withTransaction{..} 完成之后。

所以你需要非常小心你的交易。

要解决您的问题,您可以通过在 Service 类中仅将 updateDatabase() 设置为事务性并从上方删除 @Transactional 来使您的 method() 不是事务性的服务名称。

class YourService {

    @Transactional
    def updateDatabase() {
        //...
    }

    def method() {
        //...
    }
}

仅将一个方法设置为 @Transactional 只会将该方法设置为事务性,而不是让所有方法都具有事务性。

我制作了一个项目作为示例,您可以运行测试并自己检查它,我还设置了 log4j 以显示事务的生命周期,以便您更好地理解它,您只需要运行 MyProcessorIntegrationSpec.groovy

https://github.com/juandiegoh/grails-transactions-rollback

希望对你有帮助!

【讨论】:

  • 在两个服务调用上都包含一个事务意味着 service.updateDatabase 和 service.method 都将在同一个事务中,无论我是否删除了服务方法上的注释,对吗?因此,如果我将 method() 设置为非事务性的,它仍然是事务性的。正如你所说,使用工作的交易。
  • 我是否正确地假设捕获运行时异常意味着它将不再设置为仅回滚?这就是第二个场景不回滚的原因
  • 每次事务方法以 RuntimeException 结束时,都会将事务设置为 rollbackOnly。如果您按照我在答案中告诉您的那样设置 YourService ,则意味着 updateDatabase() 是事务性的,而 method() 不是,因此如果方法以 RuntimeException 结尾,则不会将事务设置为 rollbackOnly。
  • 第二种情况没有回滚的原因是因为没有事务方法结束抛出异常。 updateDatabase() 不以异常结束,Model.withTransaction { .... } 也不以异常结束。
猜你喜欢
  • 2010-12-13
  • 1970-01-01
  • 2016-11-25
  • 2017-01-12
  • 1970-01-01
  • 2016-07-06
  • 1970-01-01
  • 2016-02-10
  • 1970-01-01
相关资源
最近更新 更多