使用Spring Framework具有以下场景
@Transactional
processRequest() {
createOrder();
}
@Transactional
createOrder() {
...
try {
saveRow();
} catch (SaveNotAllowedException e) {
// Handle the expected problem
...
log.info("Save was not allowed...");
// We have to do this otherwise we get UnexpectedRollbackException
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
@Transactional(rollbackFor = SaveNotAllowedException.class)
saveRow() throws SaveNotAllowedException {
// Updating row in database is not allowed because of some business condition
throw new SaveNotAllowedException();
}
请注意:方法是公开的,并且在不同的类中。省略了参数和不相关的东西。 SaveNotAllowedException被检查异常。
在方法saveRow上,我声明我期望回滚已检查的异常。在方法createOrder中,我捕获该异常并执行相关工作来处理该情况。但由于这是一个预期的回滚,我希望Spring也将它视为预期的回滚并给我一个机会。
相反,当方法createOrder返回时,Spring会抛出UnexpectedRollbackException。要解决这个问题,我必须将setRollbackOnly()添加到catch块。这告诉Spring我真的期望预期的异常做回滚:-)。从Javadoc我感觉setRollbackOnly应该用于某些内部使用,并不是真正的官方方式。
对于未经检查的异常(扩展RuntimeException)也会出现同样的问题,例如EmptyResultDataAccessException。文档说,对于未经检查的异常,预期和自动回滚。好的,但是为什么在那种情况下仍然会给出UnexpectedRollbackException?
问题是为什么Spring将预期的异常视为意外?处理此案的官方推荐解决方案是什么?
文档不清楚。在此处阅读了一些解释UnexpectedRollbackException - a full scenario analysis和此处Transaction marked as rollback only: How do I find the cause并未对此有所了解。
有些人认为这是一个特例。据我所知,这完全相反,这实际上是通常情况。拥有业务代码,调用一些方法抛出业务异常并检查它以预期的方式改变行为似乎对我来说非常标准。那么为什么我必须做一个特殊的步骤来调用模糊的API setRollbackOnly()来避免崩溃?
答案 0 :(得分:0)
预期的异常被视为意外异常,因为只有在从最后一个创建事务的@Transactional
方法返回时,才会在AOP代理中做出关于它的决定。代理必须看到正在传播的异常并为其声明了回滚。
为了防止UnexpectedRollbackException,必须在代码中捕获已检查的异常,即在代理关闭事务后调用的代码中。
在最初的问题示例中,事务从processRequest()开始,这是从框架调用的,并且显然不会抛出任何已检查的异常。因此,在这种情况下,必须先捕获异常。
除了问题中已经提出的setRollbackOnly()解决方案之外,还有两个解决方案,不需要使用setRollbackOnly()。
请注意:方法是公开的,并且在不同的类中。省略了参数和不相关的东西。例外情况是经过检查的例外情况。
使用@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = SaveNotAllowedException.class)
在createorder()上创建新事务。在processRequest()中捕获异常。
@Transactional
processRequest() {
...
try {
createOrder();
} catch (SaveNotAllowedException e) {
// Handle the expected problem
...
log.info("Save was not allowed...");
}
try {
backupOrder();
} catch (AnotherException e) {
...
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW,
rollbackFor = SaveNotAllowedException.class)
createOrder() throws SaveNotAllowedException {
...
saveRow();
}
@Transactional(propagation = Propagation.REQUIRES_NEW,
rollbackFor = AnotherException.class)
backupOrder() throws AnotherException {
...
}
@Transactional
saveRow() throws SaveNotAllowedException {
// Updating row in database is not allowed because of some business condition
throw new SaveNotAllowedException();
}
从processRequest()中删除@Transactional
。使用@Transactional(rollbackFor = SaveNotAllowedException.class)
在createorder()上声明回滚。在processRequest()中捕获异常。
processRequest() {
...
try {
createOrder();
} catch (SaveNotAllowedException e) {
// Handle the expected problem
...
log.info("Save was not allowed...");
}
try {
backupOrder();
} catch (AnotherException e) {
...
}
}
@Transactional(rollbackFor = SaveNotAllowedException.class)
createOrder() throws SaveNotAllowedException {
...
saveRow();
}
@Transactional(rollbackFor = AnotherException.class)
backupOrder() throws AnotherException {
...
}
@Transactional
saveRow() throws SaveNotAllowedException {
// Updating row in database is not allowed because of some business condition
throw new SaveNotAllowedException();
}
但解决方案1和2可能不是你想要的。你可能希望在一个事务中拥有整个进程(createOrder()和backupOrder())而不是创建一个新进程。另一种方法是创建另一个从processRequest()调用的事务性辅助服务方法,该方法将在一个事务中调用createOrder和backupOrder。我们需要创建尽可能多的帮助方法,就像我们拥有的许多交易组合一样。
令人遗憾的是,Spring文档中没有提到这些复杂的细节。最近的信息是http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/transaction.html#tx-propagation-required,但需要更详细或至少重新措辞。