Thursday, June 5, 2008

Equality and Object-Relational Mapping

Hibernate (and apparently JPA) uses the same object instance to represent the same record within the context of a transaction/session. This ensures that the default Object implementation of equality (Object.equals(object) compares instance identity) is sufficient for comparisons of persistent classes within a given transaction.

Outside of the transaction, this is no longer true -- one record in the database might have multiple instances, and if the values are changing, the values might not be the same. This leads one to all sorts of questions about how best to implement equality for persistent objects. There are discussion threads and wiki pages on the Hibernate site that go on about this in length, and I've read them a number of times over the years.

This morning, I think I've finally decided that all the mental effort I've put into that over the years is mostly wasted time. I'm coming around to the believe that instance-comparison is, in fact, a good behavior for the equals() method of persistent (or persistable) object instances.

There are basically a limited number areas where the equals() method of a persistent object comes into play for me in rough order of frequency of use:

  1. Regular database-interacting code within the application. In this case, I'm typically working within the context of a particular transaction, and instance comparison is sufficient.
  2. Test code, often persistence tests or integration tests where I'm expecting an object's fields to be the same before and after a particular operation (e.g. create object, save to database, load fresh instance, verify same fields). In these cases, I can use a Commons ReflectionEqualsBuilder without altering the implementation of equals().
  3. Long-running database-interacting code. In this case, I might have detached instances between transactions, but mostly comparisons will occur within the context of a transaction, and I don't mind reattaching / refreshing to make this work.
  4. Database-interacting application code that needs to compare objects from one transaction with objects from another. This does come up, but it's infrequent. In these cases, I think it's probably reasonable that the default comparison (equals()) fail fast, encouraging me to think carefully about why I'm doing this kind of comparison, what I'd like to compare, and how I'd like to go about it. This is rare enough that putting a little extra effort into these cases and having to put thought into why I'm doing it is probably a good thing.
So, I'm thinking for my next project, I'm going to stick with instance equality for my persistent objects and see how it goes.
Most of the time, I'm working with objects in the context of a transaction. When I want to compare objects across multiple transactions, I'm typically doing this in a test (e.g. a test of the persistent object mapping), and I can easily handle this with a reflection equals builder without actually altering the implementation of equals().

No comments: