728x90
!! 본 글은 실무에서 발생한 이슈를 바탕으로 작성되었으나, 회사 보안으로 도메인 구조 및 코드 일부를 재구성하여 설명 !!
1. 발생한 에러 로그
org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.query.SemanticException:
Query specified join fetching, but the owner of the fetched association
was not present in the select list
[SqmSingularJoin(ParentEntity(parent).childEntity(child).grandChildEntity(grandChild) : grandChild)]
-> QueryDSL에서 fetchJoin을 사용했지만 fetch의 "주인(owner)"이 select 절에 없다는 뜻
2. 일반화된 도메인 구조
ParentEntity
- - childEntity (ManyToOne)
- - grandChildEntity (ManyToOne)
즉, 연관관계가 2단계로 연결된 구조.
3. fetchJoin을 사용한 이유
실제 프로젝트에서 fetchJoin을 사용했던 이유는, 서비스 계층에서 연관된 childEntity가 실제로 필요했고 지연 로딩(LAZY) 상태에서 이를 접근할 경우 N+1 문제가 발생할 가능성이 있었기 때문이다. 따라서, "어차피 child를 join 해야 한다면, fetchJoin으로 같이 가져와서 N+1을 예방하자.”는 판단하에 fetchJoin을 사용했다.
4. 문제를 일으킨 QueryDSL 코드
.select(grandChildEntity)
.from(parent)
.join(parent.childEntity, child).fetchJoin()
.join(child.grandChildEntity, grandChild).fetchJoin()
.where(parent.id.eq(id))
.fetchOne();
fetchJoin은 내부적으로 다음 의미를 가진다.
“parent를 조회하면서, child와 grandChild도 함께 영속성 컨텍스트에 로딩해줘.”
즉, fetchJoin은 항상 기준 엔티티(주인) 를 중심으로 동작한다.
그런데 위 코드에서는 parent를 select하지 않았다.
Hibernate 입장에서는 이렇게 보인다.
- parent 기준으로 fetchJoin을 하라고 했는데 select 절에는 parent가 없다. 엔티티 그래프를 구성할 기준이 없다!!!
그래서 Hibernate는 쿼리의 의미가 충돌한다고 판단하고 SemanticException을 발생시킨다.
5. 해결 방법 : select 에 fetchJoin 주인 포함
.select(parent)
.from(parent)
.join(parent.childEntity, child).fetchJoin()
.join(child.grandChildEntity, grandChild).fetchJoin()
.where(parent.id.eq(id))
.fetchOne();
fetchJoin을 사용할 때는 반드시 그 연관관계의 기준 엔티티를 select해야 한다. 그렇지 않으면 Hibernate는 의미적으로 잘못된 쿼리로 판단하고 예외를 발생시킨다.
728x90
'Programming > Spring' 카테고리의 다른 글
| [Spring][오류 노트] DataIntegrityViolationException 오류 발생 (Feat. @Transactional 누락) (0) | 2025.05.09 |
|---|