[JPA] 순환 참조

black·2021년 8월 24일
0

스프링부트

목록 보기
1/2
post-thumbnail

웹 프로젝트를 진행하던 중에 양방향 매핑 관계에서 발생한 순환 참조 문제를 겪어 문제가 발생한 이유와 해결 방법에 대해 포스팅한다.

JPA를 사용하여 개발을 진행하면 대부분 여러 Entity 간에 일대다 관계 매핑을 해줄 것이다.
이때 양방향 매핑을 되어 있는 Entity를 조회하게 되면 해당 Entity에 매핑되어 있는 Entity를 조회하고 이 Entity는 다시 매핑되어 있는 Entity를 조회하는 순환 참조 문제가 발생한다.

왜 순환 참조가 일어날까?

@Entity
@AllArgsConstructor
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;

    private String password;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    private List<Post> postList;
}

@Entity
@AllArgsConstructor
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String content;

    private String writer;

    @ManyToOne(fetch = FetchType.LAZY)
    private User user;
}

위의 코드와 같이 User, Post Entity가 양방향 매핑을 하고 있다고 가정해보자.
클라이언트가 본인(User)이 작성한 게시물(Post)을 조회하고 싶다고 요청이 오면 서버에서는 Post 객체를 Json 형태로 직렬화하여 Body에 응답할 것이다.
하지만 응답의 Body를 보면 매핑된 객체 간에 무한 참조가 발생한다.

Spring Boot는 Object를 json 형태로 변환하기 위해 HttpMessageConverters에서 jackson 라이브러리를 이용하여 직렬화합니다.

Process를 간단하게 알아보면 다음과 같다.
1. 요청한 Post 객체를 직렬화한다.
2. Post Entity에 user라는 필드가 있기 때문에 User 객체를 다시 직렬화한다.
3. User Entity에는 postList라는 필드가 있기 때문에 Post 객체를 다시 직렬화한다.
이 과정을 무한히 반복한다.

결론은 순환 참조의 문제는 Jackson이 객체를 직렬화하는 과정에서 발생하는 문제이다.

해결 방법

  • @JsonIgnore
    다음과 같이 JsonIgnore 어노테이션을 사용하여 해당 필드를 직렬화 대상에서 제외시킨다.
    (아예 직렬화 대상에서 제외시키는 것이 때문에 굳이 사용할 이유가 없을 것 같다..)
    @JsonIgnore
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    private List<Post> postList;
    
    @JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;
  • @JsonManagedReference, @JsonBackReference
    이 두 어노테이션을 사용하여 근본적인 순환 참조 문제를 방어할 수 있다.
    주체 클래스(User)에 @JsonMangedReference를, 비주체 클래스(Post)에 @JsonBackReference를 추가해주면 된다.
    @JsonMangedReference: 정상적으로 직렬화됨
    @JsonBackReference: 직렬화되지 않도록 막음

  • DTO 사용
    순환 참조 문제의 주 원인은 양방향 매핑이지만 더 중요한 부분은 Entity 자체를 response로 응답한 문제이다. Entity 자체를 response하는 것이 아니라, DTO 객체를 만들어 필요한 데이터만 응답함으로써 순환 참조 문제를 해결할 수 있다.

❗ 다양한 순환 참조를 해결할 수 있는 방법들이 있지만 DTO를 만들어 응답하는 것이 가장 좋은 방법이라고 생각한다. 클라이언트로부터의 요청에 따라 응답에 필요한 데이터가 상이하기 때문에 순환 참조 문제 때문이 아니더라도 DTO를 사용해 상황별로 응답할 수 있도록 만들어야 한다.

0개의 댓글