쌩로그

@Builder는 잘 써야 좋다. 본문

TroubleShooting & 고민/BE

@Builder는 잘 써야 좋다.

.쌩수. 2023. 7. 11. 23:11
반응형

목록

  1. 포스팅 개요
  2. 본론
      2-1. 일단 결론
      2-2. 애플리케이션 로직
      2-3. 문제 발생
      2-4. 문제 해결 방향
      2-5. 문제 해결
  3. 요약

1. 포스팅 개요

Entity 클래스에 @Builder 애너테이션을 클래스 레벨에 두었었는데,
클래스에 new ArrayList<>();를 다음과 같이 선언했음에도 불구하고, NullPointerException(이하 NPE)이 발생했다.
이를 해결한 포스팅이다.

@OneToMany(cascade = CascadeType.ALL, mappedBy = "accompany", orphanRemoval = true)
List<?> 땡땡List = new ArrayList<>();

예전에 봤던 글에서 @Builder를 주의해서 쓰자고 봤는데,,,
흠.. 이번에 마주쳤다.

2. 본론

2-1. 일단 결론

먼저 결론부터 내리자면,
클래스 레벨에 @Builder 애너테이션을 두면,
각 Entity 클래스에서 선언한 변수들은 값을 할당해준다 하더라도,
인스턴스 초기화시 변수들은 해당 변수 타입의 기본값으로만 들어간다.

즉, 기본형 8개(short, char, int, long, float, double, boolean, byte)를 제외한 객체 타입들은 모두 어쩔 수 없이 null값으로 들어간다는 것이다.

list.add(객체);

해당 코드를 사용하면, list가 null이기 때문에, NPE가 발생한다.

2-2. 애플리케이션 로직

애플리케이션 로직을 잠시 설명하자면,

// 한명의 회원이 여러 동행에 참여할 수 있다. => 1:N
// 하나의 동행에 여러 회원이 들어올 수 있다. => 1:N
// 종합적으로 다대다 관계이다. 
// 그래서 일대다 다대일 관계로 풀기위해서 Accompany_Member를 설계했다.

그래서 Accompany 인스턴스를 생성함과 동시에,
AccompanyMember 인스턴스 또한 같이 생성되도록 하고,
생성된 AccompanyMember 인스턴스는 Accompany엔티티의 accompanyMemberList안에 담겨야한다.

그런데 과정에서 List 객체를 불러올 수 없어서 NPE가 발생한 것이다.

2-3. 문제 발생

다음은 Entity를 PostMan을 통해서 생성하는 과정이다.
보시다시피 NPE가 발생했다.

코드를 추적해보자..

NPE가 발생했고, 어디서 발생했는지 보니,
AccompanyMemberService의 43번 라인이라고 알려준다.
딱 보면, List에 add()메서드를 사용하다가 발생했을 것이다.

ㄹㅇ이다.

그리고 내가 log.info()를 발라놨는데,
어떻게 나오는지 보자.

음... List가 null이라고 한다...

잡았다 요놈..

또 발라놓은 로그를 보면, 재미난 것을 발견할 수 있다.
이게 재밌네..ㅎㅎ

위 로그 중에 주요한 로그를 보면, 다음과 같다.

AccompanyMember(accompanyMemberId=3, accompany=Accompany(id=3, accompanyMemberList=null), 

여기에 나와있는 accompanyMemberId, id 값이 3이라고 나온다.
id에 값이 할당되었다는 건 DB에 담기긴 했다는 의미다.
DB에 담기지 않으면, id 또한 null일것이다.
왜냐하면 애초에 생성할 때 id는 할당해주지 않는다.

참고로 왜 id값이 3인가...
그건!! 바로...
내가 PostMan의 send버튼을 광클해서 그렇다...;;
여튼 각설하고,

그런데 정작 list는 null이다....
Accompany의 Entity 클래스는 다음과 같다.

@Getter
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Builder    // 원인은 Enumu Shake it..;;
@ToString   // 디버깅용
public class Accompany extends Auditable {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ACCOMPANY_ID", updatable = false)
    private Long id;

    @Column(name = "NICKNAME",length = 50, updatable = false)
    private String nickname;

    @Setter
    @Enumerated(value = EnumType.STRING)
    @Column(name = "LOCAL", length = 16)    // ERD상 Not Null이지만, 기본 X(선택없음)로 들어가므로 nullable 표시 안함.
    private Local local;


    @Setter
    @Column(name = "MAX_MEMBER_NUM", nullable = false)      // 최대인원
    private Long maxMemberNum;

    @Setter
    @Column(name = "ACCOMPANY_START_DATE")
    private Date accompanyStartDate;        // 동행 시작 날짜

    @Setter
    @Column(name = "ACCOMPANY_END_DATE")
    private Date accompanyEndDate;          // 동행 시작 날짜

    @Setter
    @Column(name = "TITLE", length = 100, nullable = false)
    private String title;

    @Lob
    @Setter
    @Column(name = "CONTENT", nullable = false)
    private String content;

    @Setter
    @ColumnDefault("false")     // 기본값 false로 지정
    @Column(name = "RECRUIT_COMPLETE")    // 기본값 false이므로, Table상 Not Null이지만, nullable 포시 안 함.
    private boolean recruitComplete;   // 모집 완료 여부 // 모집 완료 되면 True // boolean 기본 false.

    @Setter
    @Column(name = "COORDINATE_X")
    private Double coordinateX;

    @Setter
    @Column(name = "COORDINATE_Y")
    private Double coordinateY;

    @Setter
    @Column(name = "PLACE_NAME", length = 50)
    private String placeName;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "accompany", orphanRemoval = true) // orphanRemoval 연관관계가 끊어지면 자동으로 삭제
    private List<AccompanyMember> accompanyMemberList = new ArrayList<>();

    public void setAccompanyInit(String nickname) {
        this.nickname = nickname;
    }

원인은 클래스 레벨에 있는 @Builder 애너테이션이노무쉐이킷이 문제였다.

클래스 레벨에 @Builder를 붙였기 때문에,
인스턴스 초기화시 모든 선언된 변수들은 각 타입의 기본값으로 들어간다.
자바에서는 8개의 기본형 외에는 다 참조형이고,
참조형은 모두 null이 기본값이기 때문에,

List<AccompanyMember> accompanyMember = new ArrayList<>();

이렇게 ArrayList를 할당해주었더라도 결국 null일 수 밖에 없는 것이다.

2-4. 문제 해결 방향

그래서 클래스 레벨에 붙이던 @Builder 애너테이션을 지우고,
새로 생성자를 만들어서 새로만든 생성자에 @Builder를 붙이기로 했다.

다음은 새로 만든 생성자이다.

2-5. 문제 해결

이제 문제 없이 잘 들어간다.

3. 요약

Entity 클래스에 @Builder 애너테이션을 클래스 레벨에 두었었는데,
클래스에 다음과 같이
new ArrayList<>();
이렇게 값을 할당했음에도 NPE가 발생했었다.

그래서
Entity에 생성자를 따로 작성했고,
이 작성한 생성자에 @Builder를 붙여줌으로써,
Entity클래스의 변수에 할당한 값이 정상적으로 초기화되도록 하여 문제를 해결했다.

끝.

참고 글

728x90
Comments