Two Spring/Hibernate gotchas that got me

Earlier this week I got caught out by two Spring/Hibernate features. One of my service layer methods threw a checked exception after a bit of relatively complex validation, just like I intended. What happened next, however, was not expected. My test failed. Well, it didn’t fail, it died with an error and a horrible stack trace of doom. Hibernate was kind enough to tell me that a batch update failed. This struck me as slightly odd, considering the fact that an exception was thrown long before any saving was meant to occur.

I had, apparently, forgotten about the rather unintuitive automagic dirty checking and subsequent saving. In my rather humble opinion, it’s a feature that should be disabled by default, but that’s not the point here. I had been made aware of the feature in the past, but had somehow completely forgotten about it. Fine, no worries, I can live with it, I’ll just roll back my transaction.

However! My test still shouldn’t have failed, or so I thought. After all, I had Spring transaction support set up for all my service calls, and surely an exception will cause the transaction to roll back? Wrong. By default, transactions are only rolled back for Unchecked exceptions, not for checked exceptions. I can’t for the life of me fathom what the reasoning behind this decision might be, but there you have it. It’s well document in the Spring transaction documentation, but again I had completely forgotten about it.

A simple bit of config fixed the problem:

<tx:method name="*" rollback-for="java.lang.Throwable"/>

So please, don’t be like me. Don’t get caught out by these features! Unless you’re looking for a couple of hours of entertainment, that is :-).

6 Replies to “Two Spring/Hibernate gotchas that got me”

  1. Also it is the same behavior as with EJBs. RuntimeExceptions are considered system exceptions and cause a rollback while checked exceptions do not.

  2. @Dimitris: I subscribe to a slightly different school of thought. Exceptions, in my book, are always *bad* and should always cause a transaction to be rolled back. The only distinction I make between Checked and Unchecked is how I handle them. Unchecked exceptions, to me, are bugs in my code. There’s no point in trying to sort that out at run time, so an error is logged and that’s it. A Checked exception is usually triggered by some sort of invalid user input. These then have to be caught so the user can be notified of the situation.

    I guess I’m alone in this 😉

  3. “In my rather humble opinion, it’s a feature that should be disabled by default, but that’s not the point here.”
    You are not alone, you are not alone…

  4. @Bram: In many scenarios, a separate application flow is invoked on checked exception. Also in some scenario, you might want to ignore the exception or handle it differently. e.g. in order processing, if the order creation is successful but email dispatch fails due to some SMTP failures, you might still want to commit the transaction for order processing and handle email failure separately. So the decision to not rollback the transaction (as per EJB specs and implemented in Spring Framework) for checked exceptions sounds reasonable to me.

  5. @Aparna: I agree with the theory. But I don’t know about you, but if I for some bizarre reason made SMTP calls from within a Transaction which extends all the way to my database, I’d make damn sure that SMTP failures were handled gracefully without causing the transaction to fail. Or not, depending on the circumstances. SMTP is a bad example because it’s so unreliable. But in the case of some form of Reliable Messaging, maybe I *do* want the transaction to be rolled back if another system can’t be notified.

    Whether or not it’s a reasonably default depends on how you look at checked vs unchecked exceptions.

    The documentation is relatively clear, and it’s pretty easy to spot when the effects are undesired. However, EJB specs be damned, it’s unintuitive.

Leave a Reply

Your email address will not be published. Required fields are marked *