发生Grails UnexpectedRollbackException:不确定为什么

时间:2012-11-20 21:04:22

标签: exception grails transactions

我有以下代码:

class ServiceA {

   def save(Object object) {
      if (somethingBadComesBack) {
         throw new CustomRuntimeException(data)
      }
   }
}

class ServiceB {

   def serviceA

   def save(Object object) {
      try {
         serviceA.save(object)
         // do more stuff if good to go
      } catch(CustomRuntimeException e) {
        // populate some objects with errors based on exception
      }
   }
}

class ServiceC {

    def serviceB

    def process(Object object) {
       serviceB.save(object)
       if (object.hasErrors() {
          // do some stuff
       }else{
         // do some stuff
       }

       def info = someMethod(object)
       return info
    }
}

class SomeController {

   def serviceC

   def process() {

     def object = .....
     serviceC.save(object) // UnexpectedRollbackException is thrown here

   }
}

当调用ServiceA.save()并发生异常时,ServiceC.save()在尝试返回时会抛出UnexpectedRollbackException

我做了以下事情:

try {
   serviceC.process(object)
}catch(UnexpectedRollbackException e) {
   println e.getMostSpecificCause()
}

我得到了:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

我不知道从哪里开始寻找解决方法。

2 个答案:

答案 0 :(得分:11)

您正在使用运行时异常来回滚事务,但这是作弊 - 它正在利用副作用。运行时异常会自动回滚事务,因为您不需要捕获它们,所以假设如果抛出一个,则不会预期,并且默认行为是回滚。您可以将方法配置为不针对特定的预期运行时异常进行回滚,但这种情况有点罕见。已检查的异常不会回滚异常,因为在Java中它们必须在throws中被捕获或声明,因此您必须明确抛出它或将其删除;无论哪种方式,你都有机会再试一次。

故意回滚事务的正确方法是在当前setRollbackOnly()上调用TransactionStatus,但这不能在服务方法中直接访问(它位于withTransaction块中因为这是关闭的论据)。但这很容易:导入org.springframework.transaction.interceptor.TransactionAspectSupport并致电TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。这将要求您重新编写代码,因为不会有异常捕获,因此您需要检查它是否已使用TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()回滚。

我不确定它是Grails问题还是标准行为,但是当我调试它时,有3个提交调用有3个不同的TransactionStatus个实例。只有第一个设置了回滚标志,但第二个意识到第一个并且没问题。第三个被认为是一个新的交易,并且触发了您所看到的同一个例外。因此,为了解决这个问题,我将其添加到第二和第三种服务方法中:

def status = TransactionAspectSupport.currentTransactionStatus()
if (!status.isRollbackOnly()) status.setRollbackOnly()

链接回滚标志。这很有效,我没有得到UnexpectedRollbackException

将此与已检查的异常相结合可能更容易。它仍然过于昂贵,因为它会不必要地填充堆栈跟踪,但是如果你调用setRollbackOnly()并抛出一个已检查的异常,你将能够使用你现在拥有的相同的通用工作流程。

答案 1 :(得分:0)

似乎default transactionality of services正在咬你,而服务A中抛出的未经检查的异常只会导致事务回滚,甚至一旦被捕获。

上述文档称txn传播级别为PROPAGATION_REQUIRED,这意味着如果我的内存为我服务,则从服务C到A的共享相同的事务。您是否可以使用Service A的save方法抛出已检查的异常而不是RuntimeException,以避免后者自动回滚?或者禁用您服务上的交易,如果这是您的选择?