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.