Thursday, July 3, 2008

Software is Weird

So we recently upgraded from Hibernate 2.1 to Hibernate 3.2. It was a good exercise in understanding how the code in this incredible ORM library really works. In fact, I had to actually make a chance to the Hibernate source code to support an odd case of a left outer join with multiple join criteria, so I had to step through an entire query's lifecycle in Eclipse (what did people do before debuggers?!!), and figure it out. I saw that there is a lot of automagical stuff happening throughout the code. Most of this is okay. We as users of these libraries, open source and proprietary, just take for granted the behavior, because it's right, about 99.5% of the time. Good work Hibernate developers. You are true champions of software. Here's a weird one though. Pretend for a moment that you ran a program like this:

Session s = openSession();

User u = UserManager.loadUser( 1 );

u.setName ( "fakeName" );

s.flush();
s.close();

This is the equivalent of opening a hibernate session, loading an object, updating it but not saving it, and then closing it. I think the intuitive interpretation of this is that when you flush and close, nothing CRUD-related would happen, since I never called save or update on that User object. To the contrary, that object did get updated - flush() actually took that change and automagically formed an update statement like

update user set name = 'fakeName' where id = 1

for us, and executed and committed the query. That's weird to me, but it is what it is.

As some background, we use the "Session in View" pattern, which has been great. Most of the recommended settings on the interwebs recommend FlushMode.AUTO to be used in the ServletFilter that sets up the session. We figured that the wisdom of crowds was good here, so we rolled with that. We deployed to production and started seeing bizarre data corruption, that was traced the problem to an instance like the one above.

So when you want to avoid this sort of unexpected data modification, you have a couple choices:

1) Use FlushMode.AUTO, and

a) Write the app from scratch and make sure you never update a hibernate-managed object with data you don't want to be persisted to the database.

b) Scour the app to make sure you don't do anything silly like change an object and not persist it yourself

2) Use FlushMode.COMMIT, and wrap all your CRUD calls with

Transaction t = session.beginTransaction();

DAO.saveStuff( object );

t.commit();

We went with option two, and since we have a pretty clean DAO layer, there was minimal work involved, and so far it has worked like a charm. This may be pretty self-evident to many of you, but it was weird to me, so I thought I'd write about it.

1 comments:

Brendan said...

I think of save as meaning attach an entity to a session.

I think of the difference between save and update as communication to the ORM that you know whether the entity is already in the underlying persistence store. But they both attach an entity to a session.

And part of the job of a session is to reflect changes to the entities it is associated with in the database when it is flushes.

You can detach an entity from a session with evict, or detach all entities with clear if you don't want that.