Grails UnexpectedRollbackException произошло: не знаю, почему

у меня есть следующий код:

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 ответов


вы используете исключение времени выполнения для отката транзакции, но это обман - он использует побочный эффект. Исключения времени выполнения автоматически откатывают транзакции, так как вам не нужно их ловить, поэтому предполагается, что если один брошен, он не ожидался, и поведение по умолчанию-откат. Можно настроить методы, чтобы не откат для конкретных ожидаемых исключений среды выполнения, но это несколько редко. Проверенные исключения не откатывают исключения, потому что в Java они должны быть пойманы или объявлены в throws, поэтому вы должны либо явно бросить его, либо уклониться от него; в любом случае у вас был шанс попробовать еще раз.

правильный способ намеренно откатить транзакцию-позвонить setRollbackOnly() в течение TransactionStatus но это не доступно напрямую в методе обслуживания (он находится в withTransaction блок, так как это аргумент к закрытию). Но это легко добраться до: import org.springframework.transaction.interceptor.TransactionAspectSupport и звонок TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(). Это требуется переработать ваш код, так как не будет исключения для catch, поэтому вам нужно будет проверить, что он был откат с TransactionAspectSupport.currentTransactionStatus().isRollbackOnly().

Я не уверен, что это проблема Grails или стандартное поведение, но когда я отлаживал это, было 3 вызова фиксации с 3 разными TransactionStatus экземпляров. Только у первого был установлен флаг отката, но второй знал о первом и был в порядке. Третий считался новой транзакцией и был тем, который вызвал то же исключение, которое вы видели. Поэтому, чтобы обойти это, я добавил Это ко 2-м и 3-м методам обслуживания:

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

цепи флаг отката. Это сработало, и я не получил UnexpectedRollbackException.

может быть проще объединить это с проверенным исключением. Это все еще слишком дорого, так как он будет заполнять stacktrace без необходимости, но если вы позвоните setRollbackOnly() и бросить проверенное исключение, вы сможете использовать тот же общий процесс у вас сейчас.


Кажется транзакционность сервисов по умолчанию кусает вас,и непроверенное исключение, брошенное в Службу A, обрекает транзакцию только на откат, даже если она поймана.

вышеуказанные документы говорят, что уровень распространения txn PROPAGATION_REQUIRED, что должно означать, что одна и та же транзакция совместно используется с вашей службой C до A, если мне не изменяет память. Можете ли вы иметь сервис A save способ бросить проверенное исключение, а не RuntimeException, чтобы избежать автоматического отката от последнего? Или отключить транзакции на ваших услугах, если это вариант для вас?