Querydsl NPE가 발생할 경우
개인 프로젝트의 조회 쿼리 작성 중에 NPE에러가 발생하게 됨으로써 이를 해결하기 위해 알게된 내용을 정리하여 작성한 글입니다.
1. 원인
조회 쿼리를 작성 중에 NPE 에러가 발생하였습니다.
@Override
public List<MyPageHomeDto.ReservationExhibition> getReservationConfirmExhibition(Long userId,
ReservationStatus status) {
return queryFactory.selectDistinct(
Projections.bean(MyPageHomeDto.ReservationExhibition.class, payment.id,
payment.exhibitionStock.exhibition.preImg,
payment.exhiitionStock.exhibition.title,
payment.reservationStatus, payment.exhibitionStock.exhibition.location,
payment.exhibitionStock.exhibition.hostUser.nickname.as("hostName"),
payment.exhibitionStock.startDatetime))
.from(payment)
.join(payment.user, user)
.where(eqStatus(status))
.orderBy(payment.exhibitionStock.startDatetime.desc())
.fetch();
}
디버깅을 한 결과 HostUser에서 nickname을 가져와야하는데 HostUser가 Null 이 들어있었습니다.
StackOverflow : java.lang.NullPointerException: while filtering data using QueryDsl - Stack Overflow
공식문서 : 3.3. Code generation (querydsl.com)
StackOverflow와 공식문서에 따르면 객체 그래프가 4개 이상 넘어가게 되면 NPE가 발생한다고 합니다.
2. 해결
해결방법은 @QueryInit을 사용하는 것입니다.
아래 내용은 공식문서의 내용을 가져와 정리하였습니다.
Path initialization
기본적으로 Querydsl은 처음 두 수준의 참조 속성만 초기화합니다.
더 긴 초기화 경로가 필요한 경우 `com.mysema.query.annotations.QueryInit`를 통해 도메인 유형에 어노테이션을 달아야 합니다.
QueryInit은 상세 초기화가 필요한 속성에 사용됩니다. 다음 예에서는 사용법을 보여 줍니다.
@Entity
class Event {
@QueryInit("customer.address")
Account account;
}
@Entity
class Account{
Customer customer;
}
@Entity
class Customer{
String name;
Address address;
// ...
}
해당 예시는 Event가 루트 경로/변수로 초기화될 때 account -> customer 경로로 초기화를 하게되어 customer.address 값을 가져올 수 있게 됩니다.
경로 초기화 형식은 와일드카드도 지원합니다.
선언적 형식은 쿼리 유형의 모든 최상위 인스턴스에 적용되고 최종 엔터티 필드의 사용을 가능하게하는 이점이 있습니다.
나의 쿼리의 경우 Payment <- ExhibitionStock <- Exhibition <- HostUser 이렇게 매핑된 DB 구조였다.
Payment에서 Hostuser 정보를 얻기 위해서는 관계가 2 depth 이상 깊어지기 때문에 초기화를 해줘야 했다.
이렇게해서 NPE 문제를 해결할 수 있었지만 처음에는 해당 객체에 HostUser를 넣어주지 못했기 때문에 발생했다 생각하여 시간이 오래 소모 되었다.