Exception propagates after already caught

user973479 picture user973479 · Oct 25, 2011 · Viewed 8.4k times · Source

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?

Answer

Venushka Perera picture Venushka Perera · Oct 25, 2011

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.