我有以下代码:
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
我不知道从哪里开始寻找解决方法。
答案 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,以避免后者自动回滚?或者禁用您服务上的交易,如果这是您的选择?