If at first you don't succeed, try again

[트러블슈팅] 엔티티에 @ToString 사용으로 인한 순환참조(feat. JPA) 본문

개발/트러블슈팅

[트러블슈팅] 엔티티에 @ToString 사용으로 인한 순환참조(feat. JPA)

웅지니어링 2025. 6. 4. 15:21

* 문제 상황

우선 Post엔티티와 PostImage 엔티티는 양방향 연관관계로 설정했다.

하나의 Post는 여러 개의 PostImage를 가질 수 있고(1 : N)

PostImage는 하나의 Post에 속해 있기 때문이었다.(N : 1)

그리고 Post 엔티티와 PostImage 엔티티에는 디버깅을 하기 위해서 @ToString 을 썼었다.

@Entity
@Table(name = "post")
@Getter
@AllArgsConstructor
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@Builder
@ToString
public class Post {
    ...
    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    @Builder.Default
    private List<PostImage> postImages = new ArrayList<>();
    ...
}

 

@Entity
@Table(name = "post_image")
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@ToString
public class PostImage {
    ...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id", nullable = false)
    private Post post;
    ...
}

그런데 서버를 실행했을 때, StackOverFlowError가 발생했다.

 

* 원인

원인은 양방향 연관관계에 있는 엔티티에 @ToString 사용 시 순환 참조로 인해 무한 반복이 되기 때문이었다.

순환 참조의 흐름은 이렇다.

  1. Post 객체를 출력하면 postImage.toString()이 호출된다.
  2. PostImage 객체의 toString()에서 post.toString()이 호출된다.
  3. post 내에 있는 Post의 toString()이 다시 호출된다.
  4. 무한 반복
  5. 메서드 호출이 중첩될수록 Stack 영역의 메모리에 계속해서 데이터가 쌓이게 되고, 결국 호출 스택이 넘치게 된다.
  6. StackOverflowError 발생

 

* 해결 방법

해결방법은 굉장히 간단한데, 엔티티에 @ToString을 되도록 붙이지 않는 것이다.

 

* 대체 방안

다른 해결 방법은 @ToString.Exclude를 사용하는 것이다.

@Entity
@Table(name = "post_image")
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
public class PostImage {
    ...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id", nullable = false)
    @ToString.Exclude
    private Post post;
    ...
}