양방향 엔티티의 재귀 호출 문제와 해결 방법

새로 개발 중인 기능에 필요한 데이터 구조를 양방향 엔티티로 설계하게 되었습니다.

 

그런데 이 과정에서 양방향 참조로 인해 재귀 호출 문제가 발생하였고,

이를 해결할 방법을 찾아보게 되었습니다.

 

예시 코드들은 업무와 무관하게 작성되었습니다.

 


문제 상황

CategoryProduct 엔티티는 양방향 연관관계를 가지고 있습니다.

스크린샷 2025-01-05 오후 8.57.59.png

 

예를 들어, CategoryProduct의 리스트를 가지고 있고, Product는 다시 Category를 참조하는 구조입니다.

 

코드로 나타내면 다음과 같습니다.

@Entity
@Table(name = "category")
public class Category {

    @Id
    private Long id;

    @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Product> products;
}

@Entity
@Table(name = "product")
public class Product {

    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id", insertable = false, updatable = false)
    private Category category;
}

 

이런 구조에서 Category 엔티티를 프론트로 전달하려고 접근했을 때,

재귀 호출로 인해 JSON 직렬화 과정에서 무한 루프가 발생하며 StackOverflowError가 발생했습니다.

 


문제 해결

해당 방법들은 크게 2가지로 해결할 수 있습니다.

  1. @JsonBackReference@JsonManagedReference 사용하기
    • @JsonBackReference: 자식 엔티티에서 사용하며, 부모 엔티티로의 참조를 직렬화하지 않아 무한 루프를 방지합니다.
    • @JsonManagedReference: 부모 엔티티에서 사용하며, 직렬화 대상 엔티티를 지정합니다.
    @Entity
    @Table(name = "category")
    public class Category {
    
        @Id
        private Long id;
    
        @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
        @JsonManagedReference
        private List<Product> products;
    }
    
    @Entity
    @Table(name = "product")
    public class Product {
    
        @Id
        private Long id;
    
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "category_id", insertable = false, updatable = false)
        @JsonBackReference
        private Category category;
    }

    이 설정을 통해
    Category 엔티티를 JSON으로 직렬화할 때 Product는 포함되지만, 다시 Category로의 참조는 직렬화되지 않아 무한 루프 문제가 해결됩니다.

  2. @JsonIgnore을 이용해서 직렬화 제외 필드 설정하기
    • @JsonIgnore를 사용하여 직렬화 제외를 설정할 수도 있습니다. 하지만 이 경우 필드가 null로 처리되기 때문에 필요한 상황에서 문제가 될 수 있습니다.
    • 참고: StackOverflow