오류발생과 해결

SemanticException 해결 과정 (부제: QueryDsl과 Alias(별칭))

지팡구 2025. 8. 31. 22:57
return jpaQueryFactory.select( reservation ) .from(reservation) .join(reservation.reservationMembers)

문제 탐색 및 배경 설명

1:N 관계의 테이블에서 QueryDsl을 이용해 부모, 자식 테이블을 함께 Join 하는 상황에서 예기치 못한 문제 발생

 

[org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.query.SemanticException: Could not interpret path expression 'reservationMember.memberType']

 

해당 문제에 관한 메타 코드는 링크 참고

 

where 절에 사용하는 reservationMember가 조인 시 별칭(Alias)로 선언되지 않아 QueuyDsl이 경로를 해석하지 못하는 상황

return jpaQueryFactory.select( reservation )
	.from(reservation) 
    .join(reservation.reservationMembers) 
    .where(reservation.id.eq(reservationId).and(reservationMember.memberType.eq( ReservationMemberType.DRV_1ST))) .fetchOne();

문제 해결

 

위의 쿼리를 확인해보면 Join에서 reservation.reservationMembers에서 별도의 별칭을 정하지 않으면 where절 입장에서도 조건 시 reservationMember를 참조할 수가 없을 것이다 (어디로 참조해야하는지 모르기에)

그래서 별칭 지정을 통해 어떤 테이블을 참조할 것인 알려줘야하는데 이를 별칭을 통해 부여할 수 있음

 

return jpaQueryFactory
        .select(reservation)
        .from(reservation)
        .join(reservation.reservationMembers, reservationMember) // 별칭 지정
        .where(
            reservation.id.eq(reservationId)
                .and(reservationMember.memberType.eq(ReservationMemberType.DRV_1ST))
        )
        .fetchOne();

 

종합적으로 QueryDsl에서 조인된 엔티티를 where 절에서 사용하려면 그 엔티티를 참조할 경로를 명시해줘야함. 

위의 코드에서 이러한 경로를 명시해주지 않았기에 SemanticException발생

 

별칭을 통해 select  / where 등 해당 엔티티의 필드를 안전하게 참조 가능하다는 점

해당 문제를 해결하며 얻게된 추가 정보

 

1. 선언한 별칭을 일관되게 사용하라

별칭 선언 이후 별칭을 일관되게 사용하지 않으면 불필요한 조인 혹은 크로스 조인 발생 가능

2. 다중 조인시 주의할 것

	jpaQueryFactory
        .select(reservation)
        .from(reservation)
        .join(reservation.reservationMembers)
        .join(reservation.reservationMembers.user)

샘플 코드에서 reservationMembers를 조인 후, 타겟 엔티티에 user를 한번 더 조인하게 되면 이미 조인된 reservationMembers를 사용하지 않고 새로운 reservationMembers를 조인해버림

결국 reservationMembers가 두 번 조인되고 테이블 혹은 데이터의 규모가 커질수록 성능 누수 발생

 

아래와 같이 수정할 것

	jpaQueryFactory
        .select(reservation)
        .from(reservation)
        .join(reservation.reservationMembers rm)
        .join(rm.user, user)