ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA (10) - JPQL 경로 표현식, Fetch join
    카테고리 없음 2024. 1. 12. 14:20

     

    1. 경로 표현식

     

    > .을 찍어서 객체 그래프 탐색하는 것 의미 

    //연관관계에 따라 단일 값, 컬렉션값 연관 필드
    SELECT m.username // 상태 필드 
    FROM Member m
    join m.team t //단일 값 연관 필드 
    join m.orders o //컬렉션값 연관 필드 
    where t.name = '팀A'

     

    > 상태 필드 : 단순히 값을 저장하기 위한 필드 

    > 연관필드 : 연관관계를 위한 필드 (단일,컬렉션)

     

    1.1 경로 표현식 특징 

     

    > 상태필드 : 경로탐색 끝 

    > 단일값 연관 경로 : 묵시적 내부 조인 발

    //단일 값 연관 경로 
    String query = "select m.team from Member m"; //묵시적 내부조인 발생 (하지마라!)
    String query = "select m.team.name from Member m" //탐색 가능
    
    
    //컬렉션 값 연관 경로 
    select t. memebers From Team t 
    //이런식으로 가져옴 
    //한팀당 멤버 List 가지고 있음 List<List>가 결과일 것 
    //당연하게도 . 탐색 x (Collection이잖슴, 다만 size정도는 가능)
    //쓰지말고 명시적 조인을 확용하자 
    
    select m from Team t join t.members m;
    //명시적 조인으로 별칭 얻는 방법

     

     - FROM 절에서 명시적 조인을 통해 별칭 얻으면 별칭을 통해 탐색 가능하다! 

     

    1.1 경로탐색 묵시적 조인 주의사항 

     

    > 항상 내부 조인임 

    > 컬렉션은 경로 탐색의 끝이다. 명시적 조인을 통해 별칭 얻자 

    > 경로 탐색은 주로 SELECT , WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL FROM절에 영향을 준다.

     

     

    2. Fetch Join

     

    - SQL 조인 종류 X

    - JPQL에서 성능 최적화를 위해 제공하는 기능이다.

    - 엔티티나 컬렉션을 SQL 한번에 함께 조회하는 기능이다.

    - join fetch 명령어 사용하자 

     

     List<Member> members = em.createQuery(query, Member.class).getResultList();
     //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

    > LAZY 전략이여서 member들만 가져온다. 문제없다. 

    > 만약 EAGER였으면, 1+N 문제 발생했을 것이다 (즉시 로딩은 em.find()에서만 문제 발생 x -> 식별자 있어서 그러함)

    > 이떄 패치조인이 아니라면, 컬렉션 members에서 팀 엔티티를 사용하면 쿼리가 나가기 시작한다 (1+N문제)

     

    출처: JPA기본편(인프런) - 김영한

    String jpql = "select m from Member m join fetch m.team";
    List<Member> members = em.createQuery(jpql,Member.class).getResultList();
    
    for(Member member : members) {
    	//패치 조인으로 member에서 Team 메서드 사용해도 문제없다.
    }

     

     

    2.1 fetch join의 문제점 (일대다 관계에서 - 일에서 패치조인으로 다를 가져올 경우)

     

    - 컬렉션 패치 조인 문제 (데이터 뻥튀기)

    select t from Team t join fetch t.members
    where t.name = '팀A'

     

    출처: JPA기본편(인프런) - 김영한

    String jpql = "select t from Team t join fetch t.members where t.name = '팀A'";
    List<Team> teams = em.createQuery(jpql,Team.class).getResultList();
    
    for(Team team : teams){
    	//회원 정보 꺼낼시 데이터 뻥튀기 됨 
    }

     

     > 먼저 DB에서 Member가 아닌, Team을 조회한다. 이때 Team.ID = Member.TEAM_ID를 조인조건으로 사용해서 가져오면, 당연히 결과 테이블에는 Team 데이터 중복이 발생한다.

    > JPA 입장에선 일단 이 데이터를 모두 가져와야한다. -> List<Team>에 중복 데이터 저장됨

    > 추후에 같은 팀 식별자를 가진 Member를 Team에 members에 추가한다. -> 결과적으로 의도하지않은 데이터 뻥튀기 발생

     

    2.2 컬렉션 패치조인 문제 해결법 

     

    - SQL의 DISTINCT를 SQL에 추가한다. 

    - JPQL의 DISTINCT 2가지 제공 기능을 이용한다 (애플리케이션에서 엔티티 중복을 제거)

     

    select distinct t from Team t join fetch t.members where t.name ='팀A'

    출처: JPA기본편(인프런) - 김영한

     

    > SQL 조회 결과에선 실패하지만, JPA가 distinct를보고, 애플리케이션 레벨에서 제거해준다.

    > 그냥 distinct 사용하면된다는말! 

     

     

    *하이버네이트 6에서 DISTINCT 명령어 사용하지 않아도 애플리케이션에서 중복 제거 자동 적용

     

    3. 일반 조인과 페치 조인의 차이 

     

    3.1 일반 조인

     

     > 일반 조인은 실행시 연관 엔티티 함께 조인하지 않는다. (lazy) 

     > 조인 조건은 들어가지만, 값을 select는 안함! 

     > 반환결과에 연관관계 고려하지않는다. 단지 SELECT 절에 지정한 엔티티만 조회한다.

    //jqpl
    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'

     

     

    3.2 패치 조인

     

     > 패치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시로딩) 

     > 패치 조인은 객체 그래프를 SQL 한번에 조회 

     

    //jqpl
    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'

     

     

    4. fetch 조인의 한계 

     

     > 조인 대상에 별칭 줄 수 있다 (하이버네이트는 가능함! 근데 가급적 사용 금지)

        - 하이버네이트는 지원하지만 가급적 사용하지말자 -> 패치조인결과를 다른 패치조인과 연결할때만 사용하셨다함 

        - 조인 대상을 통해 조작하면 이상한? 결과 발생한다고 함 

     

    > 둘 이상의 컬렉션은 페치 조인 할 수 없다. (멤버의 주문 (다) 주문의 멤버 (다)) 

     

    > 컬렉션을 패치 조인하면 페이징 API를 사용할 수 없다. 

        -> 일대일, 다대일  단일 값 연관 필드들은 페치 조인해도 페이징 가능

        -> 하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험하다!)

     

    - 해결방법 (일대다 컬렉션 조회시 데이터 뻥튀기) 

     

    1. BatchSize설정 (일반 조인으로 해결)

    String query2 = "select t from Team t";
    
    @BatchSize(size=100)
    @OneToMany(mappedBy = "team")
    List<Member> members = new ArrayList<>(); 
    
    //BatchSize 설정하면,
    //SELECT * FROM child WHERE child.parent(1,2,3) 100개씩 끊어서 관련 child 가져온다.

     

    2. 다대일 관계로 뒤집어서 사용 

    String sql = "SELECT m from Member m join fetch m.team"

     

     

     - 정리하자면, fetch join은 연관된 엔티티들을 SQL 한번으로 조회 가능 - 성능 최적화

     - 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선한다. 

     - 실무에서 글로벌 로딩 전략은 모두 지연 로딩

     - 최적하과 필요한 곳은 페치 조인 적용 

     

      * 페치 조인으로 모든 것을 해결할 수는 없다! 다만, 페치조인은 객체 그래프 유지할 때 사용하면 효과적이다.

         여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야하면, 페치조인말고 일반 조인 사용하고 

          DTO로 변환하는것이 효과적 

Designed by Tistory.