11. 객체지향 쿼리 언어2 - 중급

JPQL 경로 표현식#

select m.username -> 상태 필드
from Member m
join m.team t -> 단일 값 연관 필드
join m.orders o -> 컬렉션 값 연관 필드
where t.name = '팀
  • 상태 필드: 단순히 값을 저장하기 위한 필드
  • 연관 필드
    • 단일 값 연관 필드
      • *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
  • 그냥 select m from Member m 을 해서 하나하나 Team을 가져오면 지연로딩이 발생해 조회할때마다 쿼리를 발생시키는 N + 1 문제가 발생함
  • 페치 조인을 통해 쿼리 한번으로 해결

컬렉션 페치 조인#

  • 일대다 관계
  • 예시
/* jpql */
select t from Team t join fetch t.members where t.name = '팀A'
/* sql */
SELECT T.*, M.*
FROM TEAM T
INNER JOIN M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'
  • 데이터 뻥튀기가 문제
    • 이너 조인이라 row가 2개 출력되서 결과도 2개나옴
    • 하지만 객체는 같은 것을 공유하므로 같은 객체들이 나옴 (객체 중복 생성은 X)
  • jpql distinct 는 중복을 줄여줌

페치 조인의 특징과 한계#

  • 페치 조인 대상에는 별칭을 줄 수 없음
    • JPA 철학은 객체 그래프를 탐색한다는 것은 연관된 것 모두를 조회하는 것에 있다
  • 둘 이상의 컬렉션은 페치 조인 할 수 없음
    • 페치 조인의 대상은 하나만
  • 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없음
    • 일대일, 다대일은 페치조인해도 페이징 가능
    • 일대다는 데이터 뻥튀기가 되서 안됨
      • row 제한으로 인해 일대 에 해당하는 데이터가 중간에 짤리는 현상이 발생해 JPA 객체의 의미를 잃음
      • 강제로 하게되면 전체 데이터를 메모리로 올려서 적용하게되는데... (ㄷㄷ)

그럼 페이징 어떻게 하지#

  1. 역으로 쿼리를 뒤집어서 페이지네이션을 적용
  • 일대다 관계를 다대일 관계로 뒤집기
  1. 페치조인을 과감하게 뺌
  • Entity 객체에 @BatchSize(size = 100) 를 통해 멤버를 100개씩 조회해서 가져와서 앱단에서 조인하게됨
  • hibernate.default_batch_fetch_size 의 값으로 위의 기능을 글로벌하게 수행할 수 있음
  1. DTO로 쿼리 직접 작성하기

페치 조인 정리#

  • 글로벌 로딩 전략은 모두 지연 로딩으로
  • 최적화가 필요한 곳은 페치 조인을 적용
  • 모든 것을 페치 조인으로 해결할 수 없음
  • 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적
  • 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야한다면,
    • 페치 조인 보단 일반 조인을 사용하고
    • 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적
Last updated on