39. 객체지향 쿼리 언어(6)
39. 객체지향 쿼리 언어(6)
2. JPQL
8. 경로 표현식
지금까지 JPQL 조인을 알아보았다. 이번에는 JPQL에서 사용하는 경로 표현식(Path Expression)을 알아보고 경로 표현식을 통한 묵시적 조인도 알아보자.
경로 표현식이라는 것은 쉽게 이야기해서 .(점)을 찍어 객체 그래프를 탐색하는 것이다. 다음 JPQL을 보자.
select m.username
from Member m
join m.team t
join m.orders o
where t.name = '팀A'
여기서 m.username, m.team, m.orders, t.name이 모두 경로 표현식을 사용한 예다.
경로 표현식의 용어 정리
경로 표현식을 이해하려면 우선 다음 용어들을 알아야 한다.
- 상태 필드(state field) : 단순히 값을 저장하기 위한 필드(필드 or 프로퍼티)
- 연관 필드(association field) : 연관관계를 위한 필드, 임베디드 타입 포함(필드 or 프로퍼티)
- 단일 값 연관 필드 : @ManyToOne, @OneToOne, 대상이 엔티티
- 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션
상태 필드는 단순히 값을 저장하는 필드이고 연관 필드는 객체 사이의 연관관계를 맺기 위해 사용하는 필드다. 아래 예제로 상태 필드와 연관 필드를 알아보자.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "name")
private String usrename; // 상태 필드
private Integer age; // 상태 필드
@ManyToOne(..)
private Team team; // 연관 필드(단일 값 연관 필드)
@OneToMany(..)
private List<Order> orders; // 연관 필드(컬렉션 값 연관 필드)
정리하면 다음 3가지 경로 표현식이 있다.
- 상태 필드 : 예 t.username, t.age
- 단일 값 연관 필드 : 예 m.team
- 컬렉션 값 연관 필드 : 예 m.orders
경로 표현식과 특징
JPQL에서 경로 표현식을 사용해서 경로 탐색을 하려면 다음 3가지 경로에 따라 어떤 특징이 있는지 이해해야 한다.
- 상태 필드 경로 : 경로 탐색의 끝이다. 더는 탐색할 수 없다.
- 단일 값 연관 경로 : 묵시적으로 내부 조인이 일어난다. 단일 값 연관 경로는 계속 탐색할 수 있다.
- 컬렉션 값 연관 경로 : 묵시적으로 내부 조인이 일어난다. 더는 탐색할 수 없다. 단 FROM 절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색할 수 있다.
예제를 통해 경로 탐색을 하나씩 알아보자.
상태 필드 경로 탐색
다음 JPQL의 m.username, m.age는 상태 필드 경로 탐색이다.
select m.username, m.age from Member m
이 JPQL을 실행한 결과 SQL은 다음과 같다.
select m.name, m.age
from Member m
상태 필드 경로 탐색은 이해하는 데 어려움이 없을 것이다.
단일 값 연관 경로 탐색
다음 JPQL을 보자.
select o.member from Order o
이 JPQL을 실행한 결과 SQL은 다음과 같다.
select m.*
from Orders o
inner join Member m on o.member_id=m.id
JPQL을 보면 o.member를 통해 주문에서 회원으로 단일 값 연관 필드로 경로 탐색을 했다. 단일 값 연관 필드로 경로 탐색을 하면 SQL에서 내부 조인이 일어나는 데 이것을 묵시적 조인이라 한다. 참고로 묵시적 조인은 모두 내부 조인이다. 외부 조인은 명시적으로 JOIN 키워드를 사용해야 한다.
- 명시적 조인 : JOIN을 직접 적어주는 것
예 SELECT m FROM Member m JOIN m.team t
- 묵시적 조인 : 경로 표현식에 의해 묵시적으로 조인이 일어나는 것, 내부 조인 INNER JOIN만 할 수 있다.
SELECT m.team FROM Member m
이번에는 복잡한 예제를 보자. 다음에 나오는 그림을 보면서 다음 예제의 JPQL을 보자.
select o.member.team
from Order o
where o.product.name = 'productA' and o.address.city = 'JINJU'
주문 중에 상품명이 'productA'고 배송지가 'JINJU'인 회원이 소속된 팀을 조회한다. 실제 내부 조인이 몇 번 일어날지 생각해보자.
select t.*
from Orders o
inner join Member m on o.member_id=m.id
inner join Team t on m.team_id=t.id
inner join Product p on o.product_id=p.id
where p.name='productA' and o.city='JINJU'
위의 예제의 실행된 SQL을 보면 총 3번의 조인이 발생했다. 참고로 o.address처럼 임베디드 타입에 접근하는 것도 단일 값 연관 경로 탐색이지만 주문 테이블에 이미 포함되어 있으므로 조인이 발생하지 않는다.
컬렉션 값 연관 경로 탐색
JPQL을 다루면서 많이 하는 실수 중 하나는 컬렉션 값에서 경로 탐색을 시도하는 것이다.
select t.members from Team t // 성공
select t.members.username from Team t // 실패
t.members처럼 컬렉션까지는 경로 탐색이 가능하다. 하지만 t.members.username처럼 컬렉션에서 경로 탐색을 시작하는 것은 허락하지 않는다. 만약 컬렉션에서 경로 탐색을 하고 싶으면 다음 코드처럼조인을 사용해서 새로운 별칭을 획득해야 한다.
select m.username from Team t join t.members m
join t.members m으로 컬렉션에 새로운 별칭을 얻었다. 이제 별칭 m부터다시 경로 탐색을 할 수 있다.
참고로 컬렉션은 컬렉션의 크기를 구할 수 있는 size라는 특별한 기능을 사용할 수 있다. size를 사용하면 COUNT 함수를 사용하는 SQL로 적절히 변환된다.
select t.members.size from Team t
경로 탐색을 사용한 묵시적 조인 시 주의사항
경로 탐색을 사용하면 묵시적 조인이 발생해서 SQL에서 내부 조인이 일어날 수 있다. 이때 주의사항은 다음과 같다.
- 항상 내부 조인이다.
- 컬렉션은 경로 탐색의 끝이다. 컬렉션에서 경로 탐색을 하려면 명시적으로 조인해서 별칭을 얻어야 한다.
- 경로 탐색은 주로 SELECT, WHERE(다른 곳에서도 사용됨)에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM 절에 영향을 준다.
조인이 성능상 차지하는 부분은 아주 크다. 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다는 단점이 있다. 따라서 단순하고 성능에 이슈가 없으면 크게 문제가 안 되지만 성능이 중요하면 분석하기 쉽도록 묵시적 조인보다는 명시적 조인을 사용하자.
참고
- 자바 ORM 표준 JPA 프로그래밍
Author And Source
이 문제에 관하여(39. 객체지향 쿼리 언어(6)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jsj3282/39.-객체지향-쿼리-언어6저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)