دوشنبه، خرداد ۱۰، ۱۳۸۹

Equality for entities in a Hibernate application - part II


Isn't the new code template in the previous part lovely? ha? In fact it is pretty easy task to do. Check this wonderful post out to do it yourself: Syntax Higlighting

I was reading Effective Java some time ago. It has a section devoted to implementing equals methods and after reading that, I thought it might be better if I revise my equals method. So I changed it to something like this:

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if(!(obj instanceof BaseEntity)) {
return false;
}
BaseEntity o = (BaseEntity) obj;
return getId() == null ?
o.getId() == null : getId().equals(o.getId());
}

The new implementation is more compact and does not need HibernateProxyHelper. But this method does not behave exactly like before. Since it uses instanceof instead of getClass(), if you have a Car and a Dog entity instances, as long as they have equal ids, they are equal! In other words this method may not behave as expected when dealing with objects of a single hierarchy in a Java collection.

instanceOf operator returns false when the object is null so there is no need for null checking.

There is a huge problem in both these implementations. If you are anything like me and use Hibernate's automatic id generation, then the ids are not set until the object is persisted! Our implementation returns true when both ids are null and you should think twice about how equals should be implemented when two entities are not persisted yet. Maybe reference comparison is sufficient but it is largely application specific.

Even I have a bigger question! Is using id a wise comparison method to implement equals method? Even when ids are database auto-generated values? I doubt it. Again consider cat and dog example. I persist each one in a separate table and MySQL uses 1 as start number for each table's id value. So a dog and a cat with ids equal to 1 are equal! The previous version that used getClass() did not have this problem, because it checked the exact uniqueness across tables guarantees uniqueness across classes. (Ids are not equal across whole database, they are just equal in a single table in MySQL)

Perhpas it is better to implement the equals method in children and include some unique business keys if applicable! Like using national id but not all entities have unique business keys. hmm...

How do you implement your equals method on objects in a database application?

یکشنبه، خرداد ۰۹، ۱۳۸۹

Equality for entities in a Hibernate application - part I

Recently I've been working on a web application, which used Hibernate as its object persistence layer. I had a super Entity class which holds Id and optimistic locking variable (@Version).
I had a hard time writing equals and hashcode methods for entities and I haven't reached a conclusion so far. In this post I wanna share my thoughts with you on this subject.
First I have started writing the class, put some annotation on it and commanded eclipse to generate equals and hashcode for me. You can see the result below:

@MappedSuperclass
public class BaseEntity {
@Id @GeneratedValue
private Long id;

@Version
private int version;

public Long getId() { return id; }

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + version;
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
BaseEntity other = (BaseEntity) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}

As Larry David always says it looked pretty pretty pretty, pretty good. But eeh, you know, it did not work. Equals and hashcode manifest themselves mostly in Java Collections API and my application has dozens of them. No luck in finding the wanted object in the collections. After hours of debugging I found out that the problem is in the equals method and exactly in this line:

if (getClass() != obj.getClass()) return false;

Can you see what is wrong? Hibernate uses lazy initialization and most of the time the getClass() method doesn't return the real type (when is lazily initialized like in collections). Instead it returns a cglib-based proxy class. So this if condition sometimes returns false at this point. The solution I found was to get the real underlying class using the following Hibernate's utility class:

Class clazz = HibernateProxyHelper.getClassWithoutInitializingProxy(obj);

So I could compare the real underlying classes, but this alone did not solve the problem. There was an even harder to spot problem at this line:

if (id == null) {
if (other.id != null)
return false;

The objects were loaded from database so the id element was present. Why the hell it was null? The culprit was Hibernate lazy initialization mechanism again! My collections were lazily initialized and calling bare fields would return null! The solution is to call getters to give the entities a chance to initialize themselves, something like this:

if (getId() == null) {
if (other.getId() != null)
return false;
}
else if (!getId().equals(other.getId()))
return false;

Now it works like a charm! :) But there are even some problems here. Read on the next in part II.

جمعه، خرداد ۰۷، ۱۳۸۹

Clean code with Uncle Bob

For those of you interested in writing good code, I highly recommend this video from QCon 2010: Robert C. Martin: Bad code, Craftsmanship, Engineering and Certification
In an extremely funny and witty way, Uncle Bob explains his core philosophy about writing clean code. Also he has a book named Clean Code which is on my reading list for this summer. Hope you find it interesting either.