Todo 프로젝트에 댓글 기능을 추가하면서 순환 참조 문제가 발생했다.
테스트 코드에서는 문제가 없었지만 swagger에서 테스트를 하려고 보니 Response Example Value에 문제가 있는 게 보였다.
comments 내 todo에 comments가 다시 들어가 있어서 순환 참조가 발생할 코드였다.
[Execute] 버튼을 누르면 예상대로 다음과 같이 순환참조 되면서 중복되는 많은 데이터가 출력된다.
서버 로그에는 다음과 같은 예외 메시지가 출력된다.
2024-05-13T20:25:33.474+09:00 WARN 12560 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Ignoring exception, response committed already: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError)
2024-05-13T20:25:33.475+09:00 WARN 12560 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError)]
이 문제를 어떻게 해결해야 될까 찾아보다가 Json 라이브러리의 어노테이션을 사용하는 방법이 있어서 시도해 보았다.
Json 라이브러리의 @JsonManagedReference와 @JsonBackReference
@JsonManagedReference : 부모 클래스 (Todo Entity)에 추가 - 1 쪽
@JsonBackReference : 자식 클래스 (Comment Entity)에 추가 - N 쪽
@Entity
@Table(name = "todo")
class Todo(
// ....
@OneToMany(mappedBy = "todo", fetch = FetchType.EAGER, cascade = [CascadeType.ALL], orphanRemoval = true)
@JsonManagedReference
var comments: MutableList<Comment> = mutableListOf(),
) {
// ...
}
@Entity
@Table(name = "comment")
class Comment(
// ...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "todo_id")
@JsonBackReference
val todo: Todo
) {
// ...
}
결과는 다음과 같다.
데이터는 정상적으로 출력되지만 출력되지 말아야 할 password까지 출력되었다.
그리고 Response Example Value는 여전히 순환 참조가 발생 코드로 남아있다.
이번에는 위에서 추가했던 어노테이션들을 제거하고 Entity 객체를 그대로 반환하던 방식에서 DTO를 사용했다.
data class TodoResponse(
// ...
val comments: List<CommentResponse> // List<Comment> -> List<CommentResponse>
)
@Entity
@Table(name = "todo")
class Todo(
// ...
) {
// ...
}
fun Todo.toResponse(): TodoResponse {
return TodoResponse(id!!, title, content, writer, createAt, completed, comments.map { it.toResponse() })
}
위와 같이 map 함수를 사용해 DTO로 변환시켜서 응답을 보내면 순환 참조 없이 정상적으로 데이터가 출력되었다.
Example Value에도 다음과 같이 필요한 데이터만 출력이 되었다.
결론: DTO를 잘 활용하자.
'TIL(Today I Learned)' 카테고리의 다른 글
TIL - Offset-based Pagination & Cursor-based Pagination (0) | 2024.05.16 |
---|---|
TIL - SQL WITH 재귀 쿼리 (0) | 2024.05.14 |
JPA(Java Persistence API)의 이해 2 (0) | 2024.05.10 |
JPA(Java Persistence API)의 이해 1 (0) | 2024.05.09 |
TIL - 대여 횟수가 많은 자동차들의 월별 대여 횟수 구하기 (0) | 2024.05.08 |