Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exception propagates after already caught

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?

like image 695
user973479 Avatar asked Oct 25 '11 13:10

user973479


People also ask

Can checked exception be propagated?

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.

What happens to an exception when it is propagated all the way up the call stack without being handled?

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.

What is an exception How does an exception propagate in the code?

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.


2 Answers

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.

like image 106
Venushka Perera Avatar answered Sep 17 '22 07:09

Venushka Perera


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:

  • Make sure your expected exception does not throw exceptions which extend 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.
  • Run each tx in its own transaction by moving the transactional method to its own class and annotating it using @Transactional(propagation=Propagation.REQUIRES_NEW). Then each call will run in its own tx and will not affect the overall tx.
  • Use the noRollbackForClassName style venushka mentioned. But use with caution, for the reason mentioned.
like image 37
atrain Avatar answered Sep 21 '22 07:09

atrain