捕获grails事务上的RuntimeExceptions

时间:2015-07-24 08:34:36

标签: hibernate grails gorm quartz-scheduler

目前,我们有一个调用事务服务的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
    }
}

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

1 个答案:

答案 0 :(得分:4)

预期的行为是:

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。

@Transactional
class MyTransactionalService {
...
}

默认情况下,每个Transactional方法都设置了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)

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

更深入地说,您有三个部分可以将您的交易设置为rollbackOnly。

  1. 如果 updateDatabase()抛出异常,则所有Transaction都将设置为rollbackOnly
  2. 如果 method()抛出异常,则所有Transaction都将设置为rollbackOnly
  3. 如果传递给 withTransaction {..} 的闭包抛出异常,则所有Transaction都将设置为rollbackOnly。
  4. 当事务结束时,事务将回滚,并且该时刻在 withTransaction {..} 完成之后。

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

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

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

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

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

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

    希望它有所帮助!