🚀 프로젝트/🌞 내친소

[Spring] JPA + Lombok 사용할 때 @OneToOne 에서 발생하는 StackOverflowError 해결

gengminy 2022. 9. 24. 17:56

JPA 와 Lombok 을 같이 사용중이라면 발생할 수 있는 에러

 

❌ 원인

@OneToOne 또는 @OneToMany 를 통해 연관관계를 정의했고

이후 데이터를 꺼내올 때 hashCode 또는 toString 을 호출하면서

무한 순환 참조에 의해 스택 오버플로우가 발생하는 에러이다

 

java.lang.StackOverflowError: null
	at com.tikitaka.naechinso.domain.member.entity.Member.toString(Member.java:21) ~[main/:na]
	at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na]
	at com.tikitaka.naechinso.domain.member.entity.MemberDetail.toString(MemberDetail.java:18) ~[main/:na]
	at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na]
	at com.tikitaka.naechinso.domain.member.entity.Member.toString(Member.java:21) ~[main/:na]
	at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na]
    ...

 

또는

 

Caused by: java.lang.StackOverflowError: null
	at com.tikitaka.naechinso.domain.member.entity.Member.hashCode(Member.java:22) ~[main/:na]
	at com.tikitaka.naechinso.domain.member.entity.MemberDetail.hashCode(MemberDetail.java:19) ~[main/:na]
	at com.tikitaka.naechinso.domain.member.entity.Member.hashCode(Member.java:22) ~[main/:na]
	at com.tikitaka.naechinso.domain.member.entity.MemberDetail.hashCode(MemberDetail.java:19) ~[main/:na]
	at com.tikitaka.naechinso.domain.member.entity.Member.hashCode(Member.java:22) ~[main/:na]

 

📌 Lombok 어노테이션

 

@EqualsAndHashCode 는 equals 와 hashCode 를 자동 생성해준다

  • eqauls : 두 객체의 내용이 같은지 비교 (동일성)
  • hashCode : 두 객체가 같은 객체인지 존재를 비교 (동등성)

@ToString 은 toString 을 자동 생성하여 멤버 데이터를 문자열로 만들어준다

 

@Data는

@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode

 한번에 정의해준다

 

 

🔍 엔티티 정의

📝 Member.java

@Entity
@Table(name = "member")
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class Member extends BaseEntity {

    @Id
    @Column(name = "mem_id")
    @GeneratedValue
    private Long id;

    @OneToOne(mappedBy = "member")
    @JoinColumn(name = "mem_detail")
    private MemberDetail detail;
    
}

 

📝 MemberDetail.java

@Entity
@Table(name = "member_detail")
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class MemberDetail extends BaseEntity {

    @Id
    @Column(name = "mem_id")
    private Long id;

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @MapsId
    @JoinColumn(name = "mem_id")
    private Member member;
    
}

Member 와 멤버의 디테일을 저장하는 MemberDetail 을

1대1 연관관계를 맺었다

 

MemberRepository.findAll 을 통해 Member 정보를 가져올 때

hashCode 를 무한으로 호출하면서 순환 참조에 빠졌다

 

 

🔑 해결 방안

📝 MemberDetail.java

@Entity
@Table(name = "member_detail")
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = {"member"})
@EqualsAndHashCode(exclude = {"member"})
public class MemberDetail extends BaseEntity {

    @Id
    @Column(name = "mem_id")
    private Long id;

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @MapsId
    @JoinColumn(name = "mem_id")
    private Member member;
    
}

자식 엔티티에다가

@ToString 과 @EqualsAndHashCode 의 옵션 중에 exclude를 추가해주면 된다

exclude = { "원하는 필드명" } 을 추가하고

원하는 필드명에 연관관계를 맺은 엔티티를 적어준다

 

이렇게 되면 ToString 과 EqualsAndHashCode 를 생성할 때

해당 필드를 제외시키게 되고 무한 순환 참조에 빠지지 않게 된다

 

해결 완료!!