Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

Fetch Join2 본문

인프런/[인프런] 자바ORM 표준 JPA 프로그래밍

Fetch Join2

Seung__ 2021. 8. 16. 21:05

1. 페치 조인의 특징과 한계


  • 페시 조인 대상에는 별칭을 줄 수 없다.
    • 하이버네이트는 가능, 하지만  가급적 사용하지 말자.
    • fetch join은 나랑 연관된 엔티티를 전부 다 가져옴.
      따라서 fetch join에 별칭을 적용하여 탐색 시, 일부만 불러오게 됨.
      예를들어 원래 t.members하면 5명이 나와야 하는데
      fetch join에 별칭 적용하여 조건을 추가하여 3개가 나왔다고하자.
      그러면 이 시점 이후에 t.members를 하면 5명이 아닌 3명만 조회된다.
      하지만 JPA 기본 설계는 t.members하면 멤버 전체를 갈 수 있어야 한다.
      (객체 그래프는 기본적으로 전부 조회할 수 있어야 함)  
  • 둘 이상의 컬렉션은 페치 조인할 수 없다.(일 - 다 - 다)
    • 일대다도 데이터 뻥튀기인데 일대다대다는? 난리남 ㅋㅋㅋㅋ
  • 컬렉션을 페지 조인하면 페이징 API(setFirstResult, setMaxResult)를 사용할 수 없다.
    • 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능
      • 데이터 뻥튀기가 안되기 때문!
      • 일대다인 경우 fetch join후 페이징 하여 일부만 가져오게되면 팀A의 멤버는 회원1만 있는 것처럼 됨.
      • 이렇게되면 망함 ㅋㅋㅋㅋㅋ 
    • 하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험?~!!)
      •  List<Team> resultList = em.createQuery(query, Team.class)
         .setFirstResult(0).setMaxResults(1).getResultList();
      • 20:29:25.559 [main] WARN org.hibernate.hql.internal.ast.QueryTranslatorImpl - HHH000104: 
        firstResult/maxResults specified with collection fetch; applying in memory!
      • 어? 로그는 남고 쿼리에는 페이징이 없다. 싹다 가져왔다.
        Hibernate: 
            /* select
                distinct t 
            From
                Team t 
            join
                fetch t.members as m */ select
                    distinct team0_.TEAM_ID as team_id1_3_0_,
                    members1_.id as id1_0_1_,
                    team0_.name as name2_3_0_,
                    members1_.age as age2_0_1_,
                    members1_.TEAM_ID as team_id5_0_1_,
                    members1_.type as type3_0_1_,
                    members1_.username as username4_0_1_,
                    members1_.TEAM_ID as team_id5_0_0__,
                    members1_.id as id1_0_0__ 
                from
                    Team team0_ 
                inner join
                    Member members1_ 
                        on team0_.TEAM_ID=members1_.TEAM_ID
                        
        resultList.size() = 1
        team.getName() = 팀A  team.getMembers().size() : 2
        --> member = Member{id=3, username='회원1', age=0}
        --> member = Member{id=4, username='회원2', age=0}
      • 하지만 메모리에 퍼올린 다음에 페이징 처리됨.ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ노답
      • 어떻게 해결? 일대다 --> 다대일로 방향을 바꿔
      • 그래도 컬렉션의 경우 페이징 하고싶어? --> @BatchSize 적용.

 

 

 

 

2. @BatchSize


  • N+1 이슈는 대부분 fetch join을 통해 해결이 가능
  • 하지만 컬렉션의 경우 페이징 처리가 필요하나 fetch join사용시 페이징 API 사용에 문제 있음
  • 이를 해결하기 위해 @BatchSize 적용 필요.
@Entity
public class Team {

    @BatchSize(size = 100)
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
    
 }
  • join fetch 사용 안하고 한 SQL을 통해 연관된 엔티티를 불러오며 페이징 처리가 필요.
  String query = "select distinct t From Team t";

  List<Team> resultList = em.createQuery(query, Team.class)
                        .setFirstResult(0).setMaxResults(1)
                        .getResultList();
  • 사이즈는 예상 대로 팀a, 팀b로 2 이다.
  • 하지만 멤버를 select할 때 m.TEAM_ID에 (? , ?) 물음포 두개가 있는데, 
    이 말은 team id 두 개(팀a, 팀b)가 들어가있는 것.
  • 즉 한 번에 팀에이 팀비와 연관된 멤버를 모두 다 가져온 것.
  • 팀을 가져올 때 멤버는 레이지 로딩이나. 멤버를 가지로 올 때
  • 해당 멤버의 팀 뿐만 아니라 리스트에 있는 팀을 인쿼리로 batch size로 지정한 만큼 내에서 존재하는 만큼 넘김.
  • 지금은 팀이 두 개 뿐이이 물음표가 두개 날라감 ㅋㅋㅋㅋ
resultList.size() = 2

Hibernate: 
    /* load one-to-many jpql.Team.members */ select
        members0_.TEAM_ID as team_id5_0_1_,
        members0_.id as id1_0_1_,
        members0_.id as id1_0_0_,
        members0_.age as age2_0_0_,
        members0_.TEAM_ID as team_id5_0_0_,
        members0_.type as type3_0_0_,
        members0_.username as username4_0_0_ 
    from
        Member members0_ 
    where
        members0_.TEAM_ID in (
            ?, ?
        )
team.getName() = 팀A  team.getMembers().size() : 2
--> member = Member{id=3, username='회원1', age=0}
--> member = Member{id=4, username='회원2', age=0}
team.getName() = 팀B  team.getMembers().size() : 1
--> member = Member{id=5, username='회원3', age=0}
  • @BatchSize를 global setting으로 설정이 가능
  •    <property name="hibernate.default_batch_fetch_size" value="100" />
  • 즉 @BatchSize를 활용하면 N+1 이슈 해결.
  • 쿼리가 N+1이 아니라 딱 테이블 수 만큼 맞출 수 있음.

 

 

3. 페치 조인의 특징과 한계


  • 연관된 엔티티들을 SQL 한  번으로 조회 - 성능 최적화
  • 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함
    • @OneToMany(fetch = FetchType.LAZY) 보다 우선순위 높음
  • 실무에서 글로벌 로딩 전략을 모두 지연 로딩
  • 최적화가 필요한 곳은 페치 조인 적용. -->대부분 최적화됨 ㅋㅋㅋ

 

4. 정리


  • 모든 것을 페치 조인으로 해결할 수는 없음
  • 페지 조인은 객체 그래프를 유지할 때 사용하면 효과적임
    • m.team와 같이 찾아갈 결우 효과적.
  • 여러 테이블은 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면,
  • 페치 조인 보다는 일반적인 조인을 사용하고, 필요한 데이터들만 조회해서
  •  DTO로 반환하는 것이 효과적.

 

 

5. GitHub : 210816 FETCH JOIN2, @BatchSize


 

GitHub - bsh6463/JPQL_STUDY

Contribute to bsh6463/JPQL_STUDY development by creating an account on GitHub.

github.com

 

'인프런 > [인프런] 자바ORM 표준 JPA 프로그래밍' 카테고리의 다른 글

[JPQL] 엔티티 직접 사용  (0) 2021.08.16
다형성 쿼리  (0) 2021.08.16
FETCH JOIN  (0) 2021.08.16
경로 표현식  (0) 2021.08.16
JPQL 기본 함수  (0) 2021.08.16
Comments