Hibernate Facts: Equals and HashCode

Introduction

Every Java object inherits the equals and hashCode methods, yet they are useful only for Value objects, being of no use for stateless behavior oriented objects.

While comparing references using the “==” operator is straight forward, for object equality things are a little bit more complicated.

Requirements

Since you are responsible for telling what equality means for a particular object type, it’s mandatory that your equals and hashCode implementations follow all the rules specified by the java.lang.Object JavaDoc (equals() and hashCode()).

It’s also important to know how your application (and its employed frameworks) make use of these two methods.

Fortunately Hibernate doesn’t require them for checking if the Entities have changed, having a dedicated dirty checking mechanisms for this purpose.

After browsing Hiberante documentation I stumbled on these two links: Equals and HashCode and Hiberante 4.3 docs pointing out the contexts where two methods are required:

  • when adding entities to Set collections
  • when reattaching entities to a new persistence context

These requirements arise from the Object.equals “consistent” constraint, leading us to the following principle:

An entity must be equal to itself across all JPA object states:

  • transient
  • attached
  • detached
  • removed (as long as the object is marked to be removed and it still living on the Heap)

Therefore we can conclude that:

1. We can’t use an auto-incrementing database id for comparing objects, since the transient and the attached object versions won’t be equal to each other.

2. We can’t rely on the default Object equals/hashCode implementations, since two entities loaded in two different persistence contexts will end up as two different Java objects, therefore breaking the all-states equality rule.

3. So if Hibernate uses the equality to uniquely identify an Object, for its whole lifetime, we need to find the right combination of properties satisfying this requirement.

Business key equality

Those entity fields having the property of being unique in the whole entity object space are generally called a business key.

The business key is also independent of any persistence technology employed in our project architecture, as opposed to a synthetic database auto incremented id.

So, the business key must be set from the very moment we are creating the Entity and then never change it.

Lets take several examples of Entities in relation to their dependencies and choose the appropriate business key.

  • Root Entity use case (an entity without any parent dependency)

This is how the equals/hashCode are implemented:

@Entity
public class Company {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;

	@Column(unique = true, updatable = false)
	private String name;

	@Override
	public int hashCode() {
		HashCodeBuilder hcb = new HashCodeBuilder();
		hcb.append(name);
		return hcb.toHashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (!(obj instanceof Company)) {
			return false;
		}
		Company that = (Company) obj;
		EqualsBuilder eb = new EqualsBuilder();
		eb.append(name, that.name);
		return eb.isEquals();
	}
}

The name field represents the Company business key, and therefore it’s declared unique and non-updatable. So two Company objects are equal if they have the same name, ignoring any other fields it may contain.

  • Children entities with an EAGER fetched parent
@Entity
public class Product {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;

	@Column(updatable = false)
	private String code;

	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "company_id", nullable = false, updatable = false)
	private Company company;

	@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product", orphanRemoval = true)
	@OrderBy("index")
	private Set images = new LinkedHashSet();

	@Override
	public int hashCode() {
		HashCodeBuilder hcb = new HashCodeBuilder();
		hcb.append(code);
		hcb.append(company);
		return hcb.toHashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (!(obj instanceof Product)) {
			return false;
		}
		Product that = (Product) obj;
		EqualsBuilder eb = new EqualsBuilder();
		eb.append(code, that.code);
		eb.append(company, that.company);
		return eb.isEquals();
	}
}

In this example we are always fetching the Company for a Product, and since the Product code is not unique among Companies we can include the parent entity in our business-key. The parent reference is marked as non up-datable, to prevent breaking the equals/hashCode contract (moving a Product from one Company to another won’t make sense anyway). But this model breaks if the Parent has a Set of Children entities, and you call something like:

public void removeChild(Child child) {
	children.remove(child);
	child.setParent(null);
}

This will break the equals/hashCode contract since the parent was set to null, and the child object won’t be found in the children collection, if that were a Set. So be careful when using bidirectional associations having Child entities using this type of equals/hashCode.

  • Children entities with a LAZY fetched parent
@Entity
public class Image {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;

	@Column(updatable = false)
	private String name;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "product_id", nullable = false, updatable = false)
	private Product product;

	@Override
	public int hashCode() {
		HashCodeBuilder hcb = new HashCodeBuilder();
		hcb.append(name);
		hcb.append(product);
		return hcb.toHashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (!(obj instanceof Image)) {
			return false;
		}
		Image that = (Image) obj;
		EqualsBuilder eb = new EqualsBuilder();
		eb.append(name, that.name);
		eb.append(product, that.product);
		return eb.isEquals();
	}
}

If the Images are fetched without the Product and the Persistence Context is closed, and we load the Images in a Set, we will get a LazyInitializationException like in the following code example:

List images = transactionTemplate.execute(new TransactionCallback<List>() {
	@Override
	public List doInTransaction(TransactionStatus transactionStatus) {
		return entityManager.createQuery(
				"select i from Image i ", Image.class)
				.getResultList();
	}
});
try {
	assertTrue(new HashSet(images).contains(frontImage));
	fail("Should have thrown LazyInitializationException!");
} catch (LazyInitializationException expected) {

}

Therefore I wouldn’t recommend this use case since it’s error prone and to properly use the equals and hashCode we always need the LAZY associations to be initialized anyway.

  • Children entities ignoring the parent

In this use case we simply drop the parent reference from our business key. As long as we always use the Child through the Parent children collection we are safe. If we load children from multiple parents and the business key is not unique among those, then we shouldn’t add those to a Set collection, since the Set may drop Child objects having the same business key from different Parents.

Conclusion

Choosing the right business key for an Entity is not a trivial job, since it reflects on your Entity usage inside and outside of Hibernate scope. Using a combination of fields that’s unique among Entities is probably the best choice for implementing equals and hashCode methods.

Using EqualsBuilder and HashCodeBuilder helps us writing concise equals and hashCode implementations, and it seems to work with Hibernate Proxies too.

Code available on GitHub.

If you have enjoyed reading my article and you’re looking forward to getting instant email notifications of my latest posts, you just need to follow my blog.

About these ads

10 thoughts on “Hibernate Facts: Equals and HashCode

  1. Instead of Apache Commons library, one could use Lombok to generate the code for equals() and hashCode() methods. Lombok can be configured to include or exclude specific fields, like the one representing the surrogate key. See http://projectlombok.org/features/EqualsAndHashCode.html for an example.

  2. Pingback: When the Java 8 Streams API is not Enough | Java, SQL and jOOQ.

  3. I answered your SO question. There’s nothing wrong with unique business keys when implementing equals. Both the database constraints and the optimistic locking versioning are ensuring data integrity.

    • Using your implementation of equals, single query can return 2 two entities (or more) that will be ‘equal’ to each other. Is that not wrong?

      • The business key must exhibit two fundamental properties:

        uniqueness
        immutability

        You should never update an entity business key with a different value.

        In your example, a concurrent thread should never change the business key of any entity. If that’s the case, then you should opt for an UUID columns that ensures uniqueness. But then that’s not natural equality either.

        If you create an entity and some other thread has just added one that’s equal to yours (same business key), then at flush time (prior to executing a query) the insert will be triggered and the db constraint violation should kick in.

  4. Pingback: How to: The JPA hashCode() / equals() dilemma | SevenNet

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s