Spring Data JPA save() method not following the hashcode/equals contract
According to this answer, https://stackoverflow.com/a/11881628/5695673, you probably have 2 records because your fields employeeId
are differents for your entities, so for spring-data the two entities are differents.
To test if your equals()/hashCode()
works as expected, you can try to put your entities in a collection which check object equality (i.e. a Set
) and try to save all the set in one time.
Example:
Set<Employee> employees = new HashSet<Employee>();
employees.add( new Employee("111"));
employees.add( new Employee("111"));
System.out.println(employees.size()); //1
employeeRepo.save(employees); // 1 record
More information here: https://developer.jboss.org/wiki/EqualsandHashCode?_sscc=t
Should I write equals() and hashCode() methods in JPA entities?
Not necessarily. There are three options:
don't override - thus you will be working with instances. This is fine in cases when you are working with the collections with only entities that are attached to the session (and hence guaranteed to be the same instance). This is (for me) the preferred way in many cases, because it requires less code and less consideration when overriding
override
hashCode()
andequals()
with a business key. That may be a subset of properties that identify the entity. For example, for aUser
a good business key might be theusername
or theemail
. This is considered good practice.override
hashCode()
andequals()
using the ID field only. This is fine in some cases, especially if you have a manually-assigned identifier (like an UUID). It is also fine if your entity will never go into a collection. But for transient entities (with no identifier) that go into collections, it causes problems, so careful with this option. As seanizer noted - you should avoid it. Generally, always, unless you are really aware of what you are doing (and perhaps documenting it)
See this article for more details. Also note that equals()
and hashCode()
are tied and should be implemented both with exactly the same fields.
Using Primiry Key (id) while overriding equals() and hashCode() methods
getClass()
In regard to the usage of getClass()
everything is straightforward.
Method equals()
expects an argument of type Object
.
It's important to ensure that you're dialing with an instance of the same class before performing casting and comparing attributes, otherwise you can end up with a ClassCastException
. And getClass()
can be used for that purpose, if objects do not belong to the same class they are clearly not equal.
Natural Id vs Surrogate Id
When you're talking about "NaturalId" like ISBN-number of a book versus "id", I guess you refer to a natural key of a persistence entity versus surrogate key which is used in a relational database.
There are different opinions on that point, the general recommended approach (see a link to the Hibernate user-guide and other references below) is to use natural id (a set of unique properties, also called business keys) in your application and ID which entity obtains after being persisted only in the database.
You can encounter hashCode()
and equals()
that are implemented based on surrogate id, and making a defensive null-check to guard against the case when an entity is in transient state and its id is null
. According to such implementations, a transient entity would not be equal to the entity in persistent state, having the same properties (apart from non-null id). Personally, I don't think this approach is correct.
The following code-sample has been taken from the most recent official Hibernate 6.1 User-Guide
Example 142. Natural Id
equals/hashCode
@Entity(name = "Book")
public static class Book {
@Id
@GeneratedValue
private Long id;
private String title;
private String author;
@NaturalId
private String isbn;
//Getters and setters are omitted for brevity
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Book book = (Book) o;
return Objects.equals(isbn, book.isbn);
}
@Override
public int hashCode() {
return Objects.hash(isbn);
}
}
The code provided above that makes use of business-keys is denoted in the guide as a final approach in contrast to implementation based on the surrogate keys, which is called a naive implementation (see Example 139
and further).
The same reasoning for the choice ID vs Natural key has been described here:
You have to override the equals() and hashCode() methods if you
intend to put instances of persistent classes in a Set (the recommended way to represent many-valued associations) and
intend to use reattachment of detached instances
Hibernate guarantees equivalence of persistent identity (database row)
and Java identity only inside a particular session scope. So as soon
as we mix instances retrieved in different sessions, we must implement
equals() and hashCode() if we wish to have meaningful semantics for
Sets.The most obvious way is to implement equals()/hashCode() by comparing
the identifier value of both objects. If the value is the same, both
must be the same database row, they are therefore equal (if both are
added to a Set, we will only have one element in the Set).
Unfortunately, we can't use that approach with generated identifiers!
Hibernate will only assign identifier values to objects that are
persistent, a newly created instance will not have any identifier
value! Furthermore, if an instance is unsaved and currently in a Set,
saving it will assign an identifier value to the object. If equals()
and hashCode() are based on the identifier value, the hash code would
change, breaking the contract of the Set. See the Hibernate website
for a full discussion of this problem. Note that this is not a
Hibernate issue, but normal Java semantics of object identity and
equality.We recommend implementing equals() and hashCode() using Business key
equality.
For more information, have a look at this recent (Sep 15, 2021) article by @Vlad Mihalcea on how to improve caching query results with natural keys The best way to map a @NaturalId business key with JPA and Hibernate, and these questions:
The JPA hashCode() / equals() dilemma
Should the id field of a JPA entity be considered in equals and hashCode?
implementation of equals and hashCode on recursive objects (JPA @ManyToMany)
In my opinion, the best practice would be to compare two entities by comparing their unique IDs. A user does not become a different user by entering a new group. much less, by changing one of the groups she belongs to.
domain specific comparisons could be various, and should be implemented in seperate comparators.
Using auto generated id of Hibernate entity object in the equals and hashcode methods
You should read Equals and HashCode on the Hibernate Community Wiki.
The main reason for not using the database identifier in equals
, and by implication, hashCode
is for dealing with stored, but not persisted, entities. Prior to persistence all you instances are going to be equal
, unless you take care to handle that case explicitly.
If you know that you're not going to be in this scenario, and you make sure it's well documented, you may well be OK. You can always change the implementations later.
Different hashCode() in different persistence context?
If we retrieve the same entity in the same session then the
hashCode() is same.
It is expected as the first level cache of Hibernate (the Session
here) keeps the entities loaded inside the transaction in cache for the transaction lifespan.
The entity is not retrieved a second time, it is just retrieved from the cache.
Each session retrieves the same entity from the table. When i print
their hashCode() they are different.
As loaded entities are not shared between sessions, it means that you didn't override hashCode()
for the entity.
So to guarantee the same hashCode()
and also their equality (equals()
), override equals()/hashCode()
if it makes sense.
Related Topics
How Does Autowiring Work in Spring
What Is the Java Equivalent for Linq
How to Catch Multiple Java Exceptions in the Same Catch Clause
How to Determine an Object's Class
Ignoring New Fields on JSON Objects Using Jackson
Why Would You Ever Implement Finalize()
Java Resultset How to Check If There Are Any Results
Preferred Way of Loading Resources in Java
How to Sort an Arraylist in Java
How to Embed a Browser in Java
How to Compile Simple Java 10/Java 11 Project with Maven
How to Test If a Double Is an Integer
How to Implement the Java Comparable Interface
Java: Export to an .Jar File in Eclipse
Multiple Returns: Which One Sets the Final Return Value