Showing posts with label hibernate. Show all posts
Showing posts with label hibernate. Show all posts

Wednesday, February 6, 2008

Failing to Fail Early

A coworker and I stumbled across some weird behavior with NHibernate today that totally surprised me. I come from the Hibernate world which tends to be about a version to a version and a half ahead of NHibernate. So I'm used to going to look for excellent support for stored procedures and realizing I'm SOL.

Today though we found an issue that wasn't lack of support, but just a horrible design decision that differs between the two frameworks. In a nutshell, Session.Load was being called with an ID that didn't exist and we weren't receiving an exception. Huh? The difference between Get and Load in my mind was always, same behavior if the PK passed in exists in the database, if it doesn't Get returns null silently, and Load throws ObjectNotFoundException, violently alerting you that your PK is not what you think it is. In this case we were getting back a proxied object that would immediately throw ObjectNotFoundException whenever one of it's getter methods were called. Nice. I assumed that we were misusing the Interceptor functionality or something to cause this but a little googlage revealed this description of the ObjectNotFoundException class in the NHibernate docs:

"Thrown when ISession.Load() fails to select a row with the given primary key (identifier value). This exception might not be thrown when Load() is called, even if there was no row on the database, because Load() returns a proxy if possible..."

Come again? What use do I have for a proxied object that is just going to throw ObjectNotFoundException the moment I start accessing it? Even worse, what if I have a business method that checks a private attribute for null on this object. You shouldn't be able to do anything to this object because according to the database IT DOESN'T EXIST. When all else fails, go to the pragmatic programmers for advice and listen when they tell you to FAIL EARLY. Give me null or give me exceptions, don't fool me into thinking my row is there when it isn't.

Tuesday, November 20, 2007

Hibernate Hijinks

I've been tasked with optimizing some slowly performing transactions in a system that has been built upon Hibernate. Yesterday I ran across an interesting situation where a single transaction was resulting in 6 separate queries to the database. This may not necessarily be bad, but when the transaction was performing as slowly as it was, we needed to take whatever means we could to speed it up. What was unique to me was that we eliminated all but 2 queries, and we eliminated each one in very different ways.
  1. Use the second level cache for largely read only data. There was a table that had slipped through the cracks of our investigation that will rarely be updated. A couple of configuration entries later and it will be stored in a second level cache and refreshed on a weekly basis instead of causing an extra query hundreds or thousands of times per day.
  2. Eagerly fetch associations that are needed every time the transaction is called. The Criteria API in Hibernate allows the user to specify the fetch mode for every association, effectively overriding the default behavior provided by the mapping file. Setting the fetch mode to Eager for this association resulted in an extra left outer join, but it eliminated an extra query per result row.
  3. Don't haphazardly access collections that are not needed by all of your clients. The transaction in it's previous state would call off to a dependent collection and call the size() method on it to determine how many child records were linked to the parent. This caused every child record in that collection to be loaded by Hibernate, even though very few of the clients actually needed to know the size of this collection. Adding a property to the incoming request to determine if the transaction actually needed to fetch that count and then doing it by simply executing a separate projection based query resulted in much speedier performance for all clients.
This scenario illustrates the flexibility of Hibernate and also the ease with which you can misuse this powerful framework. Always output the SQL generated by Hibernate in development, and make it a regular part of your design, develop, test cycle to inspect that SQL to ensure that it is doing what you think it is.