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 를 생성할 때
해당 필드를 제외시키게 되고 무한 순환 참조에 빠지지 않게 된다
해결 완료!!
'🚀 프로젝트 > 🌞 내친소' 카테고리의 다른 글
[Spring] RefreshToken + Redis 사용해서 자동 로그인 + 로그아웃 구현하기 (0) | 2022.11.14 |
---|---|
[Spring] 스프링 스케쥴러 사용해서 일정 주기로 메소드 실행하기 (Scheduler) (2) | 2022.11.05 |
[Spring/Nginx] MultipartFile 최대 용량 설정하기 (MaxUploadSizeExceededException / Request Entity Too Large) (0) | 2022.11.01 |
[Spring] 문자 인증 구현하기 (Redis + 네이버 클라우드 플랫폼 SMS API) (0) | 2022.09.11 |