JPQL 에는 경로 표현식이 3가지 존재한다.
상태 필드
단순히 값을 저장하기 위한 필드
경로 탐색의 끝, 더이상 탐색이 불가함
단일 값 연관 필드
연관관계를 위한 필드
@ManyToOne, @OneToOne 연관관계
대상이 엔티티임
묵시적 내부 조인이 발생하며, 탐색을 더 할 수 있음
컬렉션 값 연관 필드
마찬가지로 연관관계를 위한 필드
@OneToMany, @ManyToMany 연관관계
대상이 컬렉션임
묵시적 내부 조인이 발생하며, 탐색을 더이상 할 수 없음
FROM 절에서 조인을 명시적으로 사용하면 별칭을 통해 탐색할 수 있음
묵시적 내부조인?
JQPL에 JOIN으로 명시하지 않았으나 SQL로 바뀔 때 조인이 들어가는 것
코드와 실제 SQL이 다르므로 실수할 가능성이 있어 운영에서는 가급적 명시적으로 하는 것이 좋음
조인은 SQL 튜닝에서 중요한 부분이기에 명시하는 것이 좋음
페치 조인(fetch join)
JPQL에서 성능 최적화를 위해 제공하는 기능
연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
일반 조인은 SELECT에 연관된 객체를 넣지 않으면 영속하지 않지만, 페치 조인은 SELECT에 선언하지 않아도 명시한 객체들을 모두 영속한다.
join fetch ... 와 같이 사용
N+1 문제를 해결할 수 있음
일대다 관계에서 조인해 올 경우 SQL 결과를 그대로 가져오기 때문에 데이터 중복이 발생
DISTINCT 를 사용하여 데이터 중복을 제거할 수 있음
DISTINCT는 SQL에 distinct 를 추가하는 것 뿐만 아니라, 애플리케이션에서 엔티티의 중복을 제거하는 JPQL의 기능이 있음
페치 조인의 특징과 한계
페치 조인 대상에는 별칭을 줄 수 없다. (하이버네이트는 가능, 가급적 사용 X)
페치 조인은 연관된 모든 객체를 가져오기 위해 고안된 것
별칭을 통해 특정 데이터만 가져온다면 연관된 객체에 접근할 때, 연관관계는 맺었으나 존재하지 않는 엔티티가 존재할 수 있어, 운영에 어려워짐
둘 이상의 컬렉션은 페치 조인 할 수 없다. (가능은 하지만, 1:N 의 관계를 여러번 가져오려하면 의도대로 동작하지 않을 확률이 높다.)
컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
별칭과 같은 이유
하이버네이트에서는 가능하나 경고 로그를 남기고, 다 가져온 뒤 메모리에서 페이징 처리함
추가.
N+1 문제의 또 다른 해결법 - @BatchSize
@BatchSize를 1000이하의 적당히 큰 수 C으로 설정하게 되면, 지연 로딩에서 연관된 엔티티를 가져올 때, 연관되어있는 데이터 1개가 아니라 C개를 한 번에 가져와 1+ N/C 번만에 모든 데이터를 가져올 수 있게 됨
일대다 연관관계 매핑에 @BatchSize 를 넣거나, 글로벌 설정할 수 있음
다형성 쿼리
객체지향의 다형성을 직접 쿼리에서 활용하는 기능
... From Item i WHERE type(i) = Book 와 같이 하위 타입중 특정한 타입만 가져올 수도 있다.
... From Item i WHERE treat(i as Book).author = 'jinu' 와 같이 다운 캐스팅을 하여 가져오는 것도 가능하다.
엔티티 직접 사용
JPQL에서는 일반 SQL과는 달리 엔티티 전체를 쿼리에서 다룰 수 있다. ex) SELECT count(m) FROM Member m;
엔티티를 직접 사용할 경우 해당 엔티티의 기본 키 값으로 바뀌어 사용되게 된다.
파라미터에도 직접 엔티티를 넣어 사용할 수 있고, 기본 키 값으로 SQL이 전송된다.
ex)
query = SELECT m FROM Member m WHERE m = :member;
query.setPatameter("member", member1);
Named 쿼리
미리 정의해서 이름을 부여해두고 사용하는 JPQL
정적 쿼리
어노테이션, XML에 정의
애플리케이션 로딩 시점에 초기화 후 재사용 - 미리 JPQL을 SQL로 변환해놓고 캐싱하여 사용
애플리케이션 로딩 시점에 쿼리를 검증- 운영 시점에 발생할 에러를 미리 잡아줌 (컴파일 에러까진 아님)
XML에 정의할 경우 우선 순위로 사용되며, XML 파일만 바꾸어 배포하면 실행 쿼리가 바뀜
@NamedQuery 어노테이션으로 사용 (Spring Data JPA 에서 @Query가 내부적으로 Named 쿼리를 사용함)
벌크 연산
JPA에서 여러 엔티티를 한번에 변경할 때 사용
query.executeUpdate() 를 통해 사용
UPDATE, DELETE 쿼리를 사용 가능하며, 하이버네이트에서는 INSERT도 가능함
벌크 연산 주의
벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 날림
벌크 연산도 JPQL이기 때문에 플러시가 일어나지만, 영속성 컨텍스트는 그대로 남아있어 데이터베이스와 애플리케이션 메모리의 데이터가 다를 수 있음
벌크 연산만 먼저 실행하거나, 벌크 연산 수행 후 영속성 컨텍스트를 초기화하자. (Data JPA 에서 벌크 연산을 수행할 때 @Modifying 어노테이션을 사용하는데, 기본적으로 사용 후 영속성 컨텍스트를 초기화해준다.)