JPA - 객체지향 쿼리언어(4)

DevSeoRex·2022년 12월 9일
0
post-thumbnail

경로 표현식

  • 경로 표현식(Path Expression)이란 .(점)을 찍어 객체 그래프를 탐색하는 것이다.
// 경로 표현식을 사용한 JPQL 예제
select m.username     ->상태 필드
from member
	join m.team t	  -> 단일 값 연관필드
    join m.orders o	  -> 컬렉션 값 연관필드
where t.name = '팀A'

위의 JPQL에서 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 username;	// 상태 필드
    private Integer age;		// 상태 핃드
    
    @ManyToOne(...)
    private Team team;			// 연관 필드(단일 값 연관필드)
    
    @OneToMany(...)
    private List<Order> orders;	// 연관 필드(컬렉션 값 연관필드)
}

위의 엔티티를 보았을때 경로 표현식을 사용한다면 3가지 경로 표현식을 사용할 수 있다.

  • 상태 필드 : t.username, t.age
  • 단일 값 연관 필드 : m.team
  • 컬렉션 값 연관 필드 : m.orders

경로 표현식의 특징

3가지 경로 표현식을 사용해서 경로 탐색을 하려면 3가지 경로에 따라 특징이 나뉜다.

  • 상태 필드 경로 : 경로 탐색의 끝이다. 더는 탐색이 불가능하다.
  • 단일 값 연관 경로 : 묵시적으로 내부 조인이 일어난다. 단일 값 연관 경로는 계속 탐색할 수 있다.
  • 컬렉션 값 연관 경로 : 묵시적으로 내부 조인이 일어난다. 더는 탐색할 수 없다. 단 FROM 절에서 조인을 통해 별칭(Alias)을 얻으면 별칭으로 탐색이 가능하다.

묵시적 조인

단일 값 연관 필드로 경로 탐색을 하면 SQL에서 내부조인이 일어난다.
이것을 묵시적 조인이라고 하며, 묵시적 조인은 모두 내부 조인이다. 외부 조인을 사용해야 한다면 명시적으로 JOIN 키워드를 사용해야 한다.

  • 명시적 조인 : JOIN을 직접 적어주는 것을 말한다.
SELECT m FROM Member m JOIN m.team t
  • 묵시적 조인 : 경로 표현식에 의해 묵시적으로 조인이 일어나는 것을 말한다. 내부 조인(INNER JOIN)만 가능하다.
SELECT m FROM Member m

💡 임베디드 타입에 접근하는 것도 단일 값 연관 경로 탐색이지만, 이미 다른 엔티티에 포함되어 있는 값이므로 조인이 발생하지 않는다.

컬렉션 값 연관 경로 탐색

JPQL에서는 컬렉션 값에서 경로 탐색을 할 수 없다.
컬렉션 값에서 꼭 경로 탐색을 해야하는 케이스가 있다면 FROM절에서 조인을 통해 별칭을 얻어야 한다.

// 컬렉션 값에서의 연관 경로 탐색
select t.members from Team t			// 성공
select t.members.username from Team t	// 실패

// 별칭을 얻어 컬렉션 값에서 경로 탐색
select m.username from Team t join t.members m	// 성공

💡 참고로 컬렉션은 컬렉션의 크기를 구할 수 있는 size라는 기능을 사용할 수 있다. size를 사용하면 COUNT 함수를 사용하는 SQL로 적절히 변환된다.

경로 탐색을 사용한 묵시적 조인 시 주의할 점

경로 탐색을 사용하면 묵시적 조인이 발생한다.
이로 인해 SQL에서 내부 조인이 일어날 수 있는데 이때 주의할 점이 몇개 있다.

  • 항상 내부 조인이다. 외부 조인을 하려면 직접 JOIN 키워드를 사용해야 한다.
  • 컬렉션은 경로 탐색이 불가능하다. 컬렉션에서 경로 탐색을 하려면 명시적으로 조인을 해서 별칭을 얻고, 얻은 별칭을 통해 접근해야 한다.
  • 경로 탐색은 주로 SELECT, WHERE 에서 사용하지만, 묵시적 조인으로 인해 SQL의 FROM 에 영향을 준다.

💡 성능이 중요하다면 분석하기 쉽도록 묵시적 조인보다는 명시적 조인을 사용하는 것이 좋다.

서브 쿼리 - SubQuery

JPQL도 SQL처럼 서브 쿼리를 지원한다. JPQL에서는 서브 쿼리에 몇가지 제약이 있다.

  • JPQL에서 JPA 표준 명세의 경우 서브 쿼리는 WHERE, HAVING 절에서만 사용할 수 있다.
  • JPA 표준 명세의 경우에는 SELECT FROM 절에서는 사용이 불가능하다.
  • 하이버네이트의 HQL은 SELECT 절의 서브 쿼리도 허용한다. FROM 절의 서브 쿼리는 지원하지 않는다.

💡 일부 JPA 구현체는 FROM 절의 서브 쿼리도 지원한다.

// JPQL 서브 쿼리 사용 예제

// 나이가 평균보다 많은 회원을 조회
select m from Member m
where m.age > (select avg(m2.age) from Member m2)

// 한 건이라도 주문한 고객을 조회
select m from Member m
where (select count(o) from Order o where m = o.member) > 0

// 한 건이라도 주문한 고객을 컬렉션 값 연관 필드의 size 기능으로 조회
select m from Member m
where m.orders.size > 0

서브 쿼리 함수

서브 쿼리는 아래의 함수들과 같이 사용이 가능하다.

  • [ NOT ] EXISTS (subquery)
  • { ALL | ANY | SOME } (subquery)
  • [ NOT ] IN (subquery)

EXISTS

  • 설명 : 서브쿼리에 결과가 존재하면 참이다. NOT은 존재하지 않으면 참이다.
  • 예시 : 팀A 소속인 회원을 조회
select m from Member m
where exists (select m from m.team t where t.name = '팀A')

{ALL | ANY | SOME}

  • 설명 : 비교 연산자와 같이 사용한다. { '=' | '>' | '>=' | '<' | '<=' | '<>' }
    • ALL : 조건을 모두 만족하면 참이다.
    • ANY & SOME : 둘은 같은 의미다. 조건을 하나라도 만족하면 참이다.
  • 예시 1 : 전체 상품 각각의 재고보다 주문량이 많은 주문들을 조회
select o from Order o
where o.orderAmount > ALL (select p.stockAmount from product p)
  • 예시 2 : 어떤 팀이든 팀에 소속된 회원을 조회
select m from Member m
where m.team = ANY (select t from Team t)

IN

  • 설명 : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참이다. IN은 서브쿼리가 아닌 곳에서도 사용한다.
  • 예시 : 20세 이상을 보유한 팀을 조회
select t from Team t
where t IN (select t2 From Team t2 JOIN t2.members m2 where m2.age >= 20)

출처 : 자바 ORM 표준 JPA 프로그래밍(에이콘, 김영한 저)

0개의 댓글