Tuesday, November 6, 2012

Do we need equals(...) and hashcode(...) overrides in JPA domain objects ?

The idea of overriding equals(...) and hashcode(...) is such that an object can be uniquely identified through some more meaningful semantics instead of the default semantics which basically says that "object of the same class are equals if they are in the same memory location".

With JPA implementations, the concerns we have when leaving equals(...) and hashcode(...) as defaults are :-

  1. Composite primary Key will not work
  2. Issues with detaching and merging of domain objects
  3. Multiple copies of objects that are semantically similar can exists in our collection object
  4. entityManager.persist(...)

#1 Composite Key will not work

If we are not using composite key at all, then we should be fine. Is composite key a good thing to used compared to just say running number generated off sequence by the database itself is another topic of itself. I suppose if we are doing it for an existing database with tons of data already populated with some natural composite key being already in place, we'll just have to live with it. If it's a green field project, do we really want to use natural keys as our composite key?
  • Natural composite primary key takes up more indexing space compared to just incremental number as primary key
  • Database might take up more time when storing natural composite key assuming the index is a BTREE, where it needs to find the slot to stick in the composite key. Incremental number is just more predictable to the db in this case and I guess most db will be coded to take advantage of this.

#2 Issues with detaching and merging

This is a major concern. I guess the question is do me really need to use the merging feature of JPA? Following are some bits we want to take into account when doing a merge.
  • lazy-loaded relationships aren't going to be merge even if CascadeType is MERGE unless they are triggered before detach
  • merging across a relationship that is being removed will caused an exception
  • merging across a relationship that doesn't exists in persistence context will have undefined consequences except if the CascadeType across that relationship is MERGE
  • accidently 'null'ing out a detached non-lazily loaded or a triggered lazily loaded relationship will null out its counterpart in the persistence context when merging

It's much more convenient, in my humble opinion, to just do an DIY merge rather then relying on JPA's merging mechanisms (this comes from a web developer perspective). Say, we have forms on web pages that upon submit gets to a Spring controller where the values from the form are being populated in to a command object. We could just do a DIY merge into the domain object we do an 'entityManager.find(...)'.

 @PersistenceContext
 private EntityManager em;

 public String submission(Command command, BindingResult bindingResult, Model model){
    ...
      MyDomainObject domainObject = em.find(...);
      domainObject.setSomeProperty(command.getSomeProperty());
    ...
 }
But doesn't that means I'll lost my Optimistic Locking check if i have a '@Version' property in my entity?

Unfortunately I guess to a certain extend yes. So we'd probably wanna do that bit of logic ourselves.

#3 Multiple copies of objects that are semantically similar can exists in our collection object

If we do
   Set set = ... ;
   set.add(new MyDomainObject("jack"));
   set.add(new MyDomainObject("jack"));
both will be treated as different entity and will result in 2 records in the set itself. However this is arguably controllable in our application, surely we'll have some sort of validation be it in the controller or service that restrict addition that do not make any business sense.

#4 entityManager.persist(...)

So if we have an auto-generated primary key eg.
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private long id;
the id will only be valid after entityManager.persist(...) is invoked, else it'd be zero (the default value for any long primitive type). Since we did not overrides equals(...) and hashcode(...) to based on 'id' for equality, we are safe to use our domain object after entityManager.persist(...) is called.

Just me 2 cents. If you have a natural business key for your domain objects that uniquely identify itself, feel free to override equals(...) and hashcode(...). If you decide not to and if you are ok with working around some restrictions, that you should be fine as well.

Cheers

1 comment: