문제 상황
어떤 회원이 한 게시물의 조회수를 최초에 한번만 올릴 수 있도록 로직을 짜는 과정에서, NullPointer에러가 발생했습니다.
회원과 게시물 Entity를 다:다 관계로 연결하기위해서 조인 테이블 역할을하는 엔티티를 중간에 껴서,
회원 -다-------1-->조인테이블<--1-----게시물 관계를 형성했습니다.
그 후 게시물을 조회할 때마다, 게시물을 본 회원을 나타내는 조인테이블 엔티티를 조회하여, 그 중 조회하고 있는 회원이 있으면, 카운트를 올린 후 관계를 추가하고, 아닌 경우엔 아무것도 실행하지 않는 로직입니다.
public Question readAndViewCount(long questionId) {
Question question = verifyQuestion(questionId);
//조회하고 있는 회원객체 획득
Optional<User> optionalUser = userInfoUtils.extractOptionalUser();
if(optionalUser.isPresent()) {
QuestionView questionView = new QuestionView();
questionView.setQuestion(question);
questionView.setUser(optionalUser.get());
Set<QuestionView> questionViews = question.getQuestionViews();
//본 적이 없으면, 아래 로직 실행
if(!questionViews.contains(questionView)) {
question.viewUp();
question.addQuestionView(questionView);
return questionRepository.save(question); //NullPointer에러 발생!
}
}
return question;
}
위와 같은 코드에서 이전에 조회한 적이 있는지 빠르게 찾기 위해, HashSet 데이터 타입의 contains() 메서드를 사용하였습니다.
이 메서드는 객체의 hashCode() 값과 equals()값을 이용하기 때문에, 위의 QuestionView 클래스에서 두 메서드를 override했습니다.
@Override
public int hashCode() {
int hashOfUser = question.hashCode();;
int hashOfQuestionb = user.hashCode();
int digit = (int) Math.log(hashOfQuestion) + 1;
int hashCode = hashOfQuestion + hashOfUser * (int) Math.pow(10, digit);
return hashCode;
}
그런데 save()메서드를 동작시키는 과정에서 에러가 발생했습니다.
원인
원인은 hashCode()와 equals()를 오버라이드할 때, nullPointer가 발생할 가능성이 있도록 했기 때문이었습니다.
및의 글에서 hashCode()와 equals()는 절대 NPE를 발생시키지 않도록 해야한다고 합니다. 본격적인 로직을 수행하기 이전에, null인지 검사를 한 후에 작업을 수행해야하며, null값에 대해 미리 정의된 값을 가지고 있어야합니다.
또 한 가지의 문제가 더 있습니다. equals()는 위임하는 방식을 사용하면 안된다는 것입니다.
JPA의 @OneToMany관계일 경우 기본적으로 fetch가 lazy로 설정되어 있기 때문에, proxy객체를 사용하기 직전에 query를 날리는 작업을 하게 됩니다. equals()안에서 다른 객체를 참조하게 된다면, 연쇄적으로 많은 query문을 날리게 되고 많은 시간이 걸릴 수 있습니다.
해결
null 검사를 미리하고, null일 경우에 할당할 값을 미리 정의해두어 NPE를 방지해야합니다.
또한 equals를 override할 때는 연관관계에 있는 다른 객체를 참조하는 것 대신에,
다른 객체를 참조해서 미리 만든 해쉬코드를 사용하는 것입니다. 해쉬코드는 객체 생성시에 미리 저장해두기 때문에, 지연로딩으로 인한 자원의 낭비를 막을 수 있습니다.
참조
https://stackoverflow.com/questions/63205138/hashcode-throws-a-nullpointer-exception
HashCode throws a nullpointer exception
I have a puzzle for you. I am making a herb store web app and this is my database : The store can have many products A product can contain many herbs These are my JPA classes : public class Store...
stackoverflow.com
'Back-end > Spring Boot' 카테고리의 다른 글
| [SpringTest] @MockBean과 @Mock 차이 (1) | 2022.12.01 |
|---|---|
| [WebSocket] 웹소켓이란? (0) | 2022.11.29 |
| [Oauth2] Oauth2로 회원가입 / 로그인 기능 구현하기 (0) | 2022.11.03 |
| [JPA] Infinite Recursion 해결법 (0) | 2022.10.23 |
| [SpringBoot] HttpSecurity 보안 설정 정리 (0) | 2022.10.13 |