티스토리 뷰
[JPA] Lazy 로딩 Jackson Serialize 에러, @JsonIgnore말고 더 나은 방법 제시
foodev 2023. 2. 16. 11:39JPA 엔티티 사용 시 발생한 에러에 대해 다루고, @JsonIgnore이 아닌
DTO와 @Projection을 활용한 해결방법을 제시한다.
에러내용
serializer found for class -org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer..... 엔티티[엔티티]...-> [엔티티]... 블라블라~..
오류 발생원인
순환참조로 인한 오류
JPA에서 엔티티 매핑을 하는 경우 엔티티 파일이 서로
참조하는 대상이 서로 물려 있어서 참조할 수 없게 되는 현상을 말한다.
순환참조가 발생하는 이유
Spring Boot는 @ResponseBody를 선언 할시 Object를 json상태로 변환하기 위해 Jackson라이브러리를 이용하는데.
Jackson은 직렬화를 이용해서 json 형태로 객체를 변환시킬 때 발생한다.(@OneToMany, @ManyToOne에서 반복 참조로 인해)
* 직렬화란?
객체의 직렬화는 객체의 내용을 바이트 단위로 변환하여 파일 또는 네트워크를 통해서 스트림(송수신)이 가능하도록 하는 것을 의미한다.
해결방법
해결방법으론 다양한 방법들이 나온다
구글링 해보면 Json 혹은 Jackson 어노테이션을 활용하는 방법과 Dto로 응답 결과값을 리턴하는 방법이 존재한다.
하지만 나는 Dto로 해결하는 방법을 추천한다.
그 이유는 @JsonIgnore를 특정 엔티티에 선언해 버리면 Json 응답에 특정 엔티티 값이 빠져버리기 때문이다.
자세한 내용은 아래에서 살펴보자
@JsonIgnore 선언하기
오류를 살펴보면, 순환참조가 일어나는 엔티티에 가서
@OneToMany 어노테이션이 선언되어 있는 객체 위에 @JsonIgnore를 선언한다.
그러면 JSON의 응답 결과에 @JsonIgnore이 선언되어 있는 객체가 빠지게 되어 오류를 해결할 수 있다.
@JsonIgnore 선언하기의 문제점
@JsonIgnore를 사용하면 손쉽게 문제를 해결할 수 있다.
하지만 내가 생각하는 문제점은 A라는 엔티티에 @JsonIgnore를 선언해 버리면
Json 응답 결과가 빠져버리게 되는데
A엔티티 값도 포함하고 싶을 경우에 알 수가 없다.
그래서 나의 경우는
서비스 내에서 응답 결과로 해당 엔티티가 필요 없는 경우의 확장성까지 생각해서
@JsonIgnore를 선언한다(잘 선언 안 한다는 뜻)
이럴 경우는 응답할 Dto를 따로 만들어주어 해결한다.
Dto로 해결하기 feat. @QueryProjection
JPA 쿼리 결괏값을 엔티티로 return 해줄 때
엔티티가 아닌 Dto를 만들고 @QueryProjection을 활용하면
내가 원하는 데이터와, 연관관계 매핑 되어 있는 객체들의 값들도 가져올 수 있다.
Dto로 해결 방법
dto 예제
@Getter
@NoArgsConstructor
public class TeamMemberInfosResDto {
private Long id;
private String memberName;
private String birthday;
private String imageUrl;
@QueryProjection
public TeamMemberInfosResDto(Long id, String memberName, String birthday, String imageUrl) {
this.id = id;
this.memberName = memberName;
this.birthday = birthday;
this.imageUrl = imageUrl;
}
}
내가 전달하려는 값은 Team 엔티티에서의 id와 Member엔티티에서의 이름, 생일, Image엔티티에서 파일경로이다.
[1]
3개의 엔티티에 저장되어 있는 결과 값을 가져오기 위해서 Dto에 값을 선언한다.
[2]
@QueryProjection을 통해 생성자를 만들어준다.
[3]
위의 방법까지 했다면 우측의 Gradle 버튼 -> compileQuerydsl을 눌러 queryDsl 파일을 만들어준다.
[4]
Repository에서 new 연산자를 사용하여 QTeamMemberInfoResDto객체를 Heap 메모리 영역에 메모리 공간을 할당한다.
[5]
select 할 값을 선언한다.
[6]
from절에서 기준이 되는 엔티티 하나만 선언한다.
[7]
join을 통해 member, image 엔티티를 조인한다.
[8]
리턴값은 위에서 만든 Dto 값으로 리턴한다.
public List<TeamMemberInfosResDto> getTeamMemberInfoById(Long teamId) {
return queryFactory.select(new QTeamMemberInfosResDto(
teamMemberInfo.id
,member.name
,member.birthday
,image.imageUrl
))
.from(teamMemberInfo)
.leftJoin(teamMemberInfo.image, image)
.join(teamMemberInfo.member, member)
.where(teamMemberInfo.team.id.eq(teamId))
.where(teamMemberInfo.authority.eq(AuthorityStatus.LEADER)
.or(teamMemberInfo.authority.eq(AuthorityStatus.SUB_LEADER))
.or(teamMemberInfo.authority.eq(AuthorityStatus.TEAM_MEMBER)))
.fetch();
}
이렇게 하면 순환참조도 해결할 수 있고, @JsonIgnore로 선언하여 원하는 데이터를 못 가지고 오는 문제도 해결할 수 있다.
혹시 더 좋은 방법이 있다면 댓글로 피드백 부탁드리겠습니다! 감사합니다.
'💻 개발 > JPA, Querydsl' 카테고리의 다른 글
[Querydsl] 페이징 처리 하면서 랜덤하게 응답 API 전달하기 (0) | 2023.03.30 |
---|---|
[JPA] JPA에서 update하는 방법, 더티체킹과 벌크연산 (0) | 2023.02.26 |
[Querydsl] Querydsl과 DTO와 BooleanBuilder를 활용하여 조회하기 (0) | 2023.01.25 |
[JPA]즉시로딩과 지연로딩은 언제 사용할까? (feat. 프록시) (0) | 2022.12.27 |
[JPA] JPA의 OSIV란? OSIV 적용예제, OSIV 성능최적화 방법 (0) | 2022.08.29 |
- Total
- Today
- Yesterday