IT/디버깅

"query specified join fetching, but the owner of the fetched association was not present in the select list" 에러 원인과 해결

happy_life 2023. 10. 28. 16:19

queryDsl을 사용하던 중 query specified join fetching, but the owner of the fetched association was not present in the select list 에러가 발생하여 원인과 해결과정에 대해 정리하는 포스팅입니다.

 

 

1. error의 원인 코드

jpaQueryFactory
    .select(mainPayment.itemImage, mainPayment.itemName, mainPayment.subPayment.pointUse)
    .from(mainPayment)
    .join(mainPayment.subPayment, subPayment)
    .fetchJoin()
    .fetchOne();

위의 코드를 살펴보면 subPayment를 join 한 후 N+1 문제를 해결하기 위해 fetchJoin()을 사용하고 있습니다. 하지만 어째서인지 위와 같은 에러가 발생하였습니다.

 

 

 

2. error의 원인

fetch join을 사용하면서 특정 부분의 column만을 요청하는 것은  hibernate에게 부분적으로 initialized된 entity를 달라고 하는 것과 같다고 합니다. 이에 대해 hibernate는 이를 막고 있다고 합니다. 왜냐하면 나중에 추가로 필요한 부분이 있을 때 query를 또 날려야 하는지, 에러를 던져야하는지 등에 대해 혼란스러워지는 문제가 존재하기 때문입니다.

 

 

 

3. error의 해결

1. fetch join을 사용하지 않는 방안 - N+1의 문제를 해결하기 위함이었는데,  이는 query를 여러개 나가지 않게 하기라는 목적을 달성할 수 없습니다.

 

2. Projection 사용하기

 

2.1 projection class를 구현합니다.

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PaymentCancelProjection {

    private String itemImage;

    private String itemName;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate travelStartDate; // 여행 시작 날짜

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate travelEndDate; // 여행 종료 날짜

    private Integer pointUse; // 포인트 사용

    private Integer itemPayPrice; //
}

 

2.2 쿼리를 작성합니다.

PaymentCancelProjection result = jpaQueryFactory
                .select(Projections.constructor(PaymentCancelProjection.class,
                        mainPayment.itemImage,
                        mainPayment.itemName,
                        mainPayment.travelStartDate,
                        mainPayment.travelEndDate,
                        mainPayment.itemPayPrice,
                        subPayment.pointUse))
                .from(mainPayment)
                .join(mainPayment.subPayment, subPayment)
                .where(mainPayment.id.eq(mainPaymentId))
                .fetchOne();