Notion - JSON 직렬화에서의 이슈 해결 (through reference chain)
문제
최근 팀 프로젝트를 하면서 겪은 문제인데, 진행 중의 겪은 이슈의 원인을 제대로 파악하지 못해서 자세히 정리해보게 되었다.
문제의 코드와 에러 내용은 아래와 같았다.
코드
public class Article {
@OneToMany(mappedBy = "article", fetch = FetchType.EAGER)
@Builder.Default
private List<Comment> commentList = new ArrayList<>();
}
// ----------------------------------
public class Comment {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private Member author;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
private Article article;
}
에러내용
through reference chain:
GlobalResponse["data"]->
ArticleDetailResponseDto["article"]->
Article["commentList"]->
PersistentBag[3]->
Comment["author"]->
Member$HibernateProxy$LW4Bw84e["hibernateLazyInitializer"])
원인
객체를 JSON으로 직렬화하는 과정에서 발생한 문제이다.
결론부터 얘기하자면 각 객체의 필드를 역직렬화하는 과정에서 프록시 객체에는 실제 값이 들어있지 않아서 발생한 오류이다.
원인을 바로 파악하지 못했던 이유는, 이전에 겪었던 문제 중에 JSON 직렬화 과정에서 발생한 순환참조 이슈와 헷갈렸기 때문이었다.
다만, 그 때는 양방향 연관관계 매핑을 사용했었고 그로 인해 아래와 같이 순환 구조가 확실하게 눈에 보이는 상황이었다.
이전 관계 예시
Article["commentList"]->
PersistentBag[3]->
Comment["author"]->
Member["articleList"]->
PersistentBag[1]->
Article["commentList"] // ... 반복
위와 같은 구조가 아니었기에, 비슷한 에러가 발생했을 때 원인을 파악하는데 시간이 걸렸다.
자세히 설명해보자면, Entity 간의 관계는 아래와 같았다.
Article에서는 Comment를 EAGER Loading으로 가져왔지만, Comment에서는 Member(Author)를 LAZY Loading으로 가져오게되었다.
이 과정에서 Comment["author"] -> Member$HibernateProxy$LW4Bw84e["hibernateLazyInitializer"]
와 같이 Comment를 통해 가져온 Member 객체는 프록시 객체임을 알 수 있다.
프록시 객체에는 실제 값이 담겨있지 않기 때문에, 이를 JSON이 역직렬화 하는 과정에서 에러를 뱉어낸 것이다.
해결방법
FetchType.EAGER
- 필요한 데이터를 모두 로딩FetchType.LAZY
를 사용하여 프록시 객체를 읽어오지 못해 발생한 문제이므로 EAGER을 사용하여 해결 할 수 있다.- 물론 EAGER은 모든 데이터를 불러오는 방법이기 때문에 권장하지는 않는다.
public class Article {
@OneToMany(mappedBy = "article", fetch = FetchType.EAGER)
@Builder.Default
private List<Comment> commentList = new ArrayList<>();
}
// ----------------------------------
public class Comment {
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private Member author;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
private Article article;
}
@JsonIgnore
,@JsonBackReference
- 역직렬화 대상에서 제외- 역직렬화 대상에서 제외함으로써 오류를 회피하는 방법이다.
- 이 방법도 역직렬화에서 제외할 대상을 일일히 지정하는 작업이고, 해당 정보가 필요해 제외할 수 없다면 권장하지 않는다.
public class Article {
@OneToMany(mappedBy = "article", fetch = FetchType.EAGER)
@Builder.Default
private List<Comment> commentList = new ArrayList<>();
}
// ----------------------------------
public class Comment {
@JsonBackReference
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private Member author;
@JsonBackReference
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
private Article article;
}
- DTO 사용 - 직렬화 대상 정의 (추천)
- Response로 전달될 대상을 직접 정의 함으로써 오류를 방지하는 방법이다.
- DTO를 사용함으로써, 선택적으로 데이터를 가져올 수 있고, 역직렬화로 인해 발생할 문제들을 해결 할 수 있다.
public class ArticleDTO {
String title;
String content;
String author;
List<CommentDTO> comments;
// ...
}
public class CommentDTO {
String content;
String author;
}
결론적으로는 DTO를 사용해서 직렬화에서 발생할 수 있는 에러들도 해결하고, 선택적으로 데이터를 골라 응답할 수 있기 때문에 원하는 정보만 전달 할 수 있게 하는 것이 좋다.
'트러블슈팅' 카테고리의 다른 글
Kakao Login 시 401 Unauthorized (1) | 2024.01.26 |
---|---|
CreatedDate, LastModifiedDate 사용 시 값이 들어가지 않는 이슈 (0) | 2024.01.21 |
[오류를 잡아보자] 생성자 바인딩 이슈 (Cannot resolve parameter names for constructor) (0) | 2024.01.17 |
WSL 설치 오류 (1) | 2023.12.20 |
[오류를 잡아보자] Spring boot 에서의 경로 지정 (ERR_ABORTED 404 해결) (0) | 2022.03.20 |