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

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.

هیچ نظری موجود نیست:

ارسال یک نظر