객체지향쿼리언어 (JPQL) 심화 문법 (fetch join, named query, 벌크연산)

경로 표현식

: .을 찍어 객체 그래프를 탐색하는 거야

1
2
3
4
5
select m.username -> 상태 필드
from Member m
join m.team t -> 단일 값 연관 필드
join m.orders o -> 컬렉션 값 연관 필드
where t.name = '팀A

경로 표현식 용어

  • 상태 필드 : 단순히 값을 저장하기 위한 필드 ( m.username 같은거 )
  • 연관 필드 : 연관관계를 위한 필드
  • 단일 값 연관 필드 : 대상이 엔티티 ( m.team 같은거 ) @ManyToOne, @OneToOne 연관관계가 해당되겠지
  • 컬렉션 값 연관 필드 : 대상이 컬렉션 ( m.orders ) @OneToMany, @ManyToMany가 해당되겠지

경로 표현식 특징

  • 상태필드는 경로 탐색의 끝이야!! 당연히 더 타고 들어갈 것이 없겠지
  • 단일 값 연관 경로는 묵시적 내부 조인이 발생하고 탐색이 가능해. 탐색이 가능하단 것은 select m.team.name from Member m 이런 쿼리가 가능하단 거야!
  • 컬렉션 값 연관경로도 묵시적 내부 조인이 발생하나 위 단일 값 연관 경로 처럼 탐색이 가능하진 않아!! but 방법이 있어. from 절에서 명시적 조인을 통해 별칭을 얻으면 그 별칭을 통해서 다시 탐색이 들어가면서 가능하게 돼!! ( select m from Team t join t.members m 이런 식으로 )

👏👏주의사항👏👏

실무에서는 묵시적 조인을 사용할 일은 만들면 안돼!! 명시적 조인을 사용하는게 좋아!! 어떤 쿼리가 나갈지 예상이 안될 뿐더러 조인은 쿼리 튜닝의 핵심 대상이니까!!


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

  • 항상 내부 조인이 발생해
  • 컬렉션은 경로 탐색의 끝이야!! 만약 컬렉션에서 더 탐색하고 싶다면 명시적 조인을 통해 별칭을 얻어와 그 별칭을 통해 추가 탐색을 하면 돼!!
  • 경로 탐색은 주로 뭐 select, where 절에 쓰겠지만 실제 영향은 from 즉 join 절에 영향을 주게 돼
  • 그렇기에 가급적 묵시적 조인 대신에 명시적 조인을 사용하자! (조인은 sql 튜닝에 중요 포인트야! 그리고 묵시적 조인은 조인 상황을 한눈에 파악하기 어렵자나)



패치 조인(fetch join)

패치 조인은 매우매우 중요한 녀석이야!! 실무의 핵심이라고 볼 수 있지!!


패치 조인이란

  • SQL에서 제공하는 JOIN이 아니야
  • JPQL에서 성능 최적화를 위해 제공하는 기능이야
  • 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능이라는 게 핵심이야!
  • join fetch 명령어를 통해 가능해
  • 예를들어, 회원과 멤버가 연관관계를 가지고 있는 상태라면 회원을 조회하면서 연관된 팀도 함께 조회하는거지! SQL 한번에 말야!!
1
2
3
4
5
//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

이런식으로 되는거야
( 즉시로딩과 생긴 게 굉장히 유사하지 )


컬렉션 페치 조인

  • 컬렉션 페치조인을 사용할 경우 그 결과가 생각했던 것 보다 많아질 수 있어.
  • 예를들어, 팀A에 회원1, 회원2 가 속해있다고 가정해보자! 거기서 select t from Team t join fetch t.members where t.name = ‘팀A’ 이런 쿼리를 날린다면 그 쿼리의 반환 값인 List teams에는 2개의 데이터를 받아오게 되는거지!!
  • 쉽게 생각하면 실제 변환된 sql 쿼리를 날렸을 때 나오는 레코드 수 만큼 생긴다고 생각하면 쉬울꺼야!!
  • 이럴 때는 DISTINCT를 쓰면 돼!! JPQL의 DISTINCT 기능은 크게 2가지야. 먼저 변환된 SQL에 DISTINCT를 붙여주는거!! 그 다음은 어플리케이션 레벨에서 엔티티 중복을 제거해줘!!
  • select distinct t from Team t join fetch t.members where t.name = ‘팀A’ 이렇게 쿼리를 날릴 경우 그 결과로 List가 아닌 Team 하나만을 받아올 수 있게 되는거지!!

