-
JPA 객체 사용 시 instanceof 는 주의해서 사용하자Computer - Java 2024. 6. 30. 00:40
JPA가 굉장히 유용한 툴이라는 것은 사용해보면 모두 알지만, 그럼에도 불구하고 생각 외로 의도하지 않은 동작을 하는 경우가 많다.
그 중 최근 내가 겪은 문제들 중 프록시 객체를 사용하기 때문에 주의해야 하는 부분들을 몇 가지 소개하고자 한다.
1. Entity 객체 간 getClass 비교는 절대로 금물!
다음 코드를 보고 직관적으로 특정 부분에서 잘못될 수 있다는 부분을 알 수 있겠는가?
@Entity @Getter @AllArgsConstructor public class User { @GeneratedId private Long id; @NotNull private String name; @NotNull private String phoneNumber; @Override public boolean equals(Object other) { if (this.getClass() != other.getClass()) { return false; } return StringUtils.equals(this.getPhoneNumber(), ((MyObject)other).getPhoneNumber()); } }
기본적으로 equals 메서드가 getClass() 비교를 통해 이루어지기 때문에 저 메서드도 문제가 없다고 생각될 수 있으나,
사실 JPA를 조금이나마 공부를 하면 저 equals 가 제대로 동작할 리 만무하다는 것을 잘 알 수 있다!
기본적으로 JPA가 만들어내는 객체는 MyObject를 상속한 객체이기 때문에, getClass() 호출 시 User.class가 아닌 User.class를 상속한 클래스가 나오기 때문에 equals는 설령 같은 객체처럼 보여도 준영속 / 영속 상태의 객체의 equals()가 의도대로 동작하지 않을 수 있음다를 수 있음을 보여줍니다.
(물론 기본적으로 제 실무에서는 Entity 객체 간 equals를 사용하는 경우가 생각보다 없었던 것 같기는 합니다)
예를 들어, 아래와 같은 소스코드는 제대로 작동하지 않습니다.
public class UserService { // ... @Transactional public boolean equals(Object other) { // DB의 전화번호도 "010-1234-5678" 로 동일함 User userFromDB = userRepository.getById(1L); User newUser = new User(null, "larshew", "010-1234-5678"); // 하지만 이 asserttion error가 된다! (getClass가 동일하지 않기 때문) assert newUser.equals(newUser, userFromDB); } // ... }
2. instanceof 도 주의해서 사용할 것! (상속구조를 사용할 때)
@Entity @Getter @AllArgsConstructor @DiscriminatorValue("KAKAO") public class KakaoUser extends User { private String kakaoEmail; }
위 User를 상속한 KakaoUser 이라는 클래스가 있다고 하자.
그러면 아래 코드는 정상적으로 작동할까? 라고 한다면 아닐 수도 있다!
public class UserService { ... @Transactional public boolean isKakaoUser(Long id) { User user = UserRepository.findById(id); // 여기서 user는 KakaoUser를 상속받은 프록시 객체가 아닐 수도 있다! if (user instanceof KakaoUser) { return true; } return false; } }
언뜻 보면 이 코드는 정상적으로 동작할 것 같으나, Spring Boot, Spring JPA 버전에 따라서 위 소스는 정상적으로 동작하지 않을 가능성도 충분하다. 왜냐 하면, user는 실제 kakaoEmail을 뜯어보기 전까지 User를 상속한 Proxy 객체일 가능성도 충분히 존재한다!
따라서 위 부분을 적절히 수행해주기 위해서는 user를 unproxy 해 주거나, discriminator value(type)로 비교를 해 주는 것이 가장 안전한 전략일 것이다!
'Computer - Java' 카테고리의 다른 글
Effective Java - 3장. 모든 객체의 공통 메서드(2/2) (0) 2022.12.24 Effective Java - 3장. 모든 객체의 공통 메서드(1/2) (0) 2022.12.24 Effective Java - 2장. 생성과 파괴(3/3) (0) 2022.12.23 Effective Java - 2장. 생성과 파괴(2/3) (0) 2022.12.23 Effective Java - 2장. 생성과 파괴(1/3) (0) 2022.12.23