Please Wake Me From My Hibernation
When we started my current project, we had a few different ideas about how persistence would work. I'm not ashamed to admit that I was not in favor of hibernate, and that was mostly because it was big, complicated magic I didn't understand.
I was assured that the persistence layer wasn't going to be my responsibility, and the guy pushing for it would take care of it. That's fine. I won arguments that were more important to me, and I'm certainly not one to push for something just because I don't understand it.
Well, it's been several months now, and it's still big complicated magic we don't understand. Everyone in my group has had some blocking hibernate issue we've tried to work around. It's often simple stuff like you can't save this object because it's new and an object it contains wants to reference its ID and hibernate can't...I don't know, figure that out on its own.
We're now on our second cache system (treecache would just stop for a while occasionally), but we still don't have a good invalidation model. Our objects are all observable with a cocoa-style observation framework that allows you to subscribe to all kinds of wonderful things. Some subscribers will attempt to get a new instance of the changed object upon notification. Note for anyone playing at home, if you send notifications like this from SessionSynchronization.afterCompletion()
, they may arrive before the commit is complete. We're now doing them in SessionSynchronization.beforeCompletion()
and it seems to work most of the time.
The current problem I'm fighting with is a ClassCastException
. It's somewhere deep inside of hibernate and is thrown when I merge a new object in the second time (i.e. merging the result from the first merge after adding a new dependent property in it) and then it goes to load it the third time. That is, it saves it once, loads it once, saves it a second time, fails to load it a second time, commits the transaction, and then I can load it again, but only outside of that session. Awesome.
Enough Anecdotes -- What's really Wrong with Hibernate?
Instead of just telling lots of stories, let me try to describe what I see as the actual problems with this model.
Hibernate-Based Applications are Untestable
The biggest problem is that hibernate is effectively untestable. By untestable, I mean you cannot isolate it to create unit tests that validate a particular interaction. It's just assumed to work. However, it's only works if you use it correctly.
I can assume my JDBC code works a certain way, and do lower-level interactions to build higher level interactions because each layer is relatively simple (yes, I've written JDBC drivers and know they're not that simple, but relative to hibernate, they are). I can thus provide canned ResultSet
objects for particular queries and make sure my code responds appropriately at unit-test time.
You feed mostly strings to hibernate. Property relations, column mappings, fetch types, cache types, etc... are all basically strings. There are now java 5 annotations that replace the bulk of the xml configuration (which, IMO, is the only thing that made it worth trying in the first place), but that just provides syntactic constraints without providing any semantic constraints. That is, you have to have your application running in a hibernate container and exercise integration tests before you have any idea whether it's remotely correct.
It takes me 40 seconds to compile our code if I disable tests (I've got the fastest build in our group last I asked around). It takes me over a minute to deploy the application. Then I can try to exercise the code related to the annotations I've changed to figure out if they make sense.
Note that my unit tests can mock the objects we use to get things into and out of hibernate. We certainly do that. It may be that that testing is effective enough that I'm taking it for granted since almost all of our show-stopper problems are occurring in hibernate.
Hibernate is Not Bug Free
I don't think there are any people out there who honestly believe that hibernate is without bugs, but bugs hit you especially hard in areas that are especially hard to test.
I'm not the only person who's ever got a NullPointerException
from hibernate. I know this because I've seen other people in my group try to deal with them. I've got a few of these from incorrect annotations. You can sort of think of it as syntax error,
but without telling you as much as what file you messed up.
I will not claim responsibility for the ClassCastException
I mentioned above, either. It's not in my code, it doesn't affect the actual writing to the database, nor the reading from the database -- just doing them both at the same time. Come on, hibernate, if you can do them separately, you can do them together.
I may be in the minority here, but I developed a custom query language for my current application. It's a DSL that allows us to do the kinds of things we need done without leaking implementation details (e.g. we use the same query language for looking up objects in hibernate, in memory, via LDAP and even on remote entities without the caller knowing where they're coming from). I have a particular query I'm trying to satisfy right now that I simply cannot get hibernate to deal with. This would be OK if hibernate consistently gave me syntax errors, however, I get one of the two following errors in all my recent attempts:
- Hibernate likes my query, but generates invalid SQL
- Hibernate doesn't like my query because I can't compare an object of type
T
to an object of typeT
.
In the case of #1, it should be noted that the invalid query it's generating is trying to pull something out of a column it's calling null.
You can't possibly think that's right.
J2EE Counter-Modularization Exacerbates Bug Interactions
Some of the bugs we've run into have actually been fixed in newer versions of hibernate. However, that gets complicated fast. jboss ships with hibernate as part of an installer, and it sort of likes you to treat all of its components as a single application. You can swap stuff out, but the ops guys aren't going to like hearing that their RPM recipe is no longer valid and we're going to have to be turning jboss into an even smaller RPM with more RPMs that depend on it.
That solution is at least feasible today, but I don't really know that jboss doesn't actually require all of the components it ships with and that it's safe to upgrade them.
So What, We Should All Be Writing JDBC Queries All Over the Place?
No, but in the projects I've had that have been the most successful, the persistence layer is sufficiently isolated such that I've been able to remove database components completely without telling anyone.
Part of my lack of understanding of hibernate is a lack of understanding of the benefits of hibernate. It looks a lot like persistence layers I've created in the past, but with a bit more magic and a bit less testability.
I know people are using hibernate for big awesome applications and having little trouble in doing so. I also know that it is less efficient and more complicated than my own code in the applications I've worked on. There are places where it clearly provides more power, but it's hard to balance them against the areas where it takes it away.
My overall feeling after a few months with it in my current application is closer to helpless. It is working well in the majority of the code, but those few points where something goes wrong yield huge headaches.