본문 바로가기

Java

Hibernate 순환 참조 문제로 인한 StackOverflowError 해결하기

반응형
순환 참조(Circular Reference)는 두 개 이상의 객체가 서로를 참조하면서 무한 루프를 형성하는 상황을 의미한다.
예를 들어, 객체 A가 객체 B를 참조하고, 객체 B가 다시 객체 A를 참조하는 구조가 있을 때 이를 순환 참조라고 한다.
이런 구조가 직렬화나 데이터 처리 과정에서 문제가 될 수 있다.

 

1. 순환 참조 문제의 발생 이유

순환 참조는 데이터 직렬화, 특히 JSON과 같은 형식으로 객체를 변환할 때 문제가 된다.

JSON 직렬화 과정에서, 서로 참조하는 객체들이 계속해서 직렬화되다 보면 끝없이 서로를 직렬화하게 되어 무한 루프가 발생한다.

결국 이로 인해 스택 오버플로우(Stack Overflow)나 메모리 초과 문제가 발생하게 된다.

 

@Data
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nickname;
    private String email;

    @OneToMany(mappedBy = "user")
    private List<Month> months;
}

 

@Data
@Entity
public class Month {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private int month;
    private int year;

    @ManyToOne
    private User user;
}

 

위의 User 클래스는 여러 개의 Month 객체를 참조하고, Month 클래스는 User 객체를 참조한다.

이런 양방향 참조가 있으면, JSON 직렬화 시 User가 Month를 직렬화하고, 그 안에서 다시 User를 직렬화하며 무한 루프가 발생한다.

 

이를 해결하기 위한 방법으로 DTO(Data Transfer Object) 사용이 가장 안전하고 확장 가능한 방법으로 꼽히지만

추가 DTO 없이 해결하는 방법이 있다.

 


1) @JsonManagedReference와 @JsonBackReference 사용

 

 

부모-자식 관계에서 부모 필드에는 @JsonManagedReference를,

자식 필드에는 @JsonBackReference를 사용하여 순환 참조를 방지할 수 있다.

이 방법은 직렬화할 때 순환 참조를 끊어준다.

 

 

@Data
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nickname;
    private String email;

    @OneToMany(mappedBy = "user")
    @JsonManagedReference // 직렬화 허용
    private List<Month> months;
}

 

@Data
@Entity
public class Month {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private int month;
    private int year;

    @ManyToOne
    @JsonBackReference // 순환 참조 방지
    private User user;
}

 

2) @JsonIgnore 사용

이 방법은 단순히 해당 필드를 직렬화할 때 무시하는 방식이다.

@ManyToOne
@JsonIgnore // 직렬화에서 제외
private User user;

 

 

3) @JsonIdentityInfo 사용

 

이 방법은 객체에 대한 참조를 직렬화하는 대신, 객체의 ID를 사용하여 참조를 관리한다.

이 방식은 특히 여러 객체가 같은 객체를 참조하는 경우 유용하다.

 

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nickname;
    private String email;

    @OneToMany(mappedBy = "user")
    private List<Month> months;
}

 

 

반응형