Follow @vlad_mihalcea Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

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 straightforward, 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 mechanism for this purpose.

The Hiberante documentation lists the situations when these 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:

We can’t use an auto-incrementing database id in the hashCode method since the transient and the attached object versions will no longer be located in the same hashed bucket.

method since the transient and the attached object versions will no longer be located in the same hashed bucket. We can’t rely on the default Object equals and 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.

and 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. 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.

Let’s 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-updatable, 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(); } }); //Throws LazyInitializationException

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

If you want to use the entity identifier when implementing equals and hashCode , then check out this post for how to do it properly.

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 are 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.

Insert details about how the information is going to be processed DOWNLOAD NOW