简体   繁体   中英

How to correctly override equals for Hibernate entity with @NaturalId

Many times already discussed how to redefine equals/hashCode for Entity.

My question is about the need to use all the fields in equals. Consider two cases.

When we use all fields for equals:

@Entity
public class Book {

    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NaturalId
    @Column(name = "isbn", nullable = false, unique = true)
    private String isbn;

    @Column
    private String name;

    private Book() {
    }

    public Book(String isbn) {
        this.isbn = isbn;
    }

    @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(id, book.id) &&
                Objects.equals(isbn, book.isbn) &&
                Objects.equals(name, book.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(isbn);
    }
}

And test:

public class BookTest1 {

    @PersistenceContext
    protected EntityManager em;

    @Test
    public void fromTransientToManageSameEntity() {
        Book book1 = new Book("4567-5445-5434-3212");
        Book book2 = new Book("4567-5445-5434-3212");

        em.persist(book2);
        flushAndClean();

        assertThat(book1, is(not((equalTo(book2))))); // not equals
    }
}

As we see, when translating entities from a transient into a manage state - the same entities will not be equal.

Another case is when we use in equals only @NaturalId:

@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);
}

And test:

public class BookTest2 {

    @PersistenceContext
    protected EntityManager em;

    @Test
    public void fromTransientToManageSameEntity() {
        Book book1 = new Book("4567-5445-5434-3212");
        Book book2 = new Book("4567-5445-5434-3212");

        em.persist(book2);
        flushAndClean();

        assertThat(book1, equalTo(book2)); // equals
    }
}

As we see, now both entities will be equal.

My question is whether the same entity should be equal in the transition to manage state or not. And accordingly how to correctly redefine the equals in this situation.

According to this article equals and hashCode should be state-agnostic. If you have overriden only the first one, it's bad and can cause strange bugs. They need to have a contract .

The simplest way would be to use lombok - annotate your class with @EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) and the fields used in comparison with @EqualsAndHashCode.Include .

When I looked into this a while ago, I concluded that there's no single right answer.

I ended up checking only the @Id property in equals() and hashCode() , as that seemed to behave best. (We don't use any @NaturalId s; it could work with that instead, but it may be safer to stick with the @Id .)

I think the only potential issue I found with that was if a new instance gets added to a collection before being persisted. In practice, that never happens in our project, and so it works well. (If it does in your project, you may still find this the best trade-off, to avoid problems when persisted objects appear in collections, which is much more common.)

As other answers have pointed out, if you override equals() you must also override hashCode() , to ensure that equal objects always have the same hashcode. (The question's first example does comply with this, though it's perhaps a little confusing for the two methods not to check all the same fields.)

In Kotlin, by the way, those two methods become manageably small:

override fun equals(other: Any?) = other === this
                                || (other is MyEntity && entityId == other.entityId)

override fun hashCode() = entityId

(Yet another example of why I love Kotlin!)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM