11. 객체지향 쿼리 언어2 - 중급
#
JPQL 경로 표현식- 상태 필드: 단순히 값을 저장하기 위한 필드
- 연관 필드
- 단일 값 연관 필드
- *ToOne, 타겟 대상 엔티티가 하나
- 컬렉션 값 연관 필드
- *ToMany, 타겟 대상이 컬렉션
- 단일 값 연관 필드
#
경로 표현식묵시적 조인은 쓰지말자
- 상태필드: 경로 탐색의 끝, 탐색 X (. 으로 더 뻣어나갈 수 없음을 의미)
m.username
에서.
을 통해 더 뻣어 나갈 수 없음
- 단일 값 연관 경로: 묵시적 내부 조인 (inner join) 발생, 탐색 O
select m.team from Member m
select m.team.name from Member m
탐색 가능- 왠만하면 묵시적으로 안짜도록 주의해야함 (성능에 민감)
- 컬렉션 값 연관 경로: 묵시적 내부 조인 발생, 탐색 X
select t.members from Team t
(members 가 ManyToOne 으로 List)select t.members.size from Team t
이런 탐색 불가select m.size from Team t join t.members m
이렇게 별칭으로 가능
#
실무적 조언- 가급적 묵시적 조인 대신에 명시적 조인 사용
- 조인은 SQL 튜닝에 중요 포인트
- 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다는게 문제
#
페치 조인 ⭐️- JPQL 성능 최적화를 위해 제공하는 기능
- 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
- 예시
- 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
- jpql:
- 그냥
select m from Member m
을 해서 하나하나 Team을 가져오면 지연로딩이 발생해 조회할때마다 쿼리를 발생시키는N + 1
문제가 발생함 - 페치 조인을 통해 쿼리 한번으로 해결
#
컬렉션 페치 조인- 일대다 관계
- 예시
- 데이터 뻥튀기가 문제
- 이너 조인이라 row가 2개 출력되서 결과도 2개나옴
- 하지만 객체는 같은 것을 공유하므로 같은 객체들이 나옴 (객체 중복 생성은 X)
- jpql distinct 는 중복을 줄여줌
#
페치 조인의 특징과 한계- 페치 조인 대상에는 별칭을 줄 수 없음
- JPA 철학은 객체 그래프를 탐색한다는 것은 연관된 것 모두를 조회하는 것에 있다
- 둘 이상의 컬렉션은 페치 조인 할 수 없음
- 페치 조인의 대상은 하나만
- 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없음
- 일대일, 다대일은 페치조인해도 페이징 가능
- 일대다는 데이터 뻥튀기가 되서 안됨
- row 제한으로 인해 일대
다
에 해당하는 데이터가 중간에 짤리는 현상이 발생해 JPA 객체의 의미를 잃음 - 강제로 하게되면 전체 데이터를 메모리로 올려서 적용하게되는데... (ㄷㄷ)
- row 제한으로 인해 일대
#
그럼 페이징 어떻게 하지- 역으로 쿼리를 뒤집어서 페이지네이션을 적용
- 일대다 관계를 다대일 관계로 뒤집기
- 페치조인을 과감하게 뺌
- Entity 객체에
@BatchSize(size = 100)
를 통해 멤버를 100개씩 조회해서 가져와서 앱단에서 조인하게됨 hibernate.default_batch_fetch_size
의 값으로 위의 기능을 글로벌하게 수행할 수 있음
- DTO로 쿼리 직접 작성하기
#
페치 조인 정리- 글로벌 로딩 전략은 모두 지연 로딩으로
- 최적화가 필요한 곳은 페치 조인을 적용
- 모든 것을 페치 조인으로 해결할 수 없음
- 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적
- 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야한다면,
- 페치 조인 보단 일반 조인을 사용하고
- 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적