필드에 List가 있다면 builder사용시 list 초기화에 유의하세오

쭈·2023년 4월 21일
0

에러

목록 보기
5/9

문제

  1. Challenger 테이블에서 Fk로 Challenge_idUser_id를 가지고있다.

  2. 나는 객체간의 양방향 참조를 위해 Challenge클래스와 User 클래스에서 challenger를 양방향 매핑하고 있는 상태이다.

(1) 서비스계층에서 챌린지를 생성하고

		Challenge newChallenge = Challenge.builder()
			.title(request.getTitle())
			.content(request.getContent())
			.day(Arrays.toString(request.getDay()))
			.off_day(Arrays.toString(request.getOff_day()))
			.start_date(request.getStart_date())
			.end_date(request.getEnd_date())
			.status(true)
			.challengers(new ArrayList<>())
			.build();

(2) 챌린지를 생성한 유저에게 챌린지를 매핑하는 과정에서 계속 Null 오류가 발생했다.

leader.setChallenge(newChallenge);
newChallenge.getChallengers().add(leader); // NPE 발생 

원인

원인은 .. 100 % 나의 무지함에서 오는 문제였고요
너무 당연한 기초에 대한 무지함으로 며칠을 해맸다는게 화가 납니다 ~
앞으로 당연하게 넘기지 않겠습니다 !

우선 챌린지의 필드는 다음과 같이 구성되어있다.

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "challenge")
@Entity
public class Challenge {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "challenge_id")
	private Long id;

	private String title;
    private String content;


	@OneToMany(mappedBy = "challenge", fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
	private List<Challenger> challengers = new ArrayList<>(); // ????

	public void addChallenger(Challenger challenger){
		this.challengers.add(challenger);
		 }
	}
}

나는 지금까지 늘 빌더패턴을 이용해서 객체를 생성해왔다.
challengers 리스트는 객체를 생성할 때 필요한 데이터가 아니였기에
빌더에 포함시키지 않았었고..

그럼에도 불구하고 필드에 new 연산자를 통해 빈 리스트객체가 자동으로 생성될 줄 알았다.

그러나 필드에 지정한 기본값인 new Arraylist가 빌더 클래스가 만들어질 때 해당 빌더 클래스의 기본값으로 할당되지 않고 null로 초기화된다.

	// 챌린지 생성
	Challenge newChallenge = Challenge.builder()
			.title(request.getTitle())
			.content(request.getContent())
			.build();

때문에 리스트 객체가 new를 통해 생성되는 것이 아닌 null값이 들어간다.
list는 null인데 내가 자꾸 add시키려해서 NPE가 발생한 것이였다.

해결

해결은 간단하다.

(1) 빌더에서 arraylist 객체를 할당해주면 된다.

	// 챌린지 생성
	Challenge newChallenge = Challenge.builder()
			.title(request.getTitle())
			.content(request.getContent())
            .challengers(new ArrayList<>())
			.build();

(2) 또는 @Builder.Default 어노테이션을 통해 기본값으로 new ArrayList<>()를 할당해준다.

@Builder.Default
@OneToMany(mappedBy = "challenge")
private List<Challenger> challengers = new ArrayList<>();

문제 2

그런데 또 다른 의문점이 있다.

챌린지 - 챌린져 양방향매핑과 마찬가지로 유저-챌린져 양방향 매핑이 이루어진다.

User 클래스도 챌린지 클래스와 마찬가지로 내부에 챌린져 양방향 매핑 코드가 있다.

@Getter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user")
public class User {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "user_id")
	private Long id;

	((( 생략 )))

	@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
	private List<Challenger> userInChallenge = new ArrayList<>();

	public void add(Challenger challenger){
		this.userInChallenge.add(challenger);
	}
}

컨트롤러 단에서 인증된 사용자 요청에서 사용자 정보를 꺼낸 뒤 서비스 계층으로 넘어간다. 그런데 해당 User객체가 생성될 때 빌더에 리스트를 포함시키지 않았다.

@PostMapping("")
	public void createChallenge(@RequestBody ChallengeDto.Create requestDto) {
		User user = UserThreadLocal.get();
		challengeService.registerChallenge(user, requestDto);
	}
@Slf4j
@RequiredArgsConstructor
@Service
public class ChallengeService {

	@Transactional
	public void registerChallenge(User user, ChallengeDto.Create request) {

		// 챌린지 생성
		Challenge newChallenge = Challenge.builder()
			.title(request.getTitle())
			.content(request.getContent())
			.challengers(new ArrayList<>())
			.build();

		challengeRepository.save(newChallenge);

		// // 챌린지를 생성한 챌린저 DB에 저장
		Challenger leader = Challenger.builder()
			.challenge(newChallenge)
			.user(user)
			.build();

		leader.setUser(user); // 여기서는 NPE가 발생하지 않는다.
		user.getUserInChallenge().add(leader);

		leader.setChallenge(newChallenge);
		newChallenge.getChallengers().add(leader);

		challengerRepository.save(leader);
}

그런데 왜 이때는 NPE가 발생하지 않지?


https://www.baeldung.com/lombok-builder-default-value

profile
🌱

0개의 댓글