9

我有以下代码:

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

我不确定从哪里开始寻找如何解决这个问题。

4

2 回答 2

11

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

有意回滚事务的正确方法是调用setRollbackOnly()当前事务,TransactionStatus但这不能在服务方法中直接访问(它在一个withTransaction块中,因为它是闭包的参数)。但是很容易做到: importorg.springframework.transaction.interceptor.TransactionAspectSupport和 call TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。这将要求您重新编写代码,因为不会捕获异常,因此您需要检查它是否已使用TransactionAspectSupport.currentTransactionStatus().isRollbackOnly().

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

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

链接回滚标志。那行得通,我没有得到UnexpectedRollbackException.

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

于 2012-11-20T22:02:08.083 回答
0

似乎服务的默认事务性正在咬你,服务 A 中抛出的未经检查的异常注定了事务只能回滚,即使被捕获也是如此。

上面的文档说 txn 传播级别是PROPAGATION_REQUIRED,这应该意味着相同的事务从您的服务 C 共享到 A,如果我没记错的话。你可以让服务 A 的save方法抛出一个检查异常而不是 RuntimeException,以避免后者的自动回滚吗?或者禁用您的服务上的交易,如果这是您的选择?

于 2012-11-20T22:01:28.783 回答