spring/JPA

[JPA] 객체 지향 쿼리 언어 JPQL 5 (경로 표현식, fetch join)

대기업 가고 싶은 공돌이 2024. 8. 4. 04:26

경로 표현식

.(점)을 찍어 객체 그래프를 탐색하는 것이다.

 

select m.username -> 상태필드로 객체를 탐색한 경우

from Member m

   join m.team t -> 단일 값 연관 필드 (여기서 team은 엔티티기 때문에 단일 값을 탐색한 것이다.)

   join m.orders o -> 컬렉션 값 연관 필드 (여기서 orders는 컬렉션이어서 컬렉션을 탐색한 것이다.)

where t.name = '팀A'

 

.을 통해 상태 필드로 가냐, 단일 값 연과 필드로 가냐, 컬렉션 값 연관 필드로 가냐에 따라서

내부적으로 동작하는 방식이 다 달라진다.

 

 용어 정리

  • 상태 필드: 단순히 값을 저장하기 위한 필드다 (ex: m.username)
  • 연관 필드: 연관 관계를 위한 필드다
    • 단일 값 연관 필드: @ManyToOne, @OneToOne, 대상이 One 즉 엔티티다 (ex: m.team)
    • 컬렉션 값 연관 필드: @OneToMany, @ManyToMany, 대상이 컬렉션 (ex: m.orders)

 

경로 표현식 특징

  • 상태 필드: 경로 탐색의 끝, 탐색 X (m.username에서 .을 또 찍어서 탐색이 불가능 하다)
  • 단일 값 연관 경로: 묵시적 내부 조인(inner join) 발생, 탐색 O
    • member.team에서 또 .을 찍어서 탐색을 더 할 수 있다. 이 말이 뭐냐 묵시적 내부 조인이 발생했다는 얘기다.
    • select member.team from Member m 만 실행해도 innerjoin이 발생한다.
  • 컬렉션 값 연관 경로: 묵시적 내부 조인 발생, 탐색 X
    • 엔티티가 아닌 컬렉션이기 때문에 더 이상 탐색이 불가능 하다. size()같은 함수는 가능하다. 
    • from 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색 가능하다. 
      • ex: select m.username from Team t join t.Members m
      • 이와 같이 조인을 통해 별칭을 얻고 나서 탐색을 할 수 있다.

결론: 묵시적 조인을 쓰지 말고 명시적 조인을 사용하자.

 

단일 값 연관 경로 탐색의 쿼리

JPQL: select o.member from Order o

SQL: select m.*from Orders o innerjoin Member m on o.member_id = m.id 

 

예제

 

주의 사항 리마인드

  • 경로탐색 시 엔티티를 제외하면 항상 내부 조인이 발생한다.
  • 컬렉션은 경로 탐색의 끝이다. 명시적 조인을 통해 별칭을 얻어야한다.
  • 경로 탐색은 주로 select , where 절에서 사용하지만 묵시적 조인으로 인해 SQL의 from 절에 join 영향을 준다. 

 

페치 조인(fetch join)

  • SQL 조인의 종류가 아니다
  • JPQL에서 성능 최적화를 위해 제공하는 기능이다.
  • 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능이다.
  • join fetch 명령어를 사용한다.
  • 페치 조인 ::= [LEFT [OUTER] | INNER] JOIN FETCH 조인 경로 

