[SpringBoot] @Builder.Default / 클래스 내에서 필드 초기화 / NullPointException

2023. 3. 21. 15:39spring

[문제발생]

테스트를 위해 @beforeEach를 이용해 DB에 데이터를 세팅해놓고, 양방향 맵핑시 데이터가 올바르게 들어갔는지 확인을 하는 과정이었다.

@Test
	@DisplayName("답변에 연결된 질문 찾기 vs 질문에 달린 답변 찾기")
	@Transactional //테스트 환경에서는 리포지토리를 이용한 통신만 가능하다.
	void t009(){
		QuestionEntity question = this.questionRepository.findById(Long.valueOf(3)).orElse(null);
		assertNotNull(question);

		List<AnswerEntity> answerList = question.getAnswerList();
		assertEquals(1, answerList.size());
	}

 

- QuestionEntity와 AnswerEntity는 1:N 연관관계이다.

- @beforeEach에서 테스트를 위해 QuestionEntity [1] 에 대한 답변으로 AnswerEntity [1]을 등록하였다.

- 그리고 해당 QuestionEntity를 통해 등록되어있는 AnswerEntity를 찾고자 answerList를 반환하였다.

- 하지만 계속 answerEntity에서 NullPointException이 발생했다.

 

[해결 과정]

- 처음엔 맵핑이 잘못되어 있나 싶어서 맵핑을 확인하니 문제가 없었다.

- JPA의 영속성에 관한 문제가 발생하는가 해서 @Transactional을 붙이며 코드를 수정했으나 계속해서 예외 발생한다.

- 어느 시점에서 예외가 발생하나 log를 찍어보니 @beforeEach 데이터 세팅시 예외가 발생하는 것을 확인하였다.

AnswerEntity answer = AnswerEntity
				.builder()
				.question(question3)
				.content("홍길동입니다.")
				.build();
		question3.addAnswer(answer); //바로 여기 !!
		AnswerEntity savedAnswerEntity = answerRepository.save(answer);

 

 

- 그래서 AnswerEntity 코드를 다시 한 번 보았다.

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class QuestionEntity extends BaseEntity {
    @Column(length = 200)
    private String subject;
    @Column(columnDefinition = "TEXT")
    private String content;
    @OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
    private List<AnswerEntity> answerList = new ArrayList<>();

    public void addAnswer(AnswerEntity answer) {
        answerList.add(answer);
    }
}

 - 보다시피, answerList는 @oneToMany로 클래스 내부에서 객체를 생성해 주었는데 @Builder 로 인해 null값으로 덮이게 되어서 NullPointException을 발생시킨 것이었다.

@Builder은 QuestionEntity 생성 직후에 만들어지기 때문에 생성자에서 초기화 시킨 것은 덮이게 된다.

 

[해결 방안]

@Builder.Default
    @OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
    private List<AnswerEntity> answerList = new ArrayList<>();

- 필드에서 의도적으로 초기화 시켜줘야 하며, Builder로 인해 덮이기를 원치 않는 필드는 @Builder.Default로 Builder의 영향에서 벗어날 수 있다.