페치 조인과 일반 조인의 차이

  • 일반 조인은 실행시 연관된 엔티티를 함께 조회하지 않아!!
  • 즉, 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회하는거야!! 마치 즉시로딩과 같지!!

페치 조인의 특징 및 한계

  • 페치 조인 대상에는 별칭을 줄 수 없어!! (하이버네이트에서 가능하기에 쓸 수야 있겠지만 쓰지 않기를…. 바란드아..)
  • 둘 이상의 컬렉션은 페치조인 할 수 없어!!
  • 컬렉션을 페치 조인하면 페이징 api (setFirstResult, setMaxResults)를 사용할 수 없어!!
  • 결국은 페치조인은 sql 한번으로 연관된 엔티티들을 들고오게 하면서 성능 최적화를 이끄는거지!!
  • 그래서 실무에서는 글로벌 로딩 전략(fetch = FetchType.Lazy 같은)은 모두 지연로딩으로 한 상태에서 최적화가 필요한 곳에 페치조인을 적용하면 돼!! 그럼 해당 페치 조인 쿼리만 즉시로딩 처럼 들고오게 되는 거야!!
  • 만약 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면, 페치 조인 보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 변환하는 것이 효과적이래!



JPQL 다형성 쿼리


Type

: 조회 대상을 특정 자식으로 한정할 때 쓰는 놈!!

1
2
3
4
5
6
//item 중에 book, movie를 조회하라!!
select i from item i where type(i) IN (BOOK,MOVIE)

//이런식으로 쓸 수 있어!! 결국은 저 쿼리가
select i from i where i.DTYPE in ('B','M')
//이전에 배웠던 DTYPE 이용한 쿼리로 바껴서 날라가

TREAT

  • 자바의 타입 캐스팅 같애
  • 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용해
1
2
3
//jpql
select i from Item i where treat(i a Book).auther = 'kim'
//이렇게 쓰는겨



JPQL Named 쿼리

  • 미리 정의해서 이름을 부여해두고 사용하는 jpql이야!!
  • 그러니 당연히 정적이겠지!!
  • 어노테이션, XML에 정의할 수 있어!! 보통 XML에 정의하는 거 같애. Entity에 덕지덕지 내용이 많이 붙으면 보기 싫으닝께 (나중에 사용할 때 xml로 하는 방법 참고하자!)
  • 어플리케이션 로딩 시점에 초기화 후 재사용해!! 좋은 점 중에 하나지!! 모든 jpql은 결국 sql로 변환하는데 이 비용을 줄일 수 있는거지
  • 어플리케이션 로딩 시점에 쿼리를 검증할 수 있어!! 가장 중요한 부분이지!!
  • xml이 우선권을 가지고 xml을 하면 좋은 점 중 하나가 운영 환경에 따라 다른 xml을 배포할 수 있어.



JQPL 벌크 연산

  • 쿼리 한 번으로 여러 테이블 로우(엔티티) 변경!! ( 일반 더티체킹으로 변경된 사항을 반영한다면 그 쿼리 수는 어마어마해…)
  • executeUpdate()의 결과는 영향받은 엔티티 수 반환해줘
  • update, delete 지원해줘!!
  • 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 날리는거야!! 그렇기에 문제가 생길 수 있겠지???

벌크 연산 주의사항

: 위에서 말했던 것 처럼 영속성 컨텍스트를 무시하기에 생각했던 것과 다른 결과를 이후에 야기할 수 있다.

  • 벌크 연산을 먼저 실행한다!!
  • 벌크 연산 수행 후 영속성 컨텍스트를 초기화 시킨다!








https://www.inflearn.com/course/ORM-JPA-Basic