Hibernate, hashCode, equals and Eclipse

Java programmers use often Eclipse to write their code, and it’s common to rely on the Eclipse features to quickly generate the hashCode() and equals(Object obj) methods of the beans.

Working on a Hibernate based application, I often noticed unexpected issues, and after some in depth debugging I’ve found that probably some of these problems were caused by the Eclipse automatic generation of equals method.

In my opinion, this is mostly caused by the fact that Hibernate generates on top of our entity bean classes other proxy classes that overrides the getters, and other methods in order to provide the lazy loading of data.

I.e.: When Hibernate loads from the DB a Movie bean, it will have, for example, an associated list of MovieActor objects. Obviously Hibernate will not fetch all the data related to every single actor, but anyway it will generate the stub for the MovieActor objects.

The programmer would be tempted to use code like this:

Movie aMovie = getEntityManager.find(...);
MovieActor aMovieActor = getEntityManager.find(...);
if (aMovie.getActors().contains(aMovieActor)) {
	System.out.println(aMovieActor.getActor().getName() + " acted in the movie " + aMovie.getTitle());
}

I noticed that contains method often returns false even if that actor really acted in that movie!
After some long debugging sessions, my final idea is that the problem is how Eclipse automatically generated the equals method of MovieActor class:

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

In this code id is an @EmbeddedId composed by the Actor and the Movie associations. What I believe that causes problems is: other.id!

This is because probably the Id of the other object hasn’t been loaded yet, and its fetching will happen on the calling of getId() of the proxy class built on MovieActor.

So, I changed the equals method this way:

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

And after this little change, contains began to work correctly!

Another very important thing is to check use ‘instanceof’ to compare types otherwise some ugly code would be generated.
Some checks like if (getClass() != obj.getClass()) would miserably fail when hibernate proxy classes are used.