发生Grails UnexpectedRollbackException:不确定为什么

Gre*_*egg 9 grails transactions exception

我有以下代码:

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

   }
}
Run Code Online (Sandbox Code Playgroud)

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

我做了以下事情:

try {
   serviceC.process(object)
}catch(UnexpectedRollbackException e) {
   println e.getMostSpecificCause()
}
Run Code Online (Sandbox Code Playgroud)

我得到了:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
Run Code Online (Sandbox Code Playgroud)

我不知道从哪里开始寻找如何解决这个问题.

Bur*_*ith 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()
Run Code Online (Sandbox Code Playgroud)

链接回滚标志.这工作,我没有得到UnexpectedRollbackException.

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

  • Grails 2.3.7将包含默认处理此情况的功能:http://jira.grails.org/browse/GRAILS-11145 (2认同)