About Vlad Mihalcea

Vlad Mihalcea is a software architect passionate about software integration, high scalability and concurrency challenges.

Hibernate Facts: Equals and HashCode

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.

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.

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(name);
		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(name, that.name);
		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.
 

Reference: Hibernate Facts: Equals and HashCode from our JCG partner Vlad Mihalcea at the Vlad Mihalcea’s Blog blog.
Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

Leave a Reply


4 × = four



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

15,153 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books