경로 표현식
경로 표현식이란 .(점)을 찍어 객체 그래프를 탐색하는 것이다.
select m.username -> 상태 필드
from Member m
join m.team t -> 단일 값 연관 필드
join m.orders o -> 컬렉션 값 연관 필드
where t.name = '팀A'
경로 표현식 용어 정리
- 상태 필드 : 단순히 값을 저장하기 위한 필드
- 연관 필드 : 연관 관계를 위한 필드
- 단일 값 연관 필드 : @ManyToOne, @OneToOne, 대상이 엔티티
- 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션
경로 표현식 특징
- 상태 필드 : 경로 탐색의 끝이다. 더는 탐색할 수 없다
JPQL: select m.username, m.age from Member m
SQL: select m.username, m.age from Member m
- 단일 값 연관 경로 : 묵시적 내부 조인(inner join)이 발생한다. 단일 값 연관경로는 계속 탐색이 가능하다
JPQL: select o.member from Order o
SQL: select m.*
from Orders o
inner join Member m on o.member_id = m.id
- 컬렉션 값 연관 경로 : 묵시적 내부 조인이 발생한다. 더는 탐색이 불가능하다. 단, FROM 절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색할 수 있다.
명시적 조인, 묵시적 조인
- 명시적 조인 : join 키워드를 직접 사용
- 묵시적 조인 : 경로 표현식에 의해 묵시적으로 SQL 조인이 발생 (내부 조인만 가능)
경로 표현식 예시
select o.member.team from Order o -> 성공
select t.members from Team -> 성공
select t.members.username from Team t -> 실패
select m.username from Team t join t.members m -> 성공
경로 탐색을 사용한 묵시적 조인 시 주의 사항
- 항상 내부 조인이다
- 컬렉션은 경로 탐색의 끝이다. 명시적 조인을 통해 별칭을 얻어야 한다
- 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM 절에 영향을 준다
패치 조인(FETCH JOIN)
패치 조인은 SQL에서 이야기하는 조인의 종류는 아니고 JPQL에서 성능 최적화를 위해 제공하는 기능이다. 이것은 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능인데 join fetch 명령어로 사용할 수 있다.
엔티티 패치 조인
회원을 조회하면서 연관된 팀도 함께 조회한다.
[JPQL]
select m from Member m join fetch m.team
[SQL]
SELECT M.*, T.* FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID=T.ID
컬렉션 패치 조인
일대다 관계인 컬렉션을 패치 조인해보자
[JPQL]
select t
from Team t join fetch t.members
where t.name = ‘팀A'
[SQL]
SELECT T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'
패치 조인과 DISTINCT
SQL의 DISTINCT는 중복된 결과를 제거하는 명령어다. JPQL의 DISTINCT 명령어는 SQL에 DISTINCT를 추가하는 것은 물론이고 애플리케이션에서 한 번 더 중복을 제거한다.
컬렉션 패치 조인은 팀 A가 중복으로 조회된다. 다음처럼 DISTINCT를 추가해보자.
select distinct t
from Team t join fetch t.members
where t.name = ‘팀A’
SQL에 DISTINCT를 추가하지만 데이터가 다르므로 SQL 결과에서 중복 제거가 실패한다. 그 이후, DISTINCT가 추가로 애플리케이션에서 중복 제거를 시도한다. 이 때 같은 식별자를 가진 Team 엔티티를 제거하게 된다. 따라서 중복인 팀 A는 하나만 조회된다.
패치 조인과 일반 조인의 차이
- 일반 조인 실행 시 연관된 엔티티를 함께 조회하지 않는다.
- JPQL은 결과를 반환할 떄 연관관계를 고려하지 않는다. 단지 SELECT 절에 지정한 엔티티만 조회할 뿐이다. 여기서는 팀 엔티티만 조회하고 회원 엔티티는 조회하지 않는다
- 패치 조인을 사용할 때만 연관된 엔티티도 함께 조회한다 (즉시 로딩)
- 패치 조인은 객체 그래프를 SQL 한번에 조회하는 개념이다.
패치 조인의 특징과 한계
- 패치 조인 대상에는 별칭을 줄 수 없다. (하이버네이트는 가능하지만, 가급적 사용하지 말기)
- 둘 이상의 컬렉션은 패치 조인 할 수 없다
- 컬렉션을 패치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다
- 일대일, 다대일 같은 단일 값 연관 필드들은 패치 조인해도 페이징 가능
- 하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험) - 연관된 엔티티들은 SQL 한 번으로 조회 (성능 최적화)
- 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함
- 실무에서 글로벌 로딩 전략은 모두 지연 로딩, 최적화가 필요한 곳은 패치 조인 적용
패치 조인 정리
- 모든 것을 패치 조인으로 해결 할 수는 없음
- 패치 조인은 객체 그래프를 유지할 때 사용하면 효과적
- 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면, 패치 조인보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적
다형성 쿼리
조회 대상을 특정 자식으로 한정한다
예를 들어, Item 중에 Book, Movie를 조회해라
[JPQL]
select i from Item i where type(i) IN (Book, Movie)
[SQL]
select i from i where i.DTYPE in (‘B’, ‘M’)
TREAT
- 자바의 타입 캐스팅과 유사
- 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용
- FROM, WHERE, SELECT 사용
엔티티 직접 사용
JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용한다.
[JPQL]
select count(m.id) from Member m //엔티티의 아이디를 사용
select count(m) from Member m //엔티티를 직접 사용
[SQL](JPQL 둘다 같은 다음 SQL 실행)
select count(m.id) as cnt from Member m
Named 쿼리
정적 쿼리
- 미리 정의해서 이름을 부여해두고 사용하는 JPQL
- 정적 쿼리
- 어노테이션, XML에 정의
- 애플리케이션 로딩 시점에 초기화 후 재사용
- 애플리케이션 로딩 시점에 쿼리를 검증
'백엔드 > SPRING' 카테고리의 다른 글
[스프링 핵심 원리] 2. 스프링 컨테이너와 스프링 빈 (0) | 2025.03.19 |
---|---|
[스프링 핵심 원리] 1. 객체 지향 설계와 스프링 (0) | 2025.03.17 |
[JPA 프로그래밍] 9. 객체 지향 쿼리 언어1 - 기본 문법 (0) | 2025.02.15 |
[JPA 프로그래밍] 8. 값 타입 (0) | 2025.02.07 |
[JPA 프로그래밍] 7. 프록시와 연관관계 관리 (0) | 2025.02.06 |