티스토리 뷰
양방향순환참조에러, Stackoverflow: null
에러내용
- 미니프로젝트 중 comment를 등록하면 로그에 stackoverflow: null이라는 메시지와 함께 무한 로그가 출력되며 서버가 터졌다.
- 로그에는 post["comment"] -> comment -> member -> post["comment"] 이런식으로 찍혀있어 무한 순환참조가 일어나는 듯 했다.
원인
- JPA에서 양방향으로 연결된 entity를 JSON형태로 직렬화하는 과정에서 서로의 정보를 계속해서 순환하여 참조하여 stackoverflow가 발생한 것이다.
- Spring Boot는 @ResponseBody(rest api)를 구현할 시 Object를 JSON 형태로 변환하기 위해 Jackson 라이브러리를 이용하는데, Jackson은 entity의 getter를 호출하고, 직렬화를 이용해 JSON 형태로 객체를 변화시키고 view로 전달한다.
- getter를 호출하는 과정에서부터 순환 참조가 계속 발생해 view로 전달하면서 stackoverflow가 발생하게 된다.
- ※ 직렬화란, 객체의 내용을 바이트 단위로 변환하여 파일 또는 네트워크를 통해 스트림(송수신)하도록 하는 것을 의미한다.
다음 코드는 게시글과 댓글의 Entity 클래스이다.
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Entity
public class Posts extends TimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "posts", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
private List<Comment> comments;
...
}
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Table(name = "comments")
@Entitypublic
class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
@ManyToOne
@JoinColumn(name = "posts_id")
private Posts posts;
@ManyToOne
@JoinColumn(name = "user_id")
private User user; // 작성자}
Posts Entity에서 Comment를 부르는 부분을 보면 다음과 같다.
@OneToMany(mappedBy = "posts", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
private List<Comment> comments;
Posts에서 Comment는 연관관계의 주인이 아니기 때문에 데이터베이스에서 따로 FK로 저장되진 않는다.
하지만 JPA는 서버에서 클라이언트로 Posts의 정보를 보내줄 때 댓글 정보도 같이 보내주게 된다.
반대로, Comment Entity에서 Posts를 부르는 부분을 보자.
@ManyToOne
@JoinColumn(name = "posts_id")
private Posts posts;
Comment는 연관관계의 주인이므로 데이터베이스에 Posts에 대한 "posts_id"를 FK로 갖고 있다.
이 또한 JPA가 서버에서 클라이언트로 Comment의 정보를 보내줄 때 Posts 정보도 같이 보내주게 된다.
REST API로 Entity를 컨트롤러에서 직접조회하는 경우 다음과 같은 결과를 초래한다.
Posts > Comment > Posts > Comment와 같이 서로를 계속해서 참조하게되어 순환 참조를 하는 것이다.
해결
순환 참조를 방지하기 위한 방법은 여러 가지가 있다.
1. @JsonIgnore
이 어노테이션을 붙이면 JSON 데이터에 해당 프로퍼티는 null로 들어가게 된다.
즉, 데이터에 아예 포함시키지 않는다.
2. @JsonManagedReference 와 @JsonBackReference
부모 클래스(Posts entity)의 Comment 필드에 @JsonManagedReference를, 자식 클래스(Comment entity)의 Posts 필드에 @JsonBackReference를 추가해주면 순환 참조를 막을 수 있다.
3.@JsonIgnoreProperties
부모 클래스(Posts entity)의 Comment 필드에 @JsonIgnoreProperties({"posts"}) 를 붙여주면 순환 참조를 막을 수 있다.
4. DTO 사용
위와 같은 상황이 발생하게된 주원인은 '양방향 매핑'이기도 하지만, 더 정확하게는 Entity 자체를 response로 리턴한데에 있다. entity 자체를 return 하지 말고, DTO 객체를 만들어 필요한 데이터만 옮겨담아 Client로 리턴하면 순환 참조 관련 문제는 애초에 방지 할 수 있다.
5. 매핑 재설정
양방향 매핑이 꼭 필요한지 다시 한번 생각해볼 필요가 있다. 만약 양쪽에서 접근할 필요가 없다면 단방향 매핑을 해줘서 자연스레 순환 참조 문제를 해결하자.
참고문서
'개발냥이 > etc' 카테고리의 다른 글
[CI/CD] Docker & Jenkins를 이용해 Springboot CI/CD 구축해보기 (0) | 2022.11.05 |
---|---|
[Docker] EC2 인스턴스 연결하기, Docker ubuntu에 설치하기 (1) | 2022.11.03 |
🐬돌고돌래🐋 여행 추천 서비스 OPEN! (0) | 2022.10.21 |
[회고] 프론트와 첫 협업 프로젝트 (0) | 2022.09.11 |
깃 매뉴얼(Git manual)_feat, SourceTree (0) | 2022.08.28 |
- Total
- Today
- Yesterday
- Queue
- DP
- Algorithm
- java
- 자바bfs
- dfs
- Comparator
- 백준
- 자바스크립트
- 프로그래머스
- JPA
- SQLD
- 리액트
- JavaScript
- 형변환
- BFS
- 스프링
- Nest
- 이분탐색
- CS
- 자바
- Spring
- 정렬
- 해시맵
- 스프링부트
- SQL
- 알고리즘
- 자바트리
- 타입스크립트
- 자바dp
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |