views:

404

answers:

4

In the Hibernate reference, it is stated several times that

All exceptions thrown by Hibernate are fatal. This means you have to roll back the database transaction and close the current Session. You aren’t allowed to continue working with a Session that threw an exception.

One of our legacy apps uses a single session to update/insert many records from files into a DB table. Each record update/insert is done in a separate transaction, which is then committed (or rolled back in case an error occurred). Then for the next record a new transaction is opened, and so on. But the same session is used throughout the whole process, even if a HibernateException was caught in the middle of processing. We are using Oracle 9i btw with Hibernate 3.24.sp1 on JBoss 4.2.

Reading the above in the book, I realized that this design may fail. So I refactored the app to use a separate session for each record update. In a unit test with a mock session factory, I can verify that it is now requesting a new session for each record update. So far, so good.

However, we found no way to reproduce the session failure while testing the whole app (would this be a stress test btw, or ...?). We thought of shutting down the listener of the DB but we realized that the app is keeping a bunch of connections open to the DB, and the listener would not affect those connections. (This is a web app, activated once every night by a scheduler, but it can also be activated via the browser.) Then we tried to kill some of those connections in the DB while the app was processing updates - this resulted in some failed updates, but then the app happily continued updating the rest of the records. Apparently Hibernate is clever enough to reopen broken connections under the hood without breaking the whole session.

So this might not be a critical issue after all, as our app seems to be robust enough even in its original form. However, the issue keeps bugging me. I would like to know:

  1. Under what circumstances does the Hibernate session really become unusable after a HibernateException was thrown (Update: and what are the symptoms)?
  2. How to reproduce this in a test (Update: preferably in integration, rather than unit test)?
  3. (What's the proper term for such a test?)
+2  A: 
Pascal Thivent
+1  A: 

My view on this is that

  1. It't next to impossible to know if Hibernate will work, it encountered an unexpected situation and is now running in an undefined state - Basically you will be invoking UB by performing any more operations on the session.

  2. Idea - register an interceptor and throw a HibernateException in it - hope that Hibernate won't catch it :D

  3. Fault injection?

disown
Regarding 1), this is my understanding too - so I am happy I made our app more robust. The direct problem is, how to verify this in an independent test? 2) sounds interesting, as this could be done without modifying the app itself.
Péter Török
+3  A: 

Interesting one. The best way to tell is to go and take a look at Hibernate's sources. As far as I get it, the Session does not carry much state around, except for the following stuff:

  • first-level cache (see StatefulPersistenceContext) -- it might be actually broken after transaction failure;
  • actions queue (see ActionQueue) -- seem to be okay if you do manual flushing;
  • event listeners -- their instances are aware of session (see EventSource) and, as you might have noticed, the JDBC exceptions are always caused by flushing (DefaultFlushEventListener, to be more precise), but a quick glance at AbstractFlushingEventListener shows that they actually work with session's JDBCContext and ActionQueue.

And the last thing, rollback of transaction itself does not interfere with Session state in any way, see JDBCTransaction.

So as far as I've played around, the Session actually survives a faulty transaction, except that first-level cache might be slightly inaccurate; to get around this you may prefer to call session.refresh or session.load to explicitly resynchronize states.

EDIT: the quickest way to test JDBC exceptions is probably session.createSQLQuery("something offensive to SQL parser").executeUpdate() -- you'll get a perfect SQLGrammarException -- and a broken transaction =)

incarnate
A: 

Create a subclass of Session for testing. Implement it to throw an exception in some specific scenario. Use a test specific hibernate config which sets up hibernate to use your testing Session.

james
This throws a HibernateException indeed, but it does not affect the state of the Hibernate session itself. In other words, this is just a hand implemented mock Session.
Péter Török
maybe i don't understand what you are looking for then. you are looking for a reliable way to put hibernate into a broken (e.g. undefined) state? and then what?
james
I am looking for a reliable way to put the Hibernate session into a broken state, so that the older version of our app (which reuses the same session across transactions) fails, and the newer version (which gets a new session for each transaction) passes the test.
Péter Török
k, so, how does my solution not solve that? create your custom test session to have a "broken" flag. once set, the various methods just keep throwing exceptions. the old code will fail. the new code will get a new session and succeed.
james