데이터베이스를 표현하기 위한 객체인 Entity는 동등성을 어떻게 설정해야할까?
ID가 같은데 다른 필드가 같은 객체는 데이터베이스상으로는 정의될 수 없지만 자바에서는 가능하다.
혹시라도 두 객체의 동등성을 활용하는 로직이 있을 때 ID만 같게해서 객체를 생성하여 우회할수도 있어진다.
Reservation r1 = new Reservation(1L, "초코칩", date, time);
Reservation r2 = new Reservation(1L, "제이", date, time);
r1.equals(r2); // ?
그래서 모든 필드가 같아야 하도록 동등성을 설정하는게 좋다고 생각을 했었다.
하지만 이와 관련해서 더 공부해보니 모든 필드가 같아야 동등성이 성립하도록 하면 위험하다는 사실을 알게됐다.
상태가 변하면 equals 결과가 바뀐다.
Reservation r1 = new Reservation(1L, "초코칩");
Reservation r2 = new Reservation(1L, "초코칩");
r1.equals(r2); // true
r2.setName("제이");
r1.equals(r2); // false ❗
같은 DB row인데 equals가 달라지게 된다.
컬렉션에서도 치명적인 문제가 발생한다.
Set<Reservation> set = new HashSet<>();
set.add(r1);
r1.setName("변경"); // 필드 변경
set.contains(r1); // false
사실 Setter를 안열어놓고 불변객체로 만들면 그만이라고 생각할수도 있지만, JPA를 사용하면 변경감지를 활용해야하기 때문에 현실적으로 불가능하다.
이와 더불어 JPA를 사용할 때 문제가 되는 경우가 많다.
Lazy Loading 때문에 equals가 “쿼리 트리거”가 됨
다음과 같이 @ManyToOne이 걸려있는데 equals 메서드가 모든 필드 equals로 설정되어있다면,
class Reservation {
@ManyToOne(fetch = FetchType.LAZY)
private ReservationTime time;
}
추후 equals()가 호출됐을 때:
r1.equals(r2);
time 접근 → Lazy 초기화 → DB 쿼리가 실행된다.
Proxy 때문에 비교가 꼬인다.
JPA에서는 실제 객체를 상속한 프록시 객체를 활용한다.
ReservationReservation$HibernateProxy이 상태에서 실제 객체와 프록시 객체를 비교하면, false가 나오게 된다.
member.equals(that.member)
따라서 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);
}