Challenger
테이블에서 Fk로 Challenge_id
와 User_id
를 가지고있다.
나는 객체간의 양방향 참조를 위해 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<>();
그런데 또 다른 의문점이 있다.
챌린지 - 챌린져 양방향매핑과 마찬가지로 유저-챌린져 양방향 매핑이 이루어진다.
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가 발생하지 않지?