Hibernate id equals and hashcode

A mapped entity has a database identity. This is the primary key of the table.
Two entries are the same, if they both have the same Id.

A Java class without implementation of equals/hashCode equals only another class if they both actually point to the same instance.

To avoid to have multiple instance with the same database id in memory, we need to implement equals and hashcode. This actually not requirement when using Hibernate but might prevent problems in some scenarios.

The first idea could be to implement equals/hashcode on the id.

{% highlight java %}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Message message = (Message) o; if (id != null ? !id.equals(message.id) : message.id != null)

return false;

return true; } public int hashCode() { return (id != null ? id.hashCode() : 0); }

{% endhighlight %}

The problem is that this is not working, if you have a collection of instances and want to add them to a java.util.Set:

{% highlight java }
Book book = new Book();
Set authors = book.getAuthors();
authors.add(new Author(“Sebastian”));
authors.add(new Author(“Holger”));
endhighlight %}

In this case only one author would be added because both have the same hashcode and equals each other.

The solution is to problem is a business key. This is a unique combination of various properties of the class. The problem with this approach is, that quite often there is no business key.

In our Author class of the example, we could try to use first and surname as business key but there might be problems with the same names.

This is a dilemma but thanks to Marius – the participant in the training – there is another approach to solve this. His proposal was to use the id but in case it is null to check for Java object equality.

The equals may look like:

{% highlight java %}
public boolean equals(Object o) {
if (this == o) return true;
Message that = null;
if (o instanceof Message)
that = (Message) o;
return false;
if (id == null && that.id == null)
return super
// call the equals of the parent class, which will will sooner or
// later let to object comparision

return (id == null ? that.id == null : id.equals(that.id)); }

{% endhighlight %}

There is a downside of this approach as well. A changing hash code violates the contract for hashcode. As a consequence, if you deal with Maps and Sets, you should deal only with objects in state persistent or detached.

Comment from Anonymous

In apps based on JPA where hibernate is used as persistence provider I had a lot of troubles with equals. From my expirience I would change that.id to that.getId()

When referenced entities are lazy initialized it is common case to have "proxy" object whose attributes are initialized on first getter call. In my case when "that" is proxy that.id expressions always returns 0 but that.getId() returns correct id value.