I'm having the strangest thing happening and I can't figure out why. The best way to describe this is to provide a simplistic example:
@Service
@Transactional
public class Foo{
public ModelAndView delete(@ModelAttribute("abc") Long id) {
ModelAndView mav = new ModelAndView();
try {
getDaoService().delete(id); //Calls Bar.delete()
} catch (final Exception e) {
// Add a custom error message to the mav for the user to see
mav.getModelMap().addAttribute(blah, blah);
}
return mav;
}
}
@Service
@Transactional
public class Bar {
public void delete(final E entity) throws HibernateException {
if (null != entity) {
try {
sessionFactory.getCurrentSession().delete(entity);
} finally {
sessionFactory.getCurrentSession().flush();
}
}
}
}
In this particular case, I am trying to delete an object which has a constraint violation (ORA-02292). I expect the delete to fail because of this. When the delete fails, I wish to show the user an appropriate custom message.
Instead of being able to show the user a custom message, the call fails and displays the following to the screen:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
When I use a debugger, I can see that the error is appropriately caught and that the ModelAndView object has the custom message inside of it. So, I have no clue why an exception is still being thrown after it has been caught and dealt with. Does anyone have insight into why this is happening?
Unlike Unchecked Exceptions, the propagation of exception does not happen in case of Checked Exception and its mandatory to use throw keyword here. Only unchecked exceptions are propagated.
Exception propagation in Java occurs when an exception thrown from the top of the stack. When it is not caught, the exception drops down the call stack of the preceding method. If it is not caught there, it further drops down to the previous method.
An exception is first thrown from the top of the stack and if it is not caught, it drops down the call stack to the previous method. If not caught there, the exception again drops down to the previous method, and so on until they are caught or until they reach the very bottom of the call stack.
On the @Transactional
annotation, you can state whether or not to roll back your transaction due to a given exception using the noRollbackForClassName
attribute. You can do it similar to this.
@Service
@Transactional(noRollbackForClassName = "java.lang.Exception")
public class YourClass {
...
}
However, note that just saying noRollbackForClassName = "java.lang.Exception"
would mean it will not rollback for any Exception (or its subclasses), hence its not a good practice.
What you should do is, figure out what exception is actually thrown first (may be by printing out the e.getClass().getName()
), then set that class name as the noRollbackForClassName value.
Reason wise, this is happening because if some exception is thrown while attempting to delete(), the current transaction is automatically marked as roll back only, and if it is attempted to be committed, the exception you see will be thrown. The way to get passed this is to explicitly state that this certain exception should not cause a roll back.
The issue is because once an exception is thrown, Spring internally marks the tx as rollback-only. This is completely separate from Java exception handling. You have several options:
RuntimeException
; Spring only rolls back tx's when its a type RuntimeException
(see this page, section 10.5.3). HibernateException extends RuntimeException, so that's why you're getting the rollback marker.@Transactional(propagation=Propagation.REQUIRES_NEW)
. Then each call will run in its own tx and will not affect the overall tx.noRollbackForClassName
style venushka mentioned. But use with caution, for the reason mentioned.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With