- 참조: 자바 ORM 표준 JPA 프로그래밍
- 경로 표현식
경로 표현식이라는것은 쉽게 얘기해 . 을 찍어 객체 그래프를 탐색하는것을 말한다.
String jpql = "select m.name, o, a "
+ "from Member m "
+ "inner join m.orders o "
+ "inner join m.address a "
+ " where m.name = :username";
List<Object[]> members = em.createQuery(jpql)
.setParameter("username", "User#1")
.getResultList();
위와 같은 JPQL 이 있다고 할 때 m.name, m.orders, m.address 모두 경로표현식을 이용한 것이라고 말할 수 있다. 경로 표현식은 종류에 따라 아래와 같이 나눈다.
- 상태 필드: 단순히 값을 저장하기 위한 필드(ex - m.name)
- 연관필드 - 단일 값: @ManyToOne, @OneToOne 대상이 엔티티 or 임베디드 타입 (ex - m.address)
- 연관필드 - 컬렉션 값: @ManyToMany, @OneToMany 대상 컬렉션 (ex - m.orders)
JPQL 경로탐색에는 특징이 있다. 연관필드의 단일 값은 묵시적으로 내부조인이 일어나며 계속해서 탐색이 가능하다. 하지만 컬렉션 값은 묵시적으로 내부조인이 일어나며 계속탐색이 불가능하다. 탐색을 하려면 FROM 절에서 alias 를 얻어야 탐색할 수 있다.
- 단일 값 연관 경로 탐색
String jpql = "select o.member "
+ "from Order o ";
List<Member> members = em.createQuery(jpql, Member.class)
.getResultList();
members.forEach(System.out::println);
위의 JPQL 을 살펴보면 Join 을 사용하지 않았다. 하지만 Order 가 참조하고 있는 Member 엔티티를 조회하기 때문에 묵시적인 Join 이 일어난다.
Hibernate:
/* select
o.member
from
Order o */ select
member1_.member_id as member_i1_4_,
member1_.insert_datetime as insert_d2_4_,
member1_.update_datetime as update_d3_4_,
member1_.city as city4_4_,
member1_.street as street5_4_,
member1_.zipcode as zipcode6_4_,
member1_.name as name7_4_ from
orders order0_
inner join
member member1_
on order0_.member_id=member1_.member_id
그래서 위와 같이 ORDERS 와 MEMBER 테이블의 외래키로 조인 SQL 이 수행된다.
- 컬렉션 값 연관 경로 탐색
JPQL 을 사용하면서 실수를 많이 하는 부분이 컬렉션 값에서 경로를 탐색하는것이라고 할 수 있다. "회원이 주문한 Order 의 ID 들을 전부 조회해야 한다." 라는 요구사항이 있을 때 아래와 같이 JPQL 을 작성할 수 있다.
String jpql = "select m.orders.id "
+ "from Member m ";
List<Object[]> orderIds = em.createQuery(jpql)
.getResultList();
위의 JPQL 은 실행하면 오류가 난다. JPQL 은 컬렉션 값의 경로 탐색을 허용하지 않기 때문이다. 만약 컬렉션 에서 계속 경로를 탐색하고 싶다면 Join 을 이용하여 alias 를 준 다음 참조를 해야 한다.
String jpql = "select o.id "
+ "from Member m "
+ " inner join m.orders o";
em.createQuery(jpql, Long.class)
.getResultList()
.forEach(System.out::println);
위의 코드를 실행하면 정상적으로 SQL 로 변환되어수행되고, Order 의 ID. 도 알 수 있다.
Hibernate:
/* select
o.id
from
Member m
inner join
m.orders o */ select
orders1_.order_id as col_0_0_
from
member member0_
inner join
orders orders1_
on member0_.member_id=orders1_.member_id
2
4
- 경로 표현식에 의한 조인
경로 탐색을 하면 의도여부와 상관없이 묵시적으로 Join 이 일어날 수 있다는것을 인지해야 한다. 그리고 Join 은 당연히 SQL 의 성능에 영향을 미친다.
묵시적으로 발생하는 Join 은 항상 INNER JOIN 이 발생한다. 만약 회원과 회원의 주문 목록을 조회하는 JPQL 이 있다고 가정하자. 물론 실제로 사용할 때에는 Member 의 엔티티를 조회한 후 Member 의 주문 목록을 가져오겠지만 예시를 위한것이니 참조하도록 하자.
String jpql = "select m, m.orders from Member m ";
List<Object[]> orders = em.createQuery(jpql)
.getResultList();
System.out.println("orders's size: " + orders.size());
회원은 아직 주문을 한 건도 하지 않았다고 할 때 회원정보는 가져오고 주문 목록은 빈값으로 가져오고 싶을 때 위와 같이 묵시적 Join 을 사용하면 결과를 얻어올 수 없다. ORDERS 테이블과 INNER JOIN 을 수행하기 때문이다.
Hibernate:
/* select
m,
m.orders
from
Member m */ select
member0_.member_id as member_i1_4_0_,
orders1_.order_id as order_id1_6_1_,
member0_.insert_datetime as insert_d2_4_0_,
member0_.update_datetime as update_d3_4_0_,
member0_.city as city4_4_0_,
member0_.street as street5_4_0_,
member0_.zipcode as zipcode6_4_0_,
member0_.name as name7_4_0_,
orders1_.insert_datetime as insert_d2_6_1_,
orders1_.update_datetime as update_d3_6_1_,
orders1_.delivery_id as delivery6_6_1_,
orders1_.member_id as member_i7_6_1_,
orders1_.order_date as order_da4_6_1_,
orders1_.status as status5_6_1_
from
member member0_
inner join
orders orders1_
on member0_.member_id=orders1_.member_id
orders's size: 0
묵시적 조인은 위와 같은 상황이 벌어질뿐더러 다른 사람이 JPQL을 볼때도 헷갈릴 수 있으므로 명시적으로 조인을 기술해주는것이 좋다.
'Framework and Tool > JPA' 카테고리의 다른 글
JPA - 객체지향 쿼리 언어 - JPQL 다형성 쿼리 (0) | 2021.08.03 |
---|---|
JPA - 객체지향 쿼리 언어 - JPQL 서브쿼리, 조건식 (0) | 2021.08.03 |
JPA - 객체지향 쿼리 언어 - JPQL Fetch Join (0) | 2021.07.31 |
JPA - 객체지향 쿼리 언어 - JPQL Join (0) | 2021.07.31 |
JPA - 객체지향 쿼리 언어 - JPQL Paging, 집합, 정렬 (0) | 2021.07.29 |
댓글