데이터베이스를 표현하기 위한 객체인 Entity는 동등성을 어떻게 설정해야할까?

ID가 같은데 다른 필드가 같은 객체는 데이터베이스상으로는 정의될 수 없지만 자바에서는 가능하다.

혹시라도 두 객체의 동등성을 활용하는 로직이 있을 때 ID만 같게해서 객체를 생성하여 우회할수도 있어진다.

Reservation r1 = new Reservation(1L, "초코칩", date, time);                                                                                                                 
Reservation r2 = new Reservation(1L, "제이", date, time);                                                                                                                 
                                                                                                                                                                              
r1.equals(r2); // ?

그래서 모든 필드가 같아야 하도록 동등성을 설정하는게 좋다고 생각을 했었다.

‘모든 필드가 같아야 동등성’의 위험성

하지만 이와 관련해서 더 공부해보니 모든 필드가 같아야 동등성이 성립하도록 하면 위험하다는 사실을 알게됐다.

  1. 상태가 변하면 equals 결과가 바뀐다.

    Reservation r1 = new Reservation(1L, "초코칩");
    Reservation r2 = new Reservation(1L, "초코칩");
    
    r1.equals(r2); // true
    
    r2.setName("제이");
    
    r1.equals(r2); // false ❗
    

    같은 DB row인데 equals가 달라지게 된다.

  2. 컬렉션에서도 치명적인 문제가 발생한다.

    Set<Reservation> set = new HashSet<>();
    set.add(r1);
    
    r1.setName("변경"); // 필드 변경
    
    set.contains(r1); // false
    

사실 Setter를 안열어놓고 불변객체로 만들면 그만이라고 생각할수도 있지만, JPA를 사용하면 변경감지를 활용해야하기 때문에 현실적으로 불가능하다.

이와 더불어 JPA를 사용할 때 문제가 되는 경우가 많다.

  1. Lazy Loading 때문에 equals가 “쿼리 트리거”가 됨

    다음과 같이 @ManyToOne이 걸려있는데 equals 메서드가 모든 필드 equals로 설정되어있다면,

    class Reservation {
        @ManyToOne(fetch = FetchType.LAZY)
        private ReservationTime time;
    }
    

    추후 equals()가 호출됐을 때:

    r1.equals(r2);
    

    time 접근 → Lazy 초기화 → DB 쿼리가 실행된다.

  2. Proxy 때문에 비교가 꼬인다.

    JPA에서는 실제 객체를 상속한 프록시 객체를 활용한다.

    이 상태에서 실제 객체와 프록시 객체를 비교하면, false가 나오게 된다.

    member.equals(that.member)
    

id 기준 동등성

따라서 Entity 객체의 equals는 id기준으로 구현하는 것이 좋다.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof ReservationTime that)) return false;
    return id != null && Objects.equals(id, that.id); 
}