I have the following strange scenario with spring's transaction management:
I have method A which calls method B which calls method C, each of them in a different class. Methods B and C are both wrapped with transactions. Both use PROPAGATION_REQUIRED, so while spring creates two logical transactions, there is one physical transaction in the db.
Now, in method C I throw a RuntimeException. This sets the inner logical transaction as rollbackOnly and the physical transaction as well. In method B, I am aware of the possibility of UnexpectedRollbackException, so I don't proceed to commit normally. I catch the exception from C and I throw another RuntimeException.
I expect that the outer RuntimeException will cause a rollback to the outer transaction, However the actual behavior is this:
- The outer transaction appears to try to commit, or at least check its status, and then it throws the UnexpectedRollbackException because the physical transaction was already marked as rollbackOnly.
- Before throwing that exception, it prints to the logs another exception, stating that "Application exception overridden by commit exception". Thus, Caller A receives the UnexpectedRollbackException, not the exception that B throws.
I found a workaround for it, which is to actively set the outer transaction as rollback only before throwing the exception
public ModelAndView methodB(HttpServletRequest req, HttpServletResponse resp) {
try{
other.methodC();
} catch (RuntimeException e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new RuntimeException ("outer exception");
}
return handleGetRequest(req, resp);
}
However, this workaround strongly couples the code with transactions api and I'd like to avoid this. Any suggestions?
p.s. both transactions are meant to rollback on runtime exceptions. I didn't define any rollbackFor exception or anything like that