[Spring] JPA 연관관계에서 직렬화

·2025년 8월 3일
0

Spring Boot

목록 보기
6/6
post-thumbnail

서론

Spring에서 REST API를 구현할 때, Spring JPA와 Jackson을 많이 사용한다. 이 환경에서 양방향 연관관계를 매핑할 때 무슨 문제가 있을까?

순환 참조??

두 객체가 서로를 참조하면서 참조의 참조를 하는상황

@Entity
public class User {
    @OneToMany(mappedBy = "user")
    private List<Post> posts;
}

@Entity
public class Post {
    @ManyToOne
    private User user;
}

이 구조에서 User는 Post 리스트를 가지고 있고, Post는 다시 User를 참조
참조가 끝없이 반복되며, 결국 StackOverflowError 또는 API 무한 응답 대기 문제가 발생

중요한 이유

  • 프론트엔드로 JSON 데이터를 보낼 때 응답이 끝나지 않음
  • 로깅이나 디버깅 시 콘솔 출력조차 안 됨
  • Jackson이 객체 그래프를 순회하면서 순환 루프에 빠지기 때문

해결 방법

1. @JsonIgnore

해당 필드를 직렬화 대상에서 완전히 제외하는 방법

@Entity
public class Post {
    @Id @GeneratedValue
    private Long id;
    private String title;

    @ManyToOne
    @JoinColumn(name = "user_id")
    @JsonIgnore
    private User user;
}

장점

  • 구현이 간단하고, 순환 참조 문제가 즉각 해결됨

단점

  • 무조건 직렬화에서 제외되므로, 클라이언트가 Post 객체 안의 user 정보가 필요할 경우 별도 처리 필요
  • 양방향 정보 제공이 불가능해, 필요하다면 DTO 변환 로직이 복잡해질 수 있음

2. @JsonManagedReference / @JsonBackReference

Jackson이 부모‒자식 관계를 인식하도록 표시

@Entity
public class User {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user")
    @JsonManagedReference
    private List<Post> posts;
}

@Entity
public class Post {
    @Id @GeneratedValue
    private Long id;
    private String title;

    @ManyToOne
    @JoinColumn(name = "user_id")
    @JsonBackReference
    private User user;
}
  • @JsonManagedReference가 붙은 쪽(User.posts)은 직렬화(serialize) 시 포함
  • @JsonBackReference가 붙은 쪽(Post.user)은 역직렬화(deserialize) 시 포함, 직렬화 시엔 무시

장점

  • 부모 → 자식 방향은 JSON에 노출, 자식 → 부모 방향 순환은 차단
  • 양방향 데이터가 일부 필요한 경우 활용 가능

주의사항

  • 관계가 둘 이상이면 각각에 대해 value 속성으로 식별자 지정 필요
  • JPA 매핑과 Jackson 매핑이 분리되어 관리 포인트가 늘어남

3. @JsonIdentityInfo

같은 객체가 반복될 경우 중복 출력하지 않고 참조만 남기는 방식

@Entity
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "id"
)
public class User {
    @Id
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user")
    private List<Post> posts;
}

@Entity
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "id"
)
public class Post {
    @Id
    private Long id;
    private String title;

    @ManyToOne
    private User user;
}

장점

  • 순환 참조를 안전하게 처리할 수 있음
  • 복잡한 객체 그래프에서도 유지보수 부담 없이 순환 해결 가능

단점

  • 프론트엔드에서 ID로 다시 객체를 매핑해야 하므로 가독성이 떨어질 수 있음
  • 객체 전체가 아닌 참조 ID만 노출되기 때문에 추가 요청 없이 데이터를 바로 사용할 수 없음

언제 사용하는가?

  • 관계가 깊고 엮인 구조가 많아 @JsonIgnore 또는 @JsonManagedReference로는 감당이 어려울 때
  • 복잡한 객체 그래프를 객체 ID 중심으로 표현하는 게 적절한 시스템 (예: 그래프 DB, 트리 구조 등)
  • 빠르게 순환 참조를 막고 싶은 경우

4. DTO

DTO(Data Transfer Object)는 엔티티(Entity)의 데이터를 클라이언트에 전달하거나 요청받기 위한 전용 객체

public class TagPlainDto {
    private Long id;
    private String name;

    public static TagPlainDto fromEntity(TagPlain tag) {
        return new TagPlainDto(tag.getId(), tag.getName());
    }

    // Constructor, Getter
}

장점

  • 순환 참조 문제 해결
  • 보안성 향상
  • 도메인 모델과 분리된 API 구조

단점

  • 코드가 늘어남
  • 변환 로직 필요
  • 성능 고려 필요

결론

방식장점단점추천 상황
@JsonIgnore- 구현이 간단
- 즉시 순환 차단
- 해당 필드가 JSON에서 빠짐
- 필요한 데이터는 별도 처리 필요
간단히 순환만 끊고 싶을 때
@JsonManagedReference / @JsonBackReference- 부모→자식만 노출하며 순환 방지
- 일부 양방향 유지 가능
- 관계가 많아지면 value 관리 복잡
- 매핑 설정 포인트 증가
명확한 부모-자식 구조에서 순환만 제어할 때
@JsonIdentityInfo- ID 기반 참조로 복잡한 순환 그래프도 안전 처리- JSON 가독성↓ (ID만 노출)
- 추가 데이터 요청 필요
깊게 얽힌 연관 구조를 ID 중심으로 표현할 때
DTO 분리- 순환 참조 완전 제거
- 보안성·유연성·문서화 강점
- DTO 클래스·매핑 로직 작성량 증가
- 유지보수 시 동기화 필요
실무 대형 프로젝트에서 안정적 API 설계가 필요할 때

0개의 댓글