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:
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
I found the cause of this problem. It turns out that methodB was wrapped with a cglib-based proxy (using spring old way, pre 2.0) before being wrapped in transaction. so when I throw a RuntimeException from methodB, cglib ends up throwing an InvocationTargetException, which is actually a checked exception.
Spring's transaction manager ends up catching the checked exception and tries to commit the transaction, unaware of the nested runtime exception that methodB threw. Once I discovered this, I set up the transaction wrapper to rollback for checked exceptions as well, now it works as expected.