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 JOIN 본문

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

FETCH JOIN

Seung__ 2021. 8. 16. 20:04
실무에서 저어어어어어어어ㅓ엉어어어엉말 중요함.

 

 

1. fetch join


  • SQL 조인의 종류가 아님
  • JPQL에서 성능 최적화를 위해 제공하는 기능
  • 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능임.
  • join fetch 명령어 사용
  • fetch join ::=[LEFT (OUTER) | INNTER] JOIN FETCH 조인경로
  • 쿼리로 내가 원하는 객체 그래프를 어느 시점에  한 번에 조회할 것이라고 정할 수 있는 것.
  • 지연로딩으로 설정해도 fetch join의 우선순위가 높음.

 

2. Entity fetch join


  • 회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에)
  • SQL을 보면 회원 뿐 만 아니라 팀(T.*)도 함께 SELECT
  • JPQL
    •  
    • select m from Member m join fetch m.team
  • SQL: JPQL에서는 select 프로젝션에 m만 있는데, 실행된 SQL에는 M.*과 T.* 둘 다 있다.
    • SELECT M.*, T.* FROM MEMBER M
      INNER JOIN TEAM T ON M.TEAM_ID=T.ID

 

 

3. 예제


  • member1 : 팀A
  • member2 : 팀A
  • member3 : 팀B
  • 지연로딩으로 설정되어 있기 때문에, 처음 Team은 프록시로 불러온다.
  • team.getName 시점에 프록시 초기화됨.
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{

              Team teamA = new Team();
              teamA.setName("팀A");
              em.persist(teamA);

              Team teamB = new Team();
              teamB.setName("팀B");
              em.persist(teamB);

              Member member1 = new Member();
              member1.setUsername("회원1");
              member1.setTeam(teamA);
              em.persist(member1);

              Member member2 = new Member();
              member2.setUsername("회원2");
              member2.setTeam(teamA);
              em.persist(member2);

              Member member3 = new Member();
              member2.setUsername("회원3");
              member2.setTeam(teamB);
              em.persist(member2);

            em.flush();
            em.clear();

            String query = "select m From Member m";

            List<Member> resultList = em.createQuery(query, Member.class).getResultList();

            for (Member member : resultList) {
                System.out.println("member = " + member.getUsername() + "," + member.getTeam().getName());
                //회원1, 팀A(SQL)
                //회원2, 팀A(1차캐시)
                //회원3, 팀B(SQL)

            }


            tx.commit();
Hibernate: 
    /* select
        m 
    From
        Member m */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.TEAM_ID as team_id5_0_,
            member0_.type as type3_0_,
            member0_.username as username4_0_ 
        from
            Member member0_
            
 Hibernate: 
    select
        team0_.TEAM_ID as team_id1_3_0_,
        team0_.name as name2_3_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
        
member = 회원1,팀A
member = 회원2,팀A  

Hibernate: 
    select
        team0_.TEAM_ID as team_id1_3_0_,
        team0_.name as name2_3_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?

member = 회원3,팀B

 

  • N+1 이슈 : 나는 member는 가지고오는 쿼리를 날렸는데, 연관된 Team을 가져오는 쿼리도 날라감.
    • 즉 1번 째 쿼리에 해당하는 N개의 엔티티의 연관 엔티티까지 모두 SQL로 호출됨.
  • N+1은 즉시로딩이든, 지연로딩이든 발생함.
  • 해법은 fetch join임

 

 

 

4. fetch join 적용


  • N+1 이슈 해결책.
  • join을 할건데, 한번에 team까지 가져와
 String query = "select m From Member m join fetch m.team";
  • 이 때 join으로 가져온 Team은 프록시가 아님. 진짜 데이터 가져옴. 데이터가 다 채워져있음.
Hibernate: 
    /* select
        m 
    From
        Member m 
    join
        fetch m.team */ select
            member0_.id as id1_0_0_,
            team1_.TEAM_ID as team_id1_3_1_,
            member0_.age as age2_0_0_,
            member0_.TEAM_ID as team_id5_0_0_,
            member0_.type as type3_0_0_,
            member0_.username as username4_0_0_,
            team1_.name as name2_3_1_ 
        from
            Member member0_ 
        inner join
            Team team1_ 
                on member0_.TEAM_ID=team1_.TEAM_ID
                
member = 회원1,팀A
member = 회원2,팀A
member = 회원3,팀B

 

 

 

 

5. 컬렉션 fetch join


  • 일대다 관계, 컬렉션 fetch join
  • 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'
String query = "select t From Team t join fetch t.members";

List<Team> resultList = em.createQuery(query, Team.class).getResultList();

for (Team team : resultList) {
	System.out.println("team.getName() = " + team.getName()+"  team.getMembers().size() : " + team.getMembers().size());
}
  • 어? Team A가 두 번 나왔네?
  • DB입장에서 일대다 조인하면 데이터가 뻥튀기됨. ㄷㄷ

 

team.getName() = 팀A  team.getMembers().size() : 2
team.getName() = 팀A  team.getMembers().size() : 2
team.getName() = 팀B  team.getMembers().size() : 1
  • teams 결과리스트 컬렉션을 보면 팀A가 두개 존재한다.
  • 첫 번 째 팀A는 영속성 컨텍스트에 등록이 되고 두 번째 팀A를 영속성 컨텍스트에서 가져다가씀.

  • Team만 불러오면 결과는 팀A, 팀B 두 개 이지만
  • join fetch t.members의 경우는 두 테이블이 조인되기 때문에
    팀A-member1, 팀A-member2, 팀B-member3으로 사이즈가 커진다! 중요한 차이!
  • 즉 데이터베이스에서 일대다 조인할 때 데이터가 뻥튀기 될 수 있다.(중복이 있다.)

 

 

 

 

6. fetch join과 DISTINCT


  • 중복이 싫은데..?
  • SQL의 DISTINCT는 중복된 결과를 제거하는 명령
  • JPQL의 DISTINCT는 2 가지 기능을 제공
    • SQL에 DISTINCT를 추가
    • 애플리케이션에서 엔티티 중복제거
  • select distinct t
    from Team t join fetch t.members
    where t.name = '팀A'
  • SQL에 DISTINCT 추가해도 데이터가 다르므로 SQL결과에서 중복제거 실패함.
  • 즉 쿼리만으로는 데이터가 줄어들지 않는다.
  • 그래서 JPA에서 DISTINCT가 추가로 애플리케이션에서 중복 제거 시도.
  • JPA가 같은 식별자를 가진 Team Entity를 result에서 제거해서 리스트를 반환.


  • distinct 적용 후 결과 리스트의 사이즈를 찍어보면 중복이 제거된 것을 볼 수 있다.
resultList.size() = 2
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}

 

 

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


  • 일반 조인 실행시 연관된 엔티티를 함께 조회하지 않음.
  • JPQL
    •  
    • select t from Team t join t.members m where t.name = ‘팀A'
    • SELECT하는 DATA는 결국 Team.
    • member는 단순 join만하고 조회하진 않음.
  • SQL
    • SELECT T.*
      FROM TEAM T
      INNER JOIN MEMBER M ON T.ID=M.TEAM_ID 
      WHERE T.NAME = '팀A'
  • JPQL은 결과를 반환할 때 연관관계 고려하지 않음
  • 단지 SELECT 절에 지정한 엔티티만 조회함
  • 여기서는 팀 엔티티만 조회하고, 회원엔티티는 조회하지 않음.
  • 페치 조인을 사용할 때 만 연관된 엔티티도 함께 조회(즉시로딩)
  • 페치 조인은 객체 그래프를 SQL한 번에 조회하는 개념.

 

 

8. GitHub : 210816 FETCH JOIN1


 

GitHub - bsh6463/JPQL_STUDY

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

github.com

 

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

다형성 쿼리  (0) 2021.08.16
Fetch Join2  (0) 2021.08.16
경로 표현식  (0) 2021.08.16
JPQL 기본 함수  (0) 2021.08.16
JPQL 조건식 - CASE  (0) 2021.08.16
Comments