3주차 미션 - Lazy 로딩과 equals 메서드
우아한 테크 캠프 프로 2기

3주차 미션 - Lazy 로딩과 equals 메서드

모든 소스 코드는 https://github.com/lkimilhol/atdd-subway-admin/에서 확인 가능합니다!

안녕하세요. 이번에는 lazy 로딩과 관련한 문제 하나를 설명해볼까 해요.

 

lazy 로딩은 JPA에서 활용되는 데이터를 불러오는 방식 중 하나로 실제로 데이터를 사용하기 전에는 proxy 객체를 활용해 실제로 select가 이루어지지 않고 실제 데이터를 사용할때 select를 해오는 기술이라고 생각 할 수 있습니다.

 

이렇게 된다면 실제로 사용하지 않는 데이터의 조회를 하지 않기 때문에 성능상의 이점이 있을 수 있다고 생각할 수 있습니다.

 

하지만 lazy 로딩을 사용하다 보면 예기치 못한 일들이 참 많이 발생하곤 하는데요.

저는 그 중 하나인 lazy 로딩과 equals 메서드에 관련하여 얘기를 해볼까 해요.

 

1. 언제 발생 할까?


다음과 같을 때 발생합니다.

 

equals 메서드가 실행 될 때 잡은 디버깅이에요

보시는 바와 같이 Station이 프록시 객체로 되어있습니다.
이럴 경우 왜 문제가 되는 것일까요?

 

이유는 바로 equals 메서드를 보면 알 수 있습니다.

 

Station.java

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Station station = (Station) o;
    return Objects.equals(id, station.id) && Objects.equals(name, station.name);
}

이 메서드에서 어디가 문제가 될까요?

 

바로 if (o == null || getClass() != o.getClass()) return false; 구문입니다.

프록시객체로 생성된 Station은 HibernateProxy로 되어 있습니다. 이렇게 되면 getClass() != o.getClass()가 ture가 되어 false를 반환하겠네요. 즉 무조건 false가 떨어진다는 것입니다.

 

2. 그럼 해결책은?


프록시 객체의 검사를 수정해보면 될거같습니다. 먼저 테스트 케이스가 에러가 남을 확인해보죠.

 

원하는 에러가 나고 있네요

 

그럼 메서드를 어떻게 수정할까요?

바로 instanceOf를 이용하는 것인데요. 아래와 같이 수정을 해보고 테스트를 실행해 보겠습니다.

Station.java

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Station)) {
	    return false;
    }
    Station station = (Station) o;
    return Objects.equals(id, station.getId()) && Objects.equals(name, station.getName());
}

 

여기서 getId() 그리고 getName()은 왜 수정이 되었을까요? 값들이 null 이기 때문입니다.

 

마찬가지로 잘못된 동작을 하게 될거에요. 따라서 수정을 해주었습니다.

테스트케이스를 확인해보도록 하죠. 전체 테스트케이스를 돌려서 다른 부분은 문제가 없을지 확인을 해보도록 하겠습니다.

 

모두 통과했습니다

 

3. 그럼 equals()를 매번 수정해야 할까?


저는 그렇진 않다고 생각합니다.

왜냐하면 위의 예제에서 station이라는 객체를 가져올 때 실제 데이터를 가져올 수도 있거든요.

 

간단한 예제를 봐보도록 할까요?

아래의 예제는  https://github.com/lkimilhol/tistoryblog에서 확인이 가능합니다.

 

UserTest.java

@DisplayName("lazy 로딩으로 인한 equals 에러가 발생할까?")
@Test
void equalsFailed() {
    // given
    // when
    boolean isExists = userService.testComputerName();
    // then
    assertThat(isExists).isTrue();
}

간단한 테스트 케이스입니다. testComputerName이라는 메서드를 보도록 하죠.

 

UserService.java

public boolean testComputerName() {
    List<Computer> all = computerRepository.findAll();
    User user = new User("유저0");
    for (Computer computer : all) {
        if (computer.getUser().equals(user)) {
            return true;
        }
    }
    return false;
}

equals는 따로 수정하지 않았습니다. 이런 경우 테스트는 성공할까요? 즉 equals 메서드의 내용 중 instanceOf를 사용하지 않았을 때 말입니다. 결과는 성공입니다.

 

이 테스트는 왜 성공일까요? 디버깅을 해보도록 합시다.

 

 

위 사진을 보면 마찬가지로 프록시 입니다만 객체에서 오버라이딩한 equals 메서드로 들어가면

 

 

proxy가 아니게 되었습니다. 왤까요? 바로 select를 해왔기 때문입니다.

select가 바로 일어났습니다

이처럼 매번 equals 메서드를 수정 하는것이 아닌 언제 select가 일어날지 그렇지 않을지를 확인해봐야 할거같습니다.

정확히 select가 일어난 경우와 그렇지 않은 경우를 매번 예측하기란 힘들거같은데요. 왜 두가지 조회가 차이가 있는지도 차차 고민해봐야 할거같습니다.

 

4. 마치며


이상으로 lazy로딩과 equals 메서드를 재정의 하는 방식을 좀 알아보았는데요.

여러가지로 복잡한 연관관계에서 만날 수 있는 이슈가 될 수도 있다는 생각으로 포스팅 해보았습니다.

감사합니다.