개발놀이터

JPQL 중급 본문

JPA/JPA

JPQL 중급

마늘냄새폴폴 2021. 8. 26. 09:12

*경로표현식

경로표현식이란? 
.(점)을 찍어 객체 그래프를 탐색하는 것

select m.name -> m.name = 상태 필드
from Member m 
join m.team t -> m.team = 단일 값 연관 필드
join m.orders o where -> m.orders = 컬렉션 값 연관 필드
t.name = 'teamA'

상태필드 : 단순히 값을 저장하기 위한 필드 = 경로 탐색의 끝, 탐색 X
연관필드 : 연관관계를 위한 필드
-단일 값 연관 필드 (@ManyToOne, @OneToOne, 대상이 엔티티) = 묵시적 내부조인 발생, 탐색 O
-컬렉션 값 연관 필드 (@OneToMany, @ManyToMany, 대상이 컬렉션) = 묵시적 내부조인 발생, 탐색 X

묵시적 조인, 명시적 조인
묵시적 조인 : 경로 표현식에 의해 묵시적으로 SQL조인이 발생 (내부 조인만 가능) select m.team from Member m
명시적 조인 : join 키워드 직접 사용 select m from Member m join m.team t

cf) 가급적 묵시적 조인 대신 명시적 조인을 사용하는게 좋다. 왜냐하면 조인은 SQL 튜닝에 중요 포인트이기 때문이다. 묵시적 조인이 일어나는 상황은 한눈에 파악하기 어렵다.


*페치 조인 (fetch) (실무에서 매우매우 중요함)
-SQL의 조인 종류가 아니다
-JPQL에서 성능 최적화를 위해 제공하는 기능
-연관된 엔티티나 컬렉션을 SQL한번에 함께 조회하는 기능
-join fetch 명령어 사용
-join fetch 조인경로

회원을 조회하면서 연관된 팀도 함께 조회하고 싶을 때
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 이렇게 해석해서 해당 데이터를 다 퍼다 나른다. 

컬렉션 페치 조인
일대다 관계에서 컬렉션 패치 조인을 하게 되면 데이터가 뻥튀기 된다. 예를들어서 회원이 1,2,3 있고 팀이 A,B가 있으면 회원 1,2는 팀A소속이고 회원3은 팀B소속일 때 팀에대하여 페치 조인이 일어나면(일대다 패치조인을 하게 되면) 실제로 팀에대한 데이터는 팀A, 팀B 두개지만 팀A에 소속된 회원이 1,2이기때문에 데이터가 총 세개가 나오게 된다. 

페치 조인과 distinct
SQL의 distinct는 중복된 결과를 제거하는 명령이다. 하지만 JPQL에서는 2가지 기능을 제공하는데 SQL에 distinct를 추가해서 중복된 결과를 제거하도록 하고 애플리케이션 레벨에서 엔티티의 중복을 제거해준다. 컬렉션 페치 조인을 하게 되면 데이터가 뻥튀기 되는 현상이 있는데 팀ID와 이름이 팀A로 같은 두개의 데이터가 존재하게 된다. 이때 distinct를 추가해주면 같은 엔티티의 중복을 제거해 준다.
select distinct t from Team t join fetch t.members


*페치조인의 특징과 한계
특징
-연관된 엔티티들을 SQL한번으로 조회 - 성능 최적화
-엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함 @OneToMany(fetch = fetchType.LAZY) = 글로벌 전략
-실무에서 글로벌 로딩 전략은 모두 지연로딩
-최적화가 필요한 곳은 페치 조인 적용

한계
-페치 조인 대상에는 엘리어스(별칭)를 줄 수 없다.
-둘 이상의 컬렉션은 페치 조인 할 수 없다.
-컬렉션을 페치 조인하면 페이징API를 사용할 수 없다. (일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능)
컬렉션을 페치 조인 했을 때 페이징 API를 사용하는 방법
>해당 매핑에 @BatchSize(size = 1000이하의 숫자) 이렇게 적어주면 된다. 정 컬렉션 페치 조인을 했을 때 페이징을 해야 한다면 고려해보자 웬만하면 쓰지 말자


*다형성 쿼리
-type
상속관계 매핑에서 조회 대상을 특정 자식으로 한정하고 싶을 때 type을 사용하면 된다. 
ex) Item 중에 Book, Movie를 조회해라
select i from Item i where type(i) in (Book, Movie)

-treat
상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용한다.
ex) 부모인 Item과 자식인 Book이 있다.
select i from Item i where treat(i as Book).auther = 'kim'


*벌크 연산
ex) 재고가 10개 미만인 모든 상품의 가격을 10%상승하려면?
JPA 변강 감지 기능 (더티 체킹)으로 실행하려면 너무 많은 SQL을 실행하게 된다.
1. 재고가 10개 미만인 상품을 리스트로 조회한다.
2. 상품의 엔티티의 가격을 10% 증가한다.
3. 트랜잭션 커밋 시점에 변경감지가 동작한다.
->변경된 데이터가 100건이라면 100번의 update SQL을 실행한다.

쿼리 한번으로 여러 테이블의 엔티티들 변경할 수 있는데 executeUpdate()를 뒤에 넣어주기만 하면 된다.
String qlString = "update Product p set p.price = p.price * 1.1 where p.stockAmount < :stockAmount";
int resultCount = em.createQuery(qlString).setParameter("stockAmount", 10).executeUpdate();

이런 벌크연산의 주의할 점이 있는데 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 날리기 때문에 영속성 컨텍스트의 업데이트는 이뤄지지 않는다. 따라서 저렇게 executeUpdate()를 실행하고 영속성 컨텍트스에 있는 데이터를 찍어보면 데이터베이스에 있는 값과 전혀 다른 값이 들어있는 것을 확인할 수 있다. 때문에 벌크 연산을코드 시작할 때(영속성 컨텍스트에 아무것도 없을 때) 실행하거나, 벌크 연산 실행 후 영송성 컨텍스트를 초기화 하면 된다. (em.clear())

'JPA > JPA' 카테고리의 다른 글

Spring Data JPA  (0) 2021.09.23
JPA를 활용한 (XToOne) Restful API설계  (0) 2021.09.13
JPQL 초급  (0) 2021.08.26
값 타입  (0) 2021.08.26
지연로딩과 즉시로딩 (프록시)  (0) 2021.08.24