捕获grails事务上的运行时异常



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

那么在我的工作中,我在execute方法中有这个:

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
    }
}

这正常吗?这是臭虫吗?

预期行为为:

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 {
...
}

默认情况下,每个Transactional方法都有PROPAGATION。必需的属性集。

/**
* 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)

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

更深入,有三个部分可以将Transaction设置为rollbackOnly。

  1. 如果 updateddatabase ()抛出异常,所有事务将被设置为rollbackOnly
  2. 如果方法()抛出异常,所有事务将被设置为rollbackOnly
  3. 如果传递给的闭包withTransaction{..}抛出异常,所有事务将被设置为rollbackOnly。

事务将在事务结束时回滚,该时刻在withTransaction{..} 结束。

所以你需要非常小心你的事务。

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

class YourService {
    @Transactional
    def updateDatabase() {
        //...
    }
    def method() {
        //...
    }
}

将一个方法设置为@Transactional将使该方法成为事务性的,而不是使所有方法都是事务性的。

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

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

希望有帮助!

最新更新