Tuesday, January 24, 2006

Will I Use Hibernate Again?

Short Answer: Probably Not

Over the past 2 years, I've been reading about the wonders of Hibernate and how much simpler it makes CRUD operations. For two recent projects, I decided to take the plunge and use Hibernate. The purpose of this post is to explain some of the reasons I am not likely to consider Hibernate in the future.

Now, to be fair, I did not use raw Hibernate. I used Hibernate wrapped up by Spring, but if you're familiar with both, you probably know how much simpler and prettier Hibernate is if wrapped by Spring. So, I consider my experience with Hibernate more positive than it would have been without Spring. Feel free to tell me why I'm wrong.

Anyway, as you get into developing an application with Hibernate after having spent years on JDBC, ODBC, and the like, you achieve an initial euphoria over how simple it is to perform CRUD operations on the database. In my case, I wrote POJOs, mapping files, and SQL scripts and quickly had a simple schema up and running with full insert, update, delete, and select functionality.

Of course, Hibernate can make life even simpler by letting you create POJO's and SQL scripts from the mapping files. You can also create mapping files and SQL scripts from POJOs using annotations. Needless to say, there are a lot of tools that can make Hibernate even nicer than what I used.

So, you're probably wondering what is my problem. Good question...

First, let's talk about Lazy Loading. Over the years, I've written who knows how many lazy loading methods within a class to improve performance in situations where a given set of data might not be needed. It is a sound principle of database application development as long as it is fully documented. One aspect central to all lazy loading systems I've ever written, though, was the ability to connect to the database and get data regardless of the context.

What does Hibernate do? It throws your friendly neighborhood LazyLoadException anytime its session disappears. Due to the architecture of my system, it ended up being easier to turn off lazy loading everywhere than to have to hold Hibernate's hand everytime it needed a session.

Of course, turning off lazy loading introduced Hibernate's second major weakness...performance. If you have a database schema of any size/complexity, loading data can become quite cumbersome. Of course, Hibernate offers some ways through custom SQL to improve performance, but at the end of the day, does that buy you anything over iBatis?

Next, let's talk about error messages and exceptions. If I attempt to perform an operation on the database where I forget to provide a value for a not null column, I expect to get an error that says something about that column. What does Hibernate do? If you're only dealing with one table, it probably reports the right error. However, if you're dealing with a group of related tables, take your guess.

In one notable instance where I had a table with a one-to-one relationship to another table setup with full cascading, an error in the mapping of one table during an insert resulted in an error saying that the foreign key column couldn't be null. The real problem, however, was that the insert of the row in the related table failed due to a null column with a not null constraint. Until we learned that Hibernate exceptions were useless, we wasted a lot of time tracking down the wrong problem. The best debugging option is to look at the generated SQL.

Now, you may say, "...but what about your test cases for the related table?". Sorry, but I'm never given enough time to write test cases for every little thing in an application. You can wax poetic all you want about extreme programming concepts or whatever flavor of the month development method gets your goat, but I've programmed both ways, and writing test code takes longer unless your developers are incompetent.

Ooh...hope I didn't step on any toes there...moving on...

So let's talk about the biggest insanity in Hibernate...attached and detached objects. While some of these issues tie into lazy loading, there are still problems even if lazy loading is turned completely off.

Hibernate likes to promote itself as being easy to program because you use simple POJOs instead of complex EJB objects to move data in and out of the database. Of course, that's only a half truth. The real truth is that Hibernate introduces proxies throughout your code to create their imaginary universe where these are "just POJOs". Once the proxies get their hooks into your object, you can kiss simple POJO functionality like sending the object over an XML RPC protocol goodbye. Chances are that it will fail to initialize properly when it gets to the other side.

The only way I could overcome this little bit of fun was to completely reload any mapped collections included in an object that needed to go over the wire. Of course, turning on the delete orphan functionality of cascading relationships added some new fun to the mix. Hibernate detects the fact that you have disconnected the parent from the child and gripes about it with an exception. So, you really don't have a simple POJO that retains all of the fine qualities of a POJO after you let Hibernate dig in.

Now that delete orphan has been mentioned, it's a nice time to segway into a rant about its implementation. It appears as though delete orphan was designed to work exclusively with Hibernate's own collection implementations. When you perform an operation on the collection, the collection does whatever merriment it wants in the background to track the operation and reports back. Hibernate then takes the information stored in the collection and makes the same changes to the database.

Side Note: Going back to the issue above, why if delete orphan only takes action in the database upon a save/merge of the parent object does it need to track the fact that I'm tossing the Hibernate persistent collection in favor of a simple, java.util collection?

When I think of delete orphan, I think of the database loading the list of rows from the database, comparing it to the list of rows in memory, and executing the appropriate insert, update, and delete statements to bring the database in sync with the in-memory representation. Hibernate's idea of delete orphan seems like someone took the easy way out.

Don't get me wrong. Hibernate's method could be useful in some circumstances, but I want to be able to take a POJO, without any proxies or special collection classes, and have it persisted to the database just the way it is in memory with all of the dependent collections persisted correctly as well.

All of these issues caused me to spend considerable time digging through the Hibernate forums and Google looking for ways to circumvent these problems. In all of my searching, one thing always rang true. Hibernate developers act like a bunch of arrogant, holier-than-thou programmers labeling anyone and everyone incompetent while lauding their creation as perfect. Nice OS community building there guys. You definitely kept me from ever posting on the forums or, even worse, contributing to your project.

Of course, not all of my experiences with Hibernate were negative. Some of the good things:


  • Database Independence: One application we wrote runs Apache Derby for the Swing GUI and PostgreSQL at the web server.


  • SQL Queries: Taking advantage of object mapping by writing raw SQL queries was extremely simple.


  • Scalar Queries: Hibernate turns scalar queries (thow queries that do not return a mapped object) quickly and easily into an Object[][].



Unfortunately, the benefits do not outweigh the costs. Hibernate feels to me much like Ruby on Rails must feel to many Java developers. It's great for quick and dirty jobs, terrible for the big stuff.

Let the flaming begin...assuming anyone reads this stupid blog.

Labels:

7 Comments:

At 9:09 PM, Anonymous Anonymous said...

Please add a framework called Cocoon to your list. I am working on an app that is being built using Hibernate & Cocoon. I have not gone home since yesterday morning.

 
At 10:07 AM, Blogger Hap Moorii said...

Sorry to hear you haven't been able to get any sleep. I've only briefly looked at Cocoon long ago and never jumped into it so I'll take your word for it. As far as Hibernate is concerned, we've actually standardized on Hibernate w/Annotations. While most of the issues in this article still exist, we've found that Annotated Hibernate efficiency trumps all of them. And, unfortunately, webapp development for us is turning more into a timed churning out of pages as opposed to careful architecting of a custom application.

 
At 1:07 AM, Blogger Jason Sheedy said...

I have to agree. Hibernate looks very nice in the brochure, but in practice it's a time sink. Great for simple stuff, but as soon as it get's a bit complicated, the gain you get with hibernate soon go out the window.

I just found your blog post while looking for a solution to cascading delete-orphan on detached objects. I mean seriously, how often are you going to keep a hibernate session active while you wait for a user to edit an object?

Here are a few links to other posts i've found by people with the same issue.

http://opensource.atlassian.com/projects/hibernate/browse/ANN-264
http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=78&t=000702
http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=78&t=001697

 
At 10:40 AM, Blogger Hap Moorii said...

Thanks for the links. It's good to see I'm not alone in some of my frustrations. It amuses me that this post brings in the majority of my blog's traffic from Google searches. Lots of frustrated people out there...

We're still pleased with Hibernate w/Annotations, but we do a lot to side-step any complicated relationships or operations. I also found a Hibernate guy through oDesk whom I use when my frustration level gets too high. He's made things work that I had decided weren't even possible, but some of that means keeping up with the latest releases which isn't always possible.

 
At 2:49 PM, Blogger Jason Sheedy said...

I did finally get cascading deletes to work on detached objects, but the solution is less than ideal. You need to use session.merge rather than session.update which means hibernate will read the object and all it's related objects before doing the update. I think we all need a hibernate guru to show us these little tricks. ;)

 
At 4:21 PM, Blogger Hap Moorii said...

Yeah, that was the note about keeping up with releases. The software that I developed that prompted me to write this post used an older Hibernate version and was only recently upgraded. We've been using merge instead of saveOrUpdate in cases where cascading comes into play, but so far, we don't have a use-case that runs into the same problem so I couldn't have told you for sure that it worked.

Thanks for posting how you solved it.

 
At 10:07 AM, Blogger rajendar medishetty said...

Hi,

I'm also facing lot of problems with hibernate. I use session.merge(object), which returns the newly created object. when I try to access newly created object child's I get LazyInitilizationException. Every time I get the exception at a different child.

This is really scary..

 

Post a Comment

<< Home