Java/Spring

Entity 의 Equals 와 HashCode 를 오버라이드 해도 될까?

jwKim96 2022. 4. 27. 04:15

엔티티 검증

테스트 코드를 작성하다가, 두 객체가 같은지 비교하는 로직의 작성이 필요했습니다.

@Test
void TestSomething() {

    // ... 생략

    assertThat(resultEntity.getId()).isEqualTo(expectedEntity.getId());
    assertThat(resultEntity.getName()).isEqualTo(expectedEntity.getName());
    assertThat(resultEntity.getPrice()).isEqualTo(expectedEntity.getPrice());
    assertThat(resultEntity.getStockQuantity()).isEqualTo(expectedEntity.getStockQuantity());
}

이렇게 모든 속성을 비교하는 로직을 작성하는게 번거로웠습니다.
그래서 EqualsHashCode 를 오버라이드 했는데요.
여기서 문득 EntityEqualsHashCode 하면 문제가 없을까? 에 대해 생각해보게 되었습니다.

궁금했던 내용들

  • JPA영속성 컨텍스트 의 동작에 영향을 주지 않을까?
    • 만약 영향을 준다면, 영속된 객체와 그렇지 않은 객체의 구분이 안될까?
  • Instance 가 같은 Entity 인지를 판단하는 기준은 무엇일까?

일단 이 고민을 시작하게된 프로젝트는 Spring Data JDBC(Data JDBC) 를 사용중이었습니다.
Data JDBC 는 어차피 Entity 를 즉시 쿼리로 바꿔서 날리기 때문에 EqualsHashCode 는 걱정하지 않아도 되겠다고 생각했습니다.
하지만 영속성 컨텍스트 를 가지는 JPA 에서는 어떻게 될지 궁금했습니다.

그리고 더 근본적인 궁금증으로, Entity 자체의 의미와 같은 Entity를 판단하는 기준이 궁금했습니다.

1. JPA영속성 컨텍스트의 동작에 영향을 주지 않을까?

백문이 불여일견, 바로 코드로 확인해봤습니다.

@Entity  
@Getter  
@NoArgsConstructor
@AllArgsConstructor  
public class Users {  

  @Id  
  private Long id;  
  private String name;  
}

public class UsersJpaTest {  

    private EntityManagerFactory emf = Persistence.createEntityManagerFactory("hashcode-jpa-test");  
    private EntityManager em = emf.createEntityManager();  
    private EntityTransaction tx = em.getTransaction();  

    @Test  
    void test() {  
        Users users = new Users(1L, "jwkim");  

        tx.begin();  
        em.persist(users);  
        Users findUser = em.find(Users.class, users.getId());  
        tx.commit();

        assertThat(em.contains(users)).isTrue();  
        assertThat(em.contains(findUser)).isTrue();  
        assertThat(em.contains(new Users(1L, "jwkim"))).isFalse();
    }  
}

먼저, EqualsHashCode 를 오버라이드 하지 않은 Entity 로 테스트를 해봤습니다.

image

이 테스트는 예상했던 대로, 성공했습니다.

그러면 이제 UsersEqualsHashCode 를 오버라이드 해보고 다시 테스트 해보겠습니다.

@Entity  
@Getter  
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode // `Equals` 와 `HashCode` 를 오버라이드하는 Lombock 어노테이션 추가
public class Users {  

  @Id  
  private Long id;  
  private String name;  
}

image

오버라이드 한 다음의 테스트도 성공했습니다.

이번 테스트로 영속성 컨텍스트EqualsHashCode 에 무관하게 동작하니 기술적으로는 안심하고 사용할 수 있을 것 같습니다.

2. 두 Instance 가 같은 Entity 인지를 판단하는 기준은 무엇일까?

Eric Evans 의 Domain-Driven Design 에서는 Entity 에 대해 다음과 같이 정의합니다.

Many objects are not fundamentally defined by their attributes, but rather by a thread of continuity and identity.

여기서 핵심적인 부분만 해석해보면 객체들은 연속성과 정체성에 의해 정의된다 라고 생각할 수 있었습니다.

다음으로 Martin Fowler 언급한 Entity 에 대해 살펴보았습니다.(EvansClassification)

Entity: Objects that have a distinct identity that runs through time and different representations. You also hear these called "reference objects".

Martin FowlerDomain-Driven Design 을 언급하며 위와 같은 정의에 동의한다고 하는데요.
이렇게 Entity 의 정의에 대해 찾아보며 들었던 생각은, 같은 Entity 인지 확인하는데는 속성은 중요하지 않다 입니다.
오히려 identity 가 중요하다고 하니 Entity 를 나타내는 고유 식별자만 같다면 같은 Entity라고 정리할 수 있습니다.

JPA Buddy

JPA 개발을 편하게 할 수 있도록 도와주는 JPA Buddy 라는 IntelliJ 플러그인이 있습니다.
이 플러그인에서 자동으로 생성한 EqualsHashCode 는 아래와 같은 모습이라고 합니다.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o))
        return false;
    Item item = (Item) o;
    return Objects.equals(id, item.id);
}

@Override
public int hashCode() {
    return id.intValue();
}

여기서 hashCode() 메소드를 보면, id 를 이 객체의 해시값으로 사용하겠다고 되어있습니다.
즉, 속성 이 달라도 id 만 같으면 같은 Entity 라는 개념이 적용된 것 입니다.

결론

  • JPA영속성 컨텍스트 의 동작에 영향을 주지 않을까?
    • 결론 : 영향을 주지 않는다
      - 만약 영향을 준다면, 영속된 객체와 그렇지 않은 객체의 구분이 안될까?
  • Instance 가 같은 Entity 인지를 판단하는 기준은 무엇일까?
    • Entity식별자 가 같으면, 같은 Entity 로 판단한다.

결론적으로 EntityEqualsHashCode 를 오버라이드 해도 상관이 없다.
다만 id 가 같으면 같은 Entity 로 취급하는게 정론인 것 같아서, 만약 오버라이드 할때는 이 개념을 유의하자.