엔티티 페치 조인

  • 회원을 조회하면서 연관된 팀도 함께 조회 (SQL 한 번에)
  • SQL을 보면 회원 뿐만 아니라 팀( T.*) 도 함께 select
  • [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 

 

위의 예시를 보았을 때 회원과 팀 inner join을 하면 오른쪽 위와 같은 결과가 반환된다.

 

페치 조인을 실시하면 맨 아래의 그림처럼 회원 1,2,3의 엔티티가 팀 A,B 엔티티를 가르키는

 

그림을 만들어 영속성 컨텍스트에 보관한다.

 

 비교 예시

페치 조인 사용 안 할 시

우선 select m from Member m을 예시로 들어보겠다.

 

멤버 리스트만 쫙 가져온 다음에 출력을 할 때

 

soutv(m.username + m.team.TeamName)을 출력한다고 하자. (모든 페치 타입은 LAZY라고 가정)

 

이 상황에서 페치 타입이 LAZY기 때문에 한 멤버의 팀을 가져오기 위해

 

멤버 한 명 한 명 마다 team에 대한 select 쿼리가 나간다.

 

즉 멤버가 100명이라고 치면 추가로 100개의 쿼리가 더 나가는 것이다.

 

이를 N+1문제라고 한다.

 

페치 타입을 EAGER로 잡아도 N+1 문제는 발생한다.

 

이 N+1 문제를 해결하기 위해선 페치 조인을 사용해야 한다.

 

페치 조인 사용할 시

select m from Member m join fetch m.team 을 사용하면

 

쿼리는 한 번 밖에 발생하지 않게 된다. 

 

즉, 연관관계에 있는 다른 테이블 정보도 같이 보여주고 싶다면 꼭 페치 조인을 사용해야 한다.

 

컬렉션 페치 조인

  • 일대다 관계 일 때 컬렉션 페치 조인이라 한다.
  • [JPQL]
    select t
    from Team t join fetch t.members
    where t.name = '팀A'
  • [SQL]
    select t.*, m.*
    from Team t
    inner join Member m on t.id = m.team_id
    where t.name = '팀A'

일대다 상황일 때의 조인은 데이터가 뻥튀기 되는 문제가 있다.

위의 예시를 보면 쉽게 이해할 수 있다.

 

팀 A와 멤버를 조인 하게 되면, 팀 A에 멤버가 두 명이 존재하기에 팀 A가 두 행을 차지하게 된다.

 

만약 팀 A에 멤버가 100명이라면 팀 A가 100번 출력되는 것이다.

 

soutv(team.name, team.member.size())를 출력한다고 하면 팀 A , 100 이 100번 출력된다.

 

중복을 제거하고 싶으면 distinct를 사용하면 된다.

 

페치 조인과 DISTINCT

  • SQL의 DISTINCT는 중복된 결과를 제거하는 명령이다.
  • JPQL의 DISTINCT 2가지 기능 제공
    1. SQL에 DISTINCT를 추가
    2. 애플리케이션에서 엔티티 중복 제거 
      • SQL에 distinct를 날린 후 반환 값에 똑같은 엔티티가 존재하면 애플리케이션에서 중복을 제거한다.
      • select distinct t
        from Team t join fetch t.members
        where t.name = '팀A'
        쿼리를 날렸을 때를 살펴보자


sql에 distinct를 추가하지만 데이터가 다르므로 sql 결과에서 중복 제거에 실패할 것이다.

 

이후 jpa에서 같은 식별자를 가진 Team 엔티티를 제거해 버린다.

 

그럼 중복이 제거된 리스트가 반환되게 된다. 기준은 식별자이다. (위의 예시에서 식별자는 ID다.)

 

일대다는 개수가 뻥튀기가 되기 때문에 주의해야 하고, 다대일은 그냥 조인해도 아무 상관 없다.

 

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

일반 조인

  • 일반 조인 실행시 연관된 엔티티를 함께 조회하지 않는다.
  • [JPQL]
    select t
    from Team t join t.members m
    where t.name = '팀A'
  • [SQL]
    select t.*  (일반 조인시 팀의 정보만 가져온다.)
    from Team t
    inner join Member m on t.id = m.team_id
    where t.name = '팀A'
  • 이런 경우에도 마찬가지로 members는 정보가 비어있기 때문에 멤버를 조회할 때마다 쿼리가 나가게 된다.
  • 단지 select 절에 지정한 엔티티만 조회할 뿐 연관 관계 대상을 퍼올리지 않는다. 

페치 조인

  • 페치 조인은 연관된 엔티티도 함께 조회한다 (즉시 로딩)
  • 페치 조인은 객체 그래프를 SQL 한 번에 조회하는 개념이다. 
  • N+1 문제 해결

 

강의 영상: 자바 ORM 표준 JPA 프로그래밍 -기본편