liveBook · Manning

Equality by value

As explained in Java Persistence with Spring Data and Hibernate epub

The equals method is the Java object method that determines whether one object should be considered equal to another.

Java has two notions of sameness: `a == b` checks whether references point to the same instance, while `equals()` determines whether instances should be considered equal by value. Neither test necessarily matches database row identity, which is expressed by primary-key values. Two objects loaded in different persistence contexts can represent the same row without being the same Java instance. section 1.2.3 section 10.3.1

The inherited `Object.equals()` uses instance identity. A `HashSet` also uses `hashCode()` when storing and comparing elements, so overriding `equals()` requires a consistent `hashCode()`: equal instances must produce the same hash value. section 8.2.1 section 10.3.2

Choose equality fields according to the kind of persistent object:

  • For an `@Embeddable` value type, compare its values. This matters when components are placed in a `Set` or used as hash-based `Map` keys: two distinct `Image` instances with the same values should count as one set element. section 8.2.1
  • For an entity compared after detachment, use a business key when equality must hold across persistence contexts. If entities are never compared while detached, the default `equals()` can suffice. section 10.3.1 section 10.3.2
  • Avoid hashing an entity solely by its generated database identifier. Saving a transient entity can assign that identifier and change its hash while it is already in a `Set`, breaking collection behavior. section 10.3.2
Listing 8.13 Implementing custom equality with equals() and hashCode()
Path: Ch08/mapping-collections/src/main/java/com/manning/javapersistence
 /ch08/setofembeddables/Image.java
 
@Embeddable
public class Image {
     // . . .
@Override
public boolean equals(Object o) {                                #1
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Image image = (Image) o;
    return width == image.width &&
           height == image.height &&
           filename.equals(image.filename) &&
           item.equals(image.item);
}
 
@Override
public int hashCode() {                                         #2
    return Objects.hash(filename, width, height, item);
}
    // . . .
}
#1 This custom equality check in <kbd>equals()</kbd> compares all values of one <kbd>Image</kbd> to the values of another <kbd>Image</kbd>. If all values are the same, the images must be the same.
#2 The <kbd>hashCode()</kbd> method has to fulfill the contract requiring that if two instances are equal, they must have the same hash code.

The `Image` implementation compares component values in `equals()` and hashes the same values in `hashCode()`. section 8.2.1

Listing 10.3 Custom implementation of User equality
@Entity
@Table(name = "USERS",
       uniqueConstraints =
        @UniqueConstraint(columnNames = "USERNAME"))
public class User {
    @Override
    public boolean equals(Object other) {
        if (this == other) return true;
        if (other == null) return false;
        if (!(other instanceof User)) return false;
        User that = (User) other;
        return this.getUsername().equals(that.getUsername());
    }
    @Override
    public int hashCode() {
        return getUsername().hashCode();
    }
    //  . . .
}

The `User` implementation uses its unique username as a business key for both methods, rather than its database identifier. section 10.3.2

em = emf.createEntityManager();
em.getTransaction().begin();
User c = em.find(User.class, USER_ID);
assertFalse(a == c);                       #1
assertTrue(a.equals(c));                   #2
assertEquals(a.getId(), c.getId());
em.getTransaction().commit();
em.close();
Set<User> allUsers = new HashSet();
allUsers.add(a);
allUsers.add(b);
allUsers.add(c);
assertEquals(1, allUsers.size());          #3

A separately loaded `User` is a different instance but compares equal; adding all three references leaves one element in the `HashSet`. section 10.3.2

How Java Persistence with Spring Data and Hibernate epub covers it

How deep it goes
Works through equality overrides, detached-instance behavior, and generated-identifier hash instability; limits the need for entity overrides to detached comparisons.
Examples
Tests duplicate images in a `Set` and users loaded through separate persistence contexts.
What it assumes
Assumes basic Java objects and collections while explaining persistence-context identity and why generated identifiers can disrupt a `Set`.
What you can do after
Build it from scratch by implementing consistent `equals()` and `hashCode()` methods for value components and entities requiring detached equality.
How it connects
Links equality by value to `hashCode()`, `HashSet`, embeddable components, business keys, and persistence contexts.

Also explained in

Compare how 3 books explain equality by value →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Java Persistence with Spring Data and Hibernate epub. The section links point to the parts of the book it draws on.