torsdag 8 november 2007

Domain objects, equals and hash code

A good place to start a project that is going to use Hibernate is with the domain objects. The domain objects are the objects containing your actual data. So if your are developing software for a library these would be the classes named Book and Author an so on.

These are just Plain Old Java Objects (POJOs) right? Well yes sure but it's a little bit more complicated than what it sounds like. The Hibernate books say that you should have constructors, getters and setters. That's not problematic of course. They also say that I should implement my own equals and hashcode methods. Those are a little bit more trixy but lets take the problems more or less in the order I stumbled on them.

My fist attempt was using Eclipse's generated equals and hashcode methods. Just one little problem. The code Eclipse generates for you will not work with Hibernate. At least not in the way I use Hibernate. There are two main reasons for this: When using lazy loading (which I have a whole lot of other troubles with getting to work) all calls to other.foo need to be changed inte other.getFoo() since foo is loaded lazily on a getFoo call and is null otherwise. But the much bigger problem is that HashSet's won't work in the expected way.

Remember that when an object is put into a HashSet the hashCode method is used in order to figure out which hashbucket to place the object in. The problem is that later on when I change something on my object, of course that also changes the objects hashcode and this results in the contains-method returning false even when given a reference to an object which actually is in the set. It will simply look in the wrong hash bucket.

It took me a very long time to realize this since the problem only turns up if the object has been altered after it has been put in the hashset and I also more or less also needed to run the contains method in order to stumble upon the problem. So when I figured it all out I was already long into the project. I found the explanation on a hibernate page.
I think the page has changed a bit since I stumbled upon it but it is full of wisdom. It is also full of more or less working ideas on how to work around this problem. What I finally did in my project was to make sure the hashcode was always the same by actually persisting the objects original hashcode in the database. Although this isn't a very nice solution it works and the hashcode becomes like a persisted field there only to speed up searches for example when a domain object is in a hashset.

However in a new ORM using project I have started, which is using iBatis instead of Hibernate, I use another approach instead which I will describe here and that is the one I would truly recommend.

What I base this approach on is that there are actually three different sorts of equality when dealing with objects mapped from a database.
  • The trivial object identity that is == meaning that two references are to the actual same object.
  • The equality meaning that two object have all properties equal to each other. This corresponds to the equals method generated by Eclipse.
  • The these-objects-refer-to-the-same database row equality. This is not the same thing as equality since you can have two different objects loaded from the same database row in memory at the same time and you can change one and leave the other.
The third equality type is only a problem in Hibernate if you have objects that is not connected to an active session. (By the way this sessions are near the root of almost all my Hibernate problems but more about that some other time).

So let's make methods for these equality types. Well the first one hardly needs a method but the second one does. I choose to call it hasValuesEqualTo because what it does is not copatible with what we want our equals method to do (which must work well with the hashcode). So, yes the equals method will correspond to the same row in the database equality. That makes sence since you don't want multiple references to the same database row in your hashset. In order to do this I used java's unique id generator and implemented equals and hashcode as final methods in a base class. I save the id as a string and equals simple runs equals on this string and the hashcode method simply returns the hashcode for the id. Then there is the hasValuesEqualTo left which is like a common equals method. Currently the code for my baseobject can be found here. So far this solution works for me.

onsdag 31 oktober 2007

Hibernate

So you have realised that it would be great to store your java objects in a database and you have heard that Hibernate will do exactly that for you? And you won't even need to think about the database, just design a great class model and store it using Hibernate? Easy you think?

Wrong! As you may already have figured out from the title of this Blog in my experience it isn't really that easy at all. But let's start from the beginning.

A little bit more than a year ago now I started a project using Hibernate. Some of my friends warned me and showed me a frightening text about the Vietnam of Computer Science -- that is: the ORM problem.

The problem for me was such a real one and such an important one that I couldn't really find another solution. I had my java classes and I needed to persist them in a database. No matter what other people had to say about it that was what I needed to do. I guess that was my first steps along what would be a long road...

The first and most important thing you need to realise is that Hibernate makes it look easy -- especially the books about Hibernate makes it look very easy. It is not that easy! In fact it is a very complicated problem. Once you have realised that it is much easier to live with all the problems that will turn up.

I intend to write about how I learned to stop worrying about all the strange things I did to get it to work and how I learned to love the Object Relational Mapping business. But bear with me because it was almost a year ago that I did some of the stuff and in the light of time I might not remember the exact order of things and not all of the darkest times.