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, чтобы избежать автоматического отката от последнего? Или отключить транзакции на ваших услугах, если это вариант для вас?