본문 바로가기

트러블슈팅

엔터티의 양방향 관계와 Lombok의 @Data와 @toString으로 인한 StackOverFlow 문제

결론부터 말하자면, 엔터티에서 양방향 관계일 때,  Lombok의 @Data와 @toString은 StackOverFlow 문제를 일으킬 수 있기 때문에 사용에 조심해야한다는 것이다.

 

문제

FareEntity라는 객체를 담은 List를 Map<FareEntity 키, FareEntity> 형태의 Map으로 변경하고자 했다.

 

변경하는 부분에서 아래와 같이 StackOVerFlow 에러가 발생하였다.

java.lang.StackOverflowError: null
	at java.base/java.lang.String.coder(String.java:3258) ~[na:na]
	at java.base/java.lang.String.getBytes(String.java:3191) ~[na:na]
	at java.base/java.lang.AbstractStringBuilder.putStringAt(AbstractStringBuilder.java:1664) ~[na:na]
	at java.base/java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:536) ~[na:na]
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:174) ~[na:na]
	at java.base/java.lang.StringBuilder.<init>(StringBuilder.java:125) ~[na:na]
	at com.tradlinx.zimgo.forwarder.entity.CnsltnCostItemEntity.toString(CnsltnCostItemEntity.java:11) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:2951) ~[na:na]
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:168) ~[na:na]
	at com.tradlinx.zimgo.forwarder.entity.FareEntity.toString(FareEntity.java:16) ~[classes/:na]
	... 생략 ...

 

문제 원인

Map<FareEntity 키, FareEntity>의 FareEntity 키를 만들 때도 toString() 메서드를 사용하지 않았는데,  에러 메세지에는 FareEntity.toString, CnsltnCostItemEntity.toString 등 toString() 메서드가 존재했다.

 

그래서 에러의 toString()을 클릭해보니, Lombok의 @Data 어노테이션을 가리켰다. 

 

 

참고로, Lombok의 @Data에는 아래의 Lombok 기능이 포함되어 있다.

  • @ToString
  • @EqualsAndHashCode
  • @Getter : 모든 필드
  • @Setter : 정적 필드가 아닌 모든 필드
  • @RequiredArgsConstructor

그래도, Map<FareEntity 키, FareEntity>를 만드는 동안 toString()을 사용하지 않았기 때문에, 왜 @Data에서 에러가 발생하는 지 알 수 없었다. 디버깅을 위해 FareCostEntity의 toString() 메서드를 직접 오버라이드하였다.

 

 

메서드 호출 스택을 살펴보니, Map<FareEntity 키, FareEntity>을 만들 때, duplicateKeyException이 발생했다. 

 

duplicateKeyException은 변수로 받은 k, u, v를 출력한다. 결과적으로 Map<FareEntity 키, FareEntity>의 v(value)에 해당되는 FareEntity의 toString() 메서드를 호출하게 된다.

 

FareEntity에도 @Data 어노테이션이 존재하였고, FareCostEntity와 양방향 관계로 되어있었다.

 

Lombok의 @ToString은 해당 엔티티가 가지고 있는 모든 속성들이 작성된다.

또한, 양방향이기 때문에 'FareEntity의 toString() 호출 -> FareCostEntity의 toString() 호출 -> FareCostEntity 내 FareEntity의 toString() 호출 -> ....' 식으로 순환하면서 toString() 메서드를 무한으로 호출하게 되는 것이다.

 

문제 해결

우선, Map을 만들 때, DuplicateKeyException이 발생했으므로, Key가 중복된 원인을 제거하였다.

StackOverFlow 에러가 다시는 나지 않도록, @Data를 사용하지 않고, @Getter, @Setter, @RequiredArgsConstructor 등 필요로 하는 Lombok 어노테이션만 사용하는 것으로 변경하였다.

 

참고 자료

https://lkhlkh23.tistory.com/m/159

 

Lombok 너무 편리하기 때문에 주의해야 한다. (Lombok 주의점)

같이 스터디했던 동생이 사전과제에 대한 평가에 DTO에 Lombok Setter가 무분별하게 사용을 했다는 피드백을 받았다. 그러다가 좋은 DTO는 무엇일까? 라는 고민을 하다가 ... 엉뚱하게 Lombok으로 화제

lkhlkh23.tistory.com

https://kwonnam.pe.kr/wiki/java/lombok/pitfall

 

java:lombok:pitfall [권남]

 

kwonnam.pe.kr