페치조인과 일반 조인의 차이점을 알아보기 앞서 먼저 페치조인에 대해 알아보자.
페치조인
페치조인은 SQL의 조인 종류는 아니다. JPQL의 전용기능이고, JPQL에서 성능 최적화를 위해 제공하는 기능이다.
한마디로 설명하자면 연관된 엔티티나 컬렉션을 하나의 SQL에 함께 조회하는 기능이다.
예제
// Member.java @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "TEAM_ID") private Team team; private String username; } // Team.java @Entity public class Team { @Id @GeneratedValue private Long id; @OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>(); private String name; @Override public String toString() { return "Team [id=" + id + ", name=" + name + "]"; } }

먼저 회원 1, 2는 TeamA에, 회원 3은 Team B에, 그리고 회원 4는 팀 소속 X으로 세팅했다.
예제를 사용한 세팅 코드
Team team1 = new Team(); team1.setName("teamA"); em.persist(team1); Team team2 = new Team(); team2.setName("teamB"); em.persist(team2); Member member1 = new Member(); member1.setUsername("member1"); member1.setTeam(team1); em.persist(member1); Member member2 = new Member(); member2.setUsername("member2"); member2.setTeam(team1); em.persist(member2); Member member3 = new Member(); member3.setUsername("member3"); member3.setTeam(team2); em.persist(member3); Member member4 = new Member(); member4.setUsername("member4"); em.persist(member4);
일반 조인 사용
먼저 Join만 사용해서 회원 조회를 해보자.
예제코드
String query = "select m from Member m join m.team"; List<Member> result = em.createQuery(query, Member.class).getResultList(); for (Member member : result) { System.out.println("member.getUsername() = " + member.getUsername()); System.out.println("member.getTeam().getName() = " + member.getTeam().getName()); }
Member엔티티의 필드 Team 엔티티가 지연로딩 전략이므로 프록시 Team 객체를 접근할 때 마다 쿼리가 실행된다.
Hibernate: select m1_0.id, m1_0.team_id, m1_0.username from member m1_0 join team t1_0 on t1_0.id=m1_0.team_id member.getUsername() = member1 Hibernate: select t1_0.id, t1_0.name from team t1_0 where t1_0.id=? member.getTeam().getName() = teamB member.getUsername() = member2 // 이하 생략
만약 회원 100명의 팀이 다 다르다면 100개의 쿼리가 더 실행될 것이다. 이 문제가 바로 N+1문제라고 한다.
페치 조인 사용
이제 페치 조인을 사용해서 조회를 해보자.
String query = "select m from Member m join fetch m.team"; List<Member> result = em.createQuery(query, Member.class).getResultList(); for (Member member : result) { System.out.println("member.getUsername() = " + member.getUsername()); System.out.println("member.getTeam().getName() = " + member.getTeam().getName()); }
Hibernate: select m1_0.id, t1_0.id, t1_0.name, m1_0.username from member m1_0 join team t1_0 on t1_0.id=m1_0.team_id member.getUsername() = member1 member.getTeam().getName() = teamA member.getUsername() = member2 member.getTeam().getName() = teamA member.getUsername() = member3 member.getTeam().getName() = teamB
실행결과에서 볼 수 있듯이 페치조인으로 회원과 팀을 한번에 조회해서 N+1문제가 발생하지 않는걸 볼 수 있다.
result에 실행결과 값이 담길 때 Team 객체는 프록시 객체가 아닌 실제 엔티티 객체가 담긴다.
여기서 알 수 있는점은 지연로딩으로 세팅해도 페치 조인이 우선이라 같이 조회된다는 점이다.
페치조인과 일반조인의 차이점
아니 그러면 Member 엔티티의 Team 엔티티 로딩 전략을 즉시로딩으로 바꾸고 페치 조인 대신 그냥 일반 조인 써도 되는거 아니야?
결과부터 말하자면 그래도 결국 N+1 문제가 발생한다.
예제를 통해 알아 보자.
// Member.java @ManyToOne(fetch = FetchType.EAGER) // 즉시로딩으로 수정 @JoinColumn(name = "TEAM_ID") private Team team;
Hibernate: select m1_0.id, m1_0.team_id, m1_0.username from member m1_0 join team t1_0 on t1_0.id=m1_0.team_id Hibernate: select t1_0.id, t1_0.name from team t1_0 where t1_0.id=? Hibernate: select t1_0.id, t1_0.name from team t1_0 where t1_0.id=?
join이라는 쿼리로 조회할때는 JPA에서 from절에 있는 엔티티만 조회한다. 즉 연관된 엔티티를 함께 조회하지 않는다.
Hibernate: select m1_0.id, m1_0.team_id, m1_0.username from member m1_0 join team t1_0 on t1_0.id=m1_0.team_id
그래서 이 코드가 실행될 때 join으로 Team을 조회하는 것처럼 보이지만 Member 엔티티만 값을 조회후 team 객체는 프록시로 들어가 있다.
하지만 JPA가 Team의 로딩 전략이 즉시 로딩이네? 하면서
Hibernate: select t1_0.id, t1_0.name from team t1_0 where t1_0.id=? Hibernate: select t1_0.id, t1_0.name from team t1_0 where t1_0.id=?
나머지 이 두 쿼리도 같이 실행한다. 결국 N+1 문제가 발생한다.
결론적으로
- 일반 조인: JOIN 조건을 제외하고 FROM 절에 있는 대상 엔티티만 조회
- 페치 조인: FROM 절에 있는 대상 엔티티와 FETCH JOIN으로 걸려 있는 엔티티를 함께 조회
'JPA' 카테고리의 다른 글
[JPA] JPQL의 경로표현식이란? (0) | 2024.12.27 |
---|---|
[JPA] JPQL의 프로젝션, 페이징 (0) | 2024.12.24 |
[JPA] JPA의 타입 (임베디드 타입, 값 타입, 컬렉션 타입)이란? (1) | 2024.12.20 |
[JPA] 프록시와 지연로딩, 즉시로딩 (0) | 2024.11.26 |
[JPA] 연관관계 매핑 (0) | 2024.11.25 |