代码之家  ›  专栏  ›  技术社区  ›  froi

捕获grails事务上的RuntimeExceptions

  •  3
  • froi  · 技术社区  · 10 年前

    目前,我们有一个调用事务服务的grails作业。当从服务中抛出异常时,休眠的行为会变得奇怪。我们使用的是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
        }
    }
    

    这正常吗? 这是一个bug吗?

    1 回复  |  直到 9 年前
        1
  •  4
  •   juandiegoh    10 年前

    预期行为为:

    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)
    

    这意味着当服务方法运行时,它将使用在作业中创建的当前事务。这里有一个棘手的部分,当一个功能具有多个事务性部分时,它会评估每个事务性部分的回滚条件,因此当您 方法() throws RuntimeException它将您的事务设置为仅回滚,但会一直持续到该事务结束(当Model.withTransaction..完成时),并在此时回滚所有事务。

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

    1. 如果 更新数据库() 引发异常,所有事务都将设置为rollbackOnly
    2. 如果 方法() 引发异常,所有事务都将设置为rollbackOnly
    3. 如果您传递给 使用事务{..} 引发异常,则所有事务都将设置为rollbackOnly。

    当事务结束时,事务将回滚,并且该时刻在 使用事务{..} 完成。

    因此,您需要非常小心处理您的交易。

    为了解决你的问题,你可以 方法() 仅通过设置 更新数据库() 作为服务类中的事务性,并从服务名称上方删除@transactional。

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

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

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

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

    希望它有帮助!