상황
스프링 기반 API 서버를 구현 프로젝트에서 Controller 테스트를 하는 도중 에러가 발생했다.
nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError)
에러 로그를 확인해 보니 Could not write JSON: Infinite recursion (StackOverflowError)
부분을 통해 재귀 호출로 인해 StackOverflow가 발생한 것을 알 수 있었다.
현재 테스트하고 있는 부분은 채팅 기능 관련 api로
채팅방 식별자를 기준으로 채팅방에 속한 메시지를 불러오는 과정에서
채팅방(ChatRoom)과 채팅메시지(ChatMessage)라는 두 엔티티가 양방향 연관 관계에 놓여있어 JSON으로 포맷팅하는 과정에서 무한 순환 참조가 되는 것이 주요 원인이었다.
해결방법
1. @JsonIgnore
가장 간단해 보이는 방법이었다. 이 어노테이션을 붙여주면 해당 프로퍼티의 값은 null이 되어, 데이터가 들어가지 않는다.
2. @JsonMangedReference와 @JsonBackReference
@JsonIgnore와 같이 어노테이션을 붙여 해결하는 방법이다. 부모가 되는 엔티티에서 자식 엔티티 필드에 @JsonManagedRefernce를 , 자식 엔티티에서 부모 엔티티 필드에 @JsonBackReference를 붙인다. @JsonManagedReference를 사용하는 클래스에서는 해당 부분을 정상적으로 직렬화를 수행하고, @JsonBackReference를 사용하는 클래스에서는 직렬화를 수행하지 않게 된다.
채팅방과 채팅메시지 관계로 예를 들면 다음과 같다.
[ChatRoom.java]
@JsonManagedReference
@OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<ChatMessage> messages = new ArrayList<>();
[ChatMessage.java]
@JsonBackReference
@ManyToOne
@JoinColumn(name = "room_id")
private ChatRoom chatRoom;
3. DTO 사용
문제의 근원은 순환참조이긴 하지만 엔티티 자체를 응답 결과로 리턴하려다보니 이런 이슈가 발생하는 것이기도 하다. DTO와 같이 별도의 데이터 전달 객체로 맵핑하여 리턴을 하여 문제를 해결할 수 있다고 한다. 1,2번 방식대로 순환참조를 방지하도록 하는 것도 중요하지만, 엔티티를 직접 리턴하여 데이터를 전달하기에는 민감한 정보까지 넘겨줄 우려가 있기 때문에 DTO로 맵핑하는 과정이 필요해 보인다.
[참고]
'Tech > Spring' 카테고리의 다른 글
[Spring MVC] HandlerMapping (0) | 2022.03.11 |
---|---|
[Spring Data JPA] ORM, JPA, Hibernate 헷갈리는 용어 정리 (0) | 2022.01.19 |
[Spring] Validation @Email은 null을 허용한다. (1) | 2020.12.03 |
[Spring Boot] 갓 초기화한 스프링 부트 프로젝트 살펴보기 (0) | 2020.01.22 |
[Spring boot] 스프링 부트의 핵심 (0) | 2020.01.21 |