-
[JPA-Error] 빌더 패턴을 사용하여 양방향 연관관계를 맺을때 NullPointerException 예외 발생legacy/JPA 2024. 1. 21. 16:32
문제 발생
@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<>()로 초기화된다.