반응형
목차
✅ 트러블 슈팅
1. 문제 상황
- REST API에서 DTO 없이 엔티티(Entity)를 직접 Response로 반환할 경우, 서로의 연관 관계가 계속 참조를 일으켜 무한 순환 참조 문제 발생.
- 결과적으로 API 응답 시 무한 로딩 현상 및 StackOverflowError가 발생하게 됨.
[ 에러 코드 ]
더보기
Caused by: com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: caffeine.nest_dev.domain.keyword.entity.Keyword.profileKeywords: could not initialize proxy - no Session (through reference chain: caffeine.nest_dev.common.dto.CommonResponse["data"]->java.util.ImmutableCollections$ListN[0]->caffeine.nest_dev.domain.profile.dto.response.RecommendedProfileResponseDto["keywords"]->java.util.ArrayList[0]->caffeine.nest_dev.domain.keyword.entity.Keyword["profileKeywords"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:400)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:359)
at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:324)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:765)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:183)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:760)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:183)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:760)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:183)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:503)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:342)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1587)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1061)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:485)
... 141 more
2. 원인 분석
순환 참조란?
- 두 엔티티가 서로를 참조하면서 객체 직렬화 과정에서 무한히 서로를 호출하는 상황.
- 예시로, Profile 엔티티와 연관된 Keyword 엔티티를 직접 반환하면, 다음과 같은 순환 구조가 형성될 수 있다:
💡 Profile → ProfileKeyword → Keyword → ProfileKeyword → Profile → … (무한 반복)
// Entity를 그대로 반환하는 잘못된 예시
@Entity
public class Profile {
@OneToMany(mappedBy = "profile")
private List<ProfileKeyword> profileKeywords;
}
@Entity
public class ProfileKeyword {
@ManyToOne
private Profile profile;
@ManyToOne
private Keyword keyword;
}
@Entity
public class Keyword {
@OneToMany(mappedBy = "keyword")
private List<ProfileKeyword> profileKeywords;
}
- 위와 같이 서로가 서로를 직접 참조하고 있어, JSON으로 직렬화할 때 순환 참조로 인해 무한 반복이 발생하게 된다.
- 보안 상으로도 좋지 않음.
[ 예시 ]
keywords : [
id:
name:
profileKeywords: [
id:
profile: {
},
keyword: {
}
]
]
3. 해결 방법
- DTO를 만들어 순환 참조를 끊고 필요한 정보만 전달하여 무한 참조 방지.
// ProfileResponseDto 예시
public class ProfileResponseDto {
private Long id;
private String title;
private List<String> keywords;
public static ProfileResponseDto from(Profile profile) {
ProfileResponseDto dto = new ProfileResponseDto();
dto.id = profile.getId();
dto.title = profile.getTitle();
dto.keywords = profile.getProfileKeywords().stream()
.map(pk -> pk.getKeyword().getName())
.collect(Collectors.toList());
return dto;
}
}
- 이렇게 하면 실제 엔티티 대신 간단한 문자열 리스트나 프리미티브 타입만 담긴 DTO를 반환하여 순환 참조를 막을 수 있음.
REST API에서 데이터를 반환할 때는 항상 DTO를 활용하여 필요한 데이터만 선별하여 반환하자!
반응형
'스파르타 내일배움캠프 > TIL(Today I learned)' 카테고리의 다른 글
Backend와 Frontend 연결 방법 (1) | 2025.06.19 |
---|---|
면접 질문 (1) | 2025.06.18 |
예약 동시성 제어 테스트 코드 (1) | 2025.06.17 |
에러코드 출력 관련 트러블 슈팅 (1) | 2025.06.16 |
Redisson 분산락 vs Redis 원자 연산 기반 동시성 제어 (2) | 2025.06.12 |