디지몬으로 이해하는 JPA 순환 참조 문제
1. 순환 참조란?
JPA에서 엔티티 간 양방향 연관 관계를 설정하면 서로가 서로를 참조하는 구조가 만들어질 수 있다.
이 자체는 잘못된 것은 아니지만, 직렬화(예: JSON 변환)나 무한 출력, 디버깅 시 순환 참조 예외로 이어질 수 있다.
예를 들어 @OneToMany, @ManyToOne 구조를 모두 선언하면 순환이 생길 수 있다.
2. 디지몬을 예시로 살펴보자.
디지몬 파트너 구조를 생각해보자.
Tamer(테이머)는 여러 마리의 Digimon(디지몬)을 가질 수 있고,
각 Digimon은 자신이 속한 Tamer를 알고 있다.
이 구조는 다음과 같이 모델링할 수 있다.
@Entity
public class Tamer {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "tamer")
private List<Digimon> digimons = new ArrayList<>();
}
@Entity
public class Digimon {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "tamer_id")
private Tamer tamer;
}
3. 순환 참조가 실제로 발생하는 순간
만약 위 데이터를 API로 제공한다고 가정하자.
@GetMapping("/tamers")
public List<Tamer> getTamers() {
return tamerRepository.findAll();
}
이제 이 요청의 결과를 JSON으로 직렬화할 때 무슨 일이 일어날까?
- Tamer → List<Digimon> 접근
- 각 Digimon → 다시 Tamer로 접근
- 다시 그 Tamer → 또 다른 Digimon으로…
→ 무한 반복
→ StackOverflowError 또는 Jackson의 순환 참조 예외 발생
실제로 직렬화 시 이런 문제가 발생한다.
{
"name": "태일",
"digimons": [
{
"name": "아구몬",
"tamer": {
"name": "태일",
"digimons": [
{
"name": "아구몬",
"tamer": {
...
}
}
]
}
}
]
}
이런 식으로 디지몬과 테이머가 서로를 계속 호출하면서 JSON 트리가 무한히 깊어진다.
4. 해결 방법
4.1 @JsonIgnore
가장 간단한 해결책은 디지몬에서 테이머를 직렬화하지 않도록 무시하는 것이다.
@Entity
public class Digimon {
...
@ManyToOne
@JoinColumn(name = "tamer_id")
@JsonIgnore
private Tamer tamer;
}
→ 이렇게 하면 디지몬 안에 테이머 정보가 직렬화되지 않는다.
장점: 간단하고 직관적
단점: API 응답으로 테이머 정보가 필요한 경우엔 불편함
4.2 @JsonManagedReference / @JsonBackReference
Jackson 전용 애노테이션으로 부모-자식 구조를 명시적으로 나누는 방식이다.
public class Tamer {
...
@OneToMany(mappedBy = "tamer")
@JsonManagedReference
private List<Digimon> digimons;
}
public class Digimon {
...
@ManyToOne
@JsonBackReference
private Tamer tamer;
}
- @JsonManagedReference → 직렬화 대상
- @JsonBackReference → 직렬화 제외
→ 테이머 → 디지몬까지만 직렬화되고,
→ 디지몬 → 테이머는 출력되지 않는다.
4.3 DTO로 변환
가장 근본적인 해결책은 엔티티 자체를 직접 반환하지 않고, DTO로 필요한 정보만 변환해서 전달하는 것이다.
public class TamerDto {
private String name;
private List<String> digimonNames;
}
→ 엔티티를 DTO로 매핑해서 필요한 정보만 넣으면 순환 참조는 발생하지 않는다.
장점: 직렬화 문제, 보안 문제, 유연성 모두 해결 가능
단점: 매핑 코드가 필요하고 귀찮을 수 있음
5. 결론
디지몬 세계관에서, 테이머와 디지몬은 서로를 알고 있어야 하지만
누군가 그대로 출력하게 되면 서로를 무한히 참조하게 된다.
무한 참조란 디지몬 세계관에서는 낭만적이지만 실무에서는 처음 맞닥들이면 꽤나 당황스러운 것 같다.
해결 방법 | 특징 |
@JsonIgnore | 빠르고 간단하지만 유연성 부족 |
@JsonManagedReference / BackReference | 구조적 대응이 가능하지만 Jackson에 종속됨 |
DTO로 변환 | 가장 확실한 방법. 근본적으로 엔티티 외부 노출을 막을 수 있음 |