쌓고 쌓다

양방향 연관관계 매핑 JSON 순환참조 문제 본문

프로그래밍/JPA

양방향 연관관계 매핑 JSON 순환참조 문제

승민아 2023. 8. 19. 15:15

에러 발생

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
org.springframework.http.converter.HttpMessageNotWritableException:
 Could not write JSON: Infinite recursion (StackOverflowError)]
java.lang.StackOverflowError

에러 내용

보면 jackson 라이브러리에서 무한하게 에러 메시지를 보내면서 StackOverflow에러가 발생했다.

 

에러 원인

Poster

@Entity
@Getter
@Setter
public class Poster {

    @OneToMany(mappedBy = "poster", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    private List<UploadFile> imgFiles = new ArrayList<>();

}

 

UploadFile

@Entity
@NoArgsConstructor
@Getter
@Setter
public class UploadFile {

    @ManyToOne
    @JoinColumn(name="pno")
    private Poster poster;

}

 

보면 Poster과 UploadFile은 양방향 매핑이 되어있다.

즉, 순환 참조가 되어있다.

Poster은 UploadFile을 참조하고 있고 UploadFile은 Poster를 참조하고 있다.

Jackson이 엔티티를 JSON으로 변환(Getter를 사용하여)하는 과정에서 문제가 발생한다.

 

Poster를 JSON으로 변환하는 과정에서

UploadFile이 존재하여 UploadFile을 직렬화를 먼저하고자 합니다.

UploadFile에는 또 Poster가 있기에 Poster를 직렬화하고자 합니다.

이렇게 무한루프에 빠지게 됩니다.

(직렬화: 객체->JSON, 비직렬화: JSON -> 객체)

해결 방법

1. @JsonIgnore

@Entity
@Getter
@Setter
public class Poster {

    @OneToMany(mappedBy = "poster", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    @JsonIgnore
    private List<UploadFile> imgFiles = new ArrayList<>();

}

JSON으로 변환할때 이 필드는 직렬화에서 무시합니다.

 

 

2. @JsonManagedReference, @JsonBackReference

@JsonManagedReference를 직렬화를 수행할 곳에 (또는 연관관계 주인 반대 Entity)

@JsonBackReference는 직렬화하지 않을 곳에 추가한다. (또는 연관관계 주인 Entity)

 

아래의 예에서는 연관관계의 주인을 따지지 않고 원하는 직렬화 구간에 적용하였다.

원래 연관관계의 주인을 따져서 바람직하게 작성하는것이 옳다.

public class Poster {
    @JsonBackReference
    @OneToMany(mappedBy = "poster", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    private List<UploadFile> imgFiles = new ArrayList<>();
}

public class UploadFile {
    @JsonManagedReference
    @ManyToOne
    @JoinColumn(name="pno")
    private Poster poster;
}

Poster의 imgFiles는 @JsonBackReference로 직렬화를 수행하지 않고

UploadFile의 poster는 직렬화 대상이 된다.

 

Poster 응답

 

 

UploadFile 응답

 

 

 

 

3. DTO

그럼 양방향으로 조회하고싶을때 어떻게 방법이 없는걸까?

데이터 전송을 위한 오브젝트(data transfer object)인 DTO를 따로 만들어 필요한 데이터만 담아 응답하는 방법도 있다.

 

 

+ @JsonManagedReference, @JsonBackReference를 연관관계 주인에 맞춰 올바르게 사용하자.

안그럼 나중에 문제가된다..

https://non-stop.tistory.com/614

 

Cannot handle managed/back reference 'defaultReference': back reference type ... not compatible with managed type ...

먼저 Comment, Poster, UploadFile 엔티티를 보자. Comment @Entity @Data public class Comment { ... @ManyToOne @JoinColumn(name="poster_id") private Poster poster; } Poster @Entity @Getter @Setter @ToString public class Poster { ... @ManyToOne @JoinCo

non-stop.tistory.com

 

Comments