티스토리 뷰
문제 발생
@ManyToOne 그리고 @OneToMany로 양방향 연관관계를 맺으면 NullPointerException이 발생하였다.
Board(게시물)와 View(조회수, 좋아요) Entity 간의 연관관계를 맺었다.
Board Entity
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "board_id")
private Long id;
private String title; // 제목
private String content; // 내용
private long viewCnt; // 조회수
private long likeCnt; // 좋아요
private LocalDateTime createdDate; // 작성 일자
private LocalDateTime editDate; // 수정 일자
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
// 양방향 연관관계
@OneToMany(fetch = FetchType.LAZY, mappedBy = "board")
private List<View> views = new ArrayList<>();
// 연관관계 편의 메소드
public void relationshipToMember(Member member) {
this.member = member;
member.getBoards().add(this);
}
}
View Entity
@Getter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class View {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "view_id")
private Long id;
private long viewCnt;
private long likeCnt;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
Member member;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
Board board;
// 연관관계 편의 메소드
public void relationshipToBoard(Board board) {
this.board = board;
board.getViews().add(this);
}
// 연관관계 편의 메소드
public void relationshipToMember(Member member) {
this.member = member;
member.getViews().add(this);
}
}
위 Entity가 정상적으로 사용되는지 테스트를 해보았다. View와 Board를 연관관계를 맺어주는 편의 메서드를 호출하는 순간 NullPointerException이 발생한다. 그 이유로는 "Board.getViews()" 반환값이 null이라고 한다.
그런데 아래 Board Entity를 보면 Builder Pattern을 사용하고 있고 views 필드를 생설할 때 new ArrayList<>()를 통해 초기화도 해주었다. 정황상 views 필드에 null이 저장될 수 없다.
문제 원인
views 필드가 null로 저장된 이유는 Builder Pattern을 사용했기 때문이다. 클래스가 인스턴스화될 때 기본적으로는 필드에 명시한 값으로 초기화된다. 그런데 위 코드는 Builder Pattern을 사용하여 객체를 생성하기 때문에 명시적으로 지정한 값으로 초기화되지 않는다.
그렇다면 Builder Pattern을 통해 객체를 생성할 때 값을 지정하지 않으면 어떻게 되는지 확인해보자.
BuilderClass
@Builder
@Data
public class BuilderClass {
List<String> list = new ArrayList<>();
Integer num1;
int num2;
}
BuilderClassTest
@SpringBootTest
public class BuilderClassTest {
@Test
void test() {
BuilderClass build = BuilderClass.builder()
.build();
System.out.println("build = " + build);
}
}
지정하지 않은 값들은 다음과 같이 저장됨을 알 수 있다.
문제 해결
@Builder.Default 애너테이션을 사용하면 명시적으로 지정한 값으로 초기화된다. 즉, Builder Pattern으로 객체를 생성할 때 값을 지정하지 않은 필드에는 Entity에 명시한 값으로 초기화된다는 의미이다.
Builder Pattern으로 객체를 생성할 때 필드 값을 지정하지 않으면 views 필드가 new ArrayList<>()로 초기화된다.