들어가며
오늘부터는 달성 목표에 우선순위를 정해두고 하나씩 해결하는 방식으로 공부를 진행하고자한다. 확실히 하나씩 항목을 지워가면서 하니까 성취감도 들면서 내 페이스를 조절하기가 좋았다.
오늘의 TIL도 지난번과 마찬가지로 항해99 과제인 [나만의 블로그 만들기]를 개발하면서 겪은 트러블 슈팅을 하고자 한다.
트러블 슈팅
문제 - @ManyToOne으로 만들어진 FK 컬럼이 null로 세팅되는 현상
Comment entity에서 Post entity와 연결해주기 위한 N:1 관계를 만들어주는 @ManyToOne
어노테이션을 붙여주면 테이블이 생성될 때 자동으로 Comment 테이블에 FK 컬럼을 생성해준다. 그런데, 이 FK가 nullable하게 세팅되는 현상이 나타나고 있었다.
create_at과 modified_at이 null 허용인 부분은 아직 처리가 안되었으니 넘어가고, FK인 post_id를 보자.
당당하게 Null 허용이 되어있는 모습을 볼 수 있다.
하지만 기획단계에서 보면 각 게시글에 댓글이 달리는 형태이기 때문에 게시글 없이는 댓글이 달릴 수 없고, 그렇기 때문에 FK인 post_id는 not null이 되어야한다.
일단 null 처리를 한 코드이다.
// 나머지 코드 생략
@ManyToOne
@JoinColumn(name = "POST_ID", nullable = false)
private Post post;
// 나머지 코드 생략
문제는 이게 null 처리만을 위한 코드였다면 머리가 아플 일이 없는데 단지 그 역할만을 하는게 아니었다.
이 부분은 길어질 것 같으니 새로운 포스팅을 파서 정리해보기로 하고, 지금 당장 내가 원하던 문제는 해결했으니 다음으로 넘어가자.
문제 - stackoverflow 에러
Comment를 조회할 때, 왜 Post 객체를 함께 들고 다니지?
라는 물음이 생겼을 때 해결했어야했는데... 처음에 Post와 Comment간의 양방향 관계를 설정하기 전에는 괜찮을 줄 알고 그냥 넘겼던 사소한 궁금증이었는데, 결국 이게 stackoverflow 에러
와 함께 나타났다.
여기서 나타나는 에러는 JPA에서 1:N , N:1, 양방향 관계에서 발생하는 순환 참조 문제 때문에 발생한다고 한다.
순환 참조 문제가 나타나서, 최종적으로 해결한 Comment와 Post Entity 코드는 아래와 같다.
Post.java
@Entity
@Getter
@NoArgsConstructor
public class Post extends Timestamped{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String nickname;
@Column(nullable = false)
private String content;
@JsonManagedReference
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> comments;
@Builder
public Post(String title, String nickname, String content) {
this.title = title;
this.nickname = nickname;
this.content = content;
}
public void update(PostDto postDto) {
this.title = postDto.getTitle();
this.nickname = postDto.getNickname();
this.content = postDto.getContent();
}
}
Comment.java
@Entity
@Getter
@NoArgsConstructor
public class Comment extends Timestamped{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@JsonBackReference
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "POST_ID", nullable = false)
private Post post;
@Column(nullable = false)
private String nickname;
@Column(nullable = false)
private String comment;
@Builder
public Comment(Post post, String nickname, String comment) {
this.post = post;
this.nickname = nickname;
this.comment = comment;
}
public void update(CommentDto commentDto) {
this.nickname = commentDto.getNickname();
this.comment = commentDto.getComment();
}
}
기획 단계에서 게시글과 댓글이 서로를 통해서 조회를 할 수 있어야 했는데 이럴 때에는 양방향 관계를 사용했는데, 문제는 여기에서 발생한다.
RestController에서 Entitiy를 그대로 반환하는 경우 stackoverflow에러가 튀어나오게 된다.
Post Entitiy를 그대로 반환하면 Post가 참조하는 Comment Entity도 같이 조회하고, Comment Entity는 Post Entitiy를 조회하고...
이런 순환 참조 문제가 발생하는 경우를 찾아보니 몇가지가 나왔는데,
첫번째, Entity에서 Lombok의 @ToString
어노테이션을 사용한 경우
두번째, @ResponseBody
가 선언되어있는 경우
내 같은 경우는 두번째에 해당된다. @RestController
에 포함되어있는 @ResponseBody
때문에 객체를 json형태로 직렬화하는 과정에서 발생하는 문제였던 것이다.
참고로 @ResponseBody
를 선언할 시 jackson 라이브러리가 작동한다고 한다.
자, 문제가 발생하는 이유를 알았으니 해결해보자! Jackson 라이브러리가 문제를 일으켰으니 Jackson이 제공해주는 어노테이션을 사용해서 해결할 수 있다.
- @JsonIgnore
필드 단위로 적용되서 해당 필드를 Jackson이 무시하는 어노테이션. 이 어노테이션이 붙은 필드는 직렬화가 되지 않는다. - @JsonManagedReference, @JsonBackReference
전자는 정상적으로 직렬화를 시키는 어노테이션이고, 후자는 직렬화하지 않도록 막는 어노테이션이다. - @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
Jackson 2.0 이후부터 사용할 수 있는 어노테이션으로, 직렬화에 포함시킬 속성값을 property 속성에 지정해서 순환 참조 시 id로 중복을 방지하는 어노테이션이다.
어노테이션을 사용하는 방식 말고 가장 좋은 방법은 Entity 자체를 반환하는 대신 DTO에 매핑을 해서 이용하는 방식이 가장 좋을 것 같다.
이 방식은 내일의 TIL에서 다시 작성하기로 한다!
마치며
JPA 때문에 삽질을 되게 많이 했다. 정말 괜히 러닝커브가 높다고 하는게 아니라는 걸 몸소 깨우치고 있는데 , 또 새로운걸 알아가니까 힘들면서도 재미가 있다. 정리하고 싶은게 넘쳐나는데 시간이 너무 부족하다!!! 오늘 정리 못한 것들은 내일 집중해서 꼭! 정리하기로 한다.
오늘의 한마디는 by 희수님 - 오늘은 배운게 없다.
'Study > TIL' 카테고리의 다른 글
[TIL] 06/08 항해99 31일차 - 유효성검증, 삽질로그 (0) | 2022.06.09 |
---|---|
[TIL] 06/07 항해99 30일차 - JPA sort, 삽질로그 (0) | 2022.06.08 |
[TIL] 06/04 항해99 27일차 - 과제 [나만의 블로그 만들기] (0) | 2022.06.05 |
[TIL] 06/03 항해99 26일차 - domain & DTO (0) | 2022.06.04 |
[TIL] 06/02 항해99 25일차 - 트러블 슈팅 (1) | 2022.06.03 |