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


