개요
오늘 개인 프로젝트에 JPA를 적용하고 잘 작동하는지 테스트하던 도중, StackOverflowError 가 생겼다. 자세히 들여다보니 Entity에 붙인 @ToString 때문에 생긴 오류인 것을 알아냈다.
원인은?
문제가 발생한 entity를 살펴보자.
@Getter
@ToString
@EqualsAndHashCode
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity(name = "Member")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private String password;
@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Image> images = new ArrayList<>();
...
}
@Getter
@ToString
@EqualsAndHashCode
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String prompt;
@Column(name = "image_size")
private String size;
private String url;
@ManyToOne
private User owner;
...
}
위의 User 와 Image는 서로 연관 관계를 갖고 있다. 이때, 다음과 같이 삭제 할 Image entity를 찾고
Image imageToDelete = imageRepository.findById(1L).get();
아래와 같이 User 에서 담고 있던 Image 를 삭제하면
owner.getImages().remove(imageToDelete);
이렇게 StackOverflowError 가 발생한다. 왜 이런 일이 발생하는 것일까?
위의 error stack를 자세히 읽어보면, 사이 사이에 Image.toString() 과 User.toString() 이 반복적으로 호출되는 것을 알 수 있다.
각 entity에 붙은 @ToString 에 의한 순환참조가 일어나다 보니 결국에는 각 객체가 반복해서 .toString() 을 호출하게 된 것이다.
해결 방법
해결 방법은 매우 간단하다. @ToString 에 의한 순환 참조를 끊어주면 된다.
1. @ToString(exclude = ...) 사용
기본적으로 Lombok 의 toString() method는 해당 클래스의 모든 필드와 값을 String 값으로 표현한다. 그렇기 때문에 제외할 필드를 지정한다면 해결이 가능하다.
@ToString(exclude = "bar")
public class Foo {
@ManyToOne
private Bar bar;
...
}
혹은 @ToString.exclude 를 제외하고 싶은 필드에 직접 붙여서 사용 가능하다.
@ToString
public class Foo {
@ManyToOne
@ToString.exclude
private Bar bar;
...
}
2. toString() 재정의
@ToString annotation을 사용하지 않고, 직접 toString() 을 override해서 제외할 필드를 빼고 정의 가능
public class Foo {
@ManyToOne
private Bar bar;
...
public String toString() {
... // bar 제외
}
}
마무리
아주 사소하지만 자주 일어날 수 있는 문제라 생각해서 이 글에 정리했다. 작은 기능 하나라도 제대로 알고 사용해야 한다는 것을 다시 한 번 깨닫게 되는 계기가 되었다.
Reference
'JPA' 카테고리의 다른 글
[Spring / JPA] Soft Delete를 JPA에서 적용하는 방법 (0) | 2024.05.21 |
---|---|
[Spring /JPA] Spring JPA Delete Query 작성 시 주의할 점 (0) | 2023.12.26 |
[JPA] JPA Auditing (0) | 2023.12.24 |
[JPA] 지연 로딩과 즉시 로딩 (0) | 2023.12.24 |
[JPA] JPA 연관 관계 정리 (0) | 2023.12.24 |