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;
}