Having the following scenario using 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();
}
Please note: Methods are public and in different classes. Parameters and irrelevant stuff omitted. SaveNotAllowedException is checked exception.
On the method saveRow I declare that I expect rollback on the checked exception. In the method createOrder I catch that exception and do a relevant work to process that case. But since this is an expected rollback, I would expect that Spring also treats it as expected rollback and gives me a go.
Instead what happens is that Spring throws UnexpectedRollbackException when method createOrder returns. To solve the problem I have to add setRollbackOnly() to the catch block. This tells Spring that I really expect the expected exception to do the rollback :-). From the Javadoc I get the feeling that the setRollbackOnly is supposed to be used for some internal use and is not really the official way to do it.
The same problem happens for unchecked exceptions (extending RuntimeException) e.g. EmptyResultDataAccessException. Documentation says that the rollback is expected and automatic for unchecked exceptions. OK, but then why does it still give UnexpectedRollbackException in that case?
The question is why does Spring treat the expected exception as unexpected? What is the official recommended solution to treat this case?
The documentation is not clear on this. Having read some explanation here UnexpectedRollbackException - a full scenario analysis and here Transaction marked as rollback only: How do I find the cause does not shed any more light on it.
Some people suggest this is a special case. To my understanding this is complete opposite, this is actually the usual scenario. Having business code, calling some method throwing business exception and checking it to alter the behavior in expected way seems pretty standard to me. So why I have to do a special step calling obscure API setRollbackOnly() to save me from crashing?