@Builder 사용시 양방향 연관관계 NPE 문제

알파로그·2023년 4월 4일
0

Spring Boot

목록 보기
37/57
post-custom-banner

✏️ 발단

  • 팀프로젝트 도중 Service 계층을 test 할 때 문제가 발생했다.
  • 내가 맡은 domain 은 게시글 이였는데 게시글을 생성하기 위해서는 회원 객체가 필요했다.
  • test 하기위해 회원 객체 생성후 게시글 생성과 함께 회원쪽 게시글에 add 를 했더니 NEP 가 발생

📍 게시글 entity

//-- create method --//
public static Board create(String title, String post, Member member) {
    Board board = new Board();
    board.title = title;
    board.post = post;
    board.addMember(member);
    return board;
}

//-- 연관관계 편의 method --//
private void addMember(Member member) {
    this.member = member;
    member.getBoards().add(this);
}

📍 게시글 service

//-- create board--//
@Transactional
public Long create(Board board) {
    repository.save(board);
    return board.getId();
}

📍 회원 entity - 연관관계 필드

@OneToMany(mappedBy = "member")
private List<Board> boards;

📍 회원 service

public void create(String name, String password, String token) {
        Member member = Member.builder()
                .name(name)
                .password(password)
                .token(token)
                .build();
        memberRepository.save(member);
    }

📍 test 코드

//-- create object --//
private Member createMember() {
    memberService.create("A", "1234", "A");
    return memberService.getMember("A");
}

private Long createBoard(String title, String post, Member member) {
    Board board = Board.create(title, post, member);
    return boardService.create(board);
}

//-- Service test --//
@Test
void save() {
    // given
    Member member = createMember();
    Long boardId = createBoard("title", "post", member);

    // when
    Board board = boardService.getBoard(boardId);

    // then
    assertThat(board.getTitle()).isEqualTo("title");
}

✏️ 테스트 실패 원인

📍 연관관계 편의 method 에 문제가 있을 경우

  • method 에 문제가 있는줄 알고 편의 mehod 를 계속해서 바꿔가며 문제를 파악했지만 아무 문제도 없었다.

📍 회원 객체 생성방법에 문제가 있을 경우

  • 여러가지 반례를 생각해가며 디버그를 따라 추적해보니 회원 객체를 생성할 때 회원의 게시글 필드에서 null 값이 저장되고있다는걸 확인했다.
@Test
void name() {
    memberService.create("A", "sdf", "A");
    Member member = memberService.getMember("A");

    assertThat(member.getId()).isEqualTo(1);
    assertThat(member.getBoards()).isNull(); // member.getBoards == null
}

✏️ 문제 해결

📍 1차 시도 - 실패

  • 회원 entity 의 게시글 필드를 new 로 초기화 시켜봤다.
    • 아무런 변화도 일어나지 않고,
      디버스로 확인해봐도 객체가 생성될 때 null 로 생성이 되었다.
@OneToMany(mappedBy = "member")
private List<Board> boards = new ArrayList<>();

📍 2차 시도 - 실패

  • 회원 servcie 에 transactional 이 선언되어있지 않은걸 확인했다.
    • 혹시 이것때문인가 싶어서 선언해주었지만 역시 상관이 없었다.

📍 3차 시도 - 실패

  • 사실 build 생성은 개인프로젝트에서 사용해본적이 없어서 이렇게 봐서는 제대로 생성이 된건지 잘 가늠이 안됬다.
    • 그래서 new 로 초기화를 시켜보았다.
    • 아무런 변화도 일어나지 않았다.
  • 조사를 해보니 new 를 선언해주지 않아도 해당 로직 자체가 new 를 선언한 것과 같은 기능을 한다고 한다.
public void create(String name, String password, String token) {
    Member member = new Member.builder()
            .name(name)
            .password(password)
            .token(token)
            .build();
    memberRepository.save(member);
}

📍 4차 시도 - 성공했지만 원인 파악이 안됨

  • @Builder 에 대한 이해도가 낮기 때문에 확실하게 Builder 의 문제인지 확인하기 위해 않아서 생성자를 사용해 코드를 수정해봤다.
public void create(String name, String password, String token) {
    Member member = new Member(name, password, token);
    memberRepository.save(member);
}
  • 이렇게 생성하니 npe 이 발생하지 않는다.
    • 아무래도 builder 의 사용법이 잘못된것이 원인 인것같다.

📍 5차 시도 - 성공

  • @Builder.Default 어노테이션 속성 추가
    • 해당 속성을 추가한 후 객체를 생성할경우 new 로 초기화가 된다.
@OneToMany(mappedBy = "member")
@Builder.Default
private List<Board> boards = new ArrayList()<>;
profile
잘못된 내용 PR 환영
post-custom-banner

0개의 댓글