Wednesday, December 5, 2012

Hibernate 4

As part of our new "flight plan" for the end of the year, we decided to take this opportunity to upgrade Hibernate, the object/relational mapping tool responsible for most of the database interaction in Flex.

Upgrading our technology stack is fairly common at Flex.  We frequently upgrade Spring, tons of smaller libraries like apache-commons and we recently upgraded Jasper Reports.  Upgrading Hibernate, however, is not something to be undertaken lightly.  Is very rare that you can upgrade Hibernate without doing some kind of refactoring or finding strange and serious regressions.

We Fear Change

Part of this is cultural.  The folks at Hibernate and JBoss have a possibly undeserved reputation for not respecting backwards compatibility.  Gavin King, the original developer, had a paternalistic attitude about how Hibernate should be used and expressed this attitude through a notoriously abrasive personality.  Steve Ebersole, the current lead for Hibernate, is a bit more diplomatic and it seems that Hibernate now has a kinder and gentler culture.  Another reason Hibernate may have struggled with reverse compatibility issues is that the problem they're solving - mapping complex object graphs to two dimensional database tables - is incredibly difficult.  With so much abstraction and propeller-head algorithms required to build something like Hibernate, I'm not surprised in the least that abstractions leak and have to be revised as things move forward.

I would never even attempt to develop something like Hibernate and I have a tremendous amount of respect for what the Hibernate people have accomplished.  Not only did they build a great and useful ORM tool that's become the defacto standard - they forced the entire Java establishment including the folks at Sun and now Oracle to redefine Java persistence.  JPA is Hibernate and wouldn't exist without it - make no mistake.  Were it not for Gavin (love him or hate him) and Hibernate, Sun would no doubt still be trying to cram Entity Beans down our throats.

I mention this "upgrade friction" not to bag on Hibernate, but to highlight the fact that you don't upgrade Hibernate when you have a project that's up and running unless you expect to realize some kind of tangible benefit from it.  For us, we liked the new service based approach in Hibernate because it seemed more Spring friendly (but not quite, as we shall see) and we liked the new caching architecture that got introduced over several iterations of Spring 3.  But the big thing for us was multi-tenancy support.  The big project on the horizon for us at Flex is converting the architecture to a one-instance/many-customers approach.  I didn't even know the lingo for we were about to do was "multi-tenancy" until I started reading release notes for Hibernate 4.  That sealed the deal.  However painful it might be to upgrade Hibernate, it had to be less pain than developing our own JDBC layer multi-tenancy system (which we may still have to do, but the likelihood is far less now.)

Can't We All Just Get Along

We, like many people who use Hibernate, also use Spring.  Hibernate folks, in their talks and blogs, tend to downplay a Spring/Hibernate stack, even suggesting in an oblique way that Spring/Hibernate is an uncommon architecture that kind of annoys them.  Of course, anybody with their eyes open in the world of Java Architecture knows that Spring/Hibernate architectures are incredibly common, perhaps the most common Java technology stack these days.  It wouldn't surprise me if the people that attend JBoss conferences don't reinforce this reality, but it's a reality nonetheless.

The Spring/Hibernate feud, to whatever extent it really exists, reminds me of an interview I once saw with Stephen Morrissey, known to his fans as just "Morrissey" from the Smiths.  In this interview Morrissey talks about how he saw a fan of his in the airport wearing a Cure T-Shirt.  Morrissey gave the fan a talking to and goes on to talk about how much he hates The Cure, which was a shock to me.  It's very rare to find a CD collection with Morrissey that doesn't also include The Cure.  Another analogy might be the South Park / Family Guy feud.  It's disappointing because fans of one are usually fans of the other and it also reflects a certain cluelessness on the part of the belligerents.  If you think you can convince a typical Morrissey fan to pick a side and abandon The Cure, you don't have a clue, you don't understand your fans.  Likewise, every time Hibernate takes a dig at Spring, they're taking a dig at their own users.  We're going to use both together as long as there is a Spring and a Hibernate - and it's time they embraced the idea.  I'd like to see the Venn diagram of Spring and Hibernate contributors.  If they don't touch, that's a problem.  (And maybe they do.  I haven't checked.)

Square Pegs and Round Holes

With all this intrigue and background well established, let's talk the actual upgrade process.  A key issue for us was the ability to define a RegionFactory (Hibernate's cache abstraction) in Spring and inject it into the Hibernate Session Factory.  We had no problem doing this is in Hibernate 3 using one of Spring's factory beans.

There was no facility for doing this in Spring's SessionFactoryBean for Hibernate 4.  I read that Hibernate 4 permits services to be swapped out using the ServiceRegistry, so I assumed that Spring's Hibernate 4 factory bean was just a little stale.  So, I subclassed it to support service injection and used the ServiceRegistry as documented to inject our RegionFactory.

Problem is it didn't work.  We'd get an error during startup warning us to use Hibernate's preferred method of defining a region factory - which is by classname as a configuration property.  You can't inject dependencies in things you define by class name, which is why we don't want to do it that way.

I poked around in the Hibernate source for several hours and found this little gem in a class called CacheImpl, which is the class that Hibernate uses to implement their cache integration.

public CacheImpl(SessionFactoryImplementor sessionFactory) {
    this.sessionFactory = sessionFactory;
    this.settings = sessionFactory.getSettings();
    //todo should get this from service registry
    this.regionFactory = settings.getRegionFactory();
    regionFactory.start( settings, sessionFactory.getProperties() );
    ....
}
So, there it is, proof positive that you can't inject a RegionFactory in Hibernate no matter how hard you try.  Although it does suggest that the folks at Hibernate are aware of the issue and will fix it relatively soon.

But we needed a fix now so I spent several more hours trying to find something I could subclass or swap out that would allow us to sneak in our RegionFactory, but through a combination of default scoped interfaces and final classes, it was all for naught. In the end, we had to fork Hibernate and change that one line of code ourselves. As it turned out, it wasn't just one line of code - several different places where RegionFactory is looked up had to be changed. I thought about submitting a patch to Hibernate, but my fix might be simplistic given other ways Hibernate is used, so I figured it was better to wait.

So, for now we have a fork of Hibernate.  We'll switch back to a standard version as soon as they fix the issue.

Sessions

If other major issue we've seen with Hibernate 4 - and in this case I think the issue concerns both Hibernate and some of the code Spring provides to work with Hibernate - is that Sessions aren't always readily available.  We no longer have a "get a session and create one if one doesn't exist" method of getting sessions.  We use the HibernateTransactionManager provided by Spring and the workaround so far has been to make facade or service methods that don't need transactions (things that Spring generates transactional proxies for in our architecture) transactional.  We also use the Spring OpenSessionInView filter which handles this for all browser initiated requests, leaving this session issue relegated to scheduled tasks, JMS queue consumers and startup stuff.  Personally, I think the Spring Hibernate Transaction Manager needs to be tweaked to handle this issue, but that's just my take on it.  I don't know the inner workings of Hibernate or Spring well enough to know if that's really the solution.  It's something we've been able to easily work around, so no biggie.

Our Region Factory

Since we went through so much trouble to inject our own Region Factory, it's reasonable to ask why we'd go through all that trouble.  The answer is that we're moving toward a custom distributed cache that's a hybrid of EhCache and Memecached.  Some caches are small and static enough that simply having a local in memory cache (provided via EhCache) makes perfect sense.  In other cases, we need to move the memory footprint of the cache to another server.  We need a lot of flexibility and most of the current cache implementations impose a kind of all or nothing proposition.  We're not even 100% sure how this will work yet.  More to come on that front....
 

No comments:

Post a Comment