[ 정의 ]
.(점)
을 찍어객체 그래프
를탐색
하는 것
ex)select m.username -> 상태 필드 from Member m join m.team t -> 단일 값 연관 필드 join m.orders o -> 컬렉션 값 연관 필드 where t.name = '팀A'
- 상태 필드(state field)
: 단순히 값을 저장하기 위한 필드
ex)m.username
- 연관 필드(association field)
단일 값 연관 필드
: 대상이 단수의 엔티티(Entity)
ex)@ManyToOne
/@OneToOne
컬렉션 값 연관 필드
: 대상이 컬렉션(Collection)
ex)@OneToMany
/@ManyToMany
- 묵시적 조인
:경로 표현식
에 의해묵시적
으로SQL 조인
이 발생되는 것
(내부조인만 가능 / 외부조인은 불가능)- 명시적 조인
:join 키워드
를 직접 사용하는 것
- 상태필드(state field)
: 경로 탐색의 끝,탐색 X
m.username
에서 더이상 탐색할 수 없음 (더 들어갈 게 없음)- 단일 값 연관 경로
:묵시적 내부 조인 발생 O
/탐색 O
/* 묵시적 내부 조인 X */ select m from Member m --> Member내부에 Team이라는 객체 필드가 있지만, 기본적으로 지연로딩(LAZY)이기 때문 /* 묵시적 내부 조인 O */ select m.team from Member m --> 연관 필드를 직접 프로젝션에 올렸기 때문에 실제 Join된 커리가 나간다(묵시적 내부 조인) --> 그리고, Member에 있는 또 다른 연관 필드에 접근 가능(탐색 O)
- 컬렉션 값 연관 경로
:묵시적 내부 조인 발생 O
/탐색 X
/* 묵시적 내부 조인 O */ select t.members from Team t --> 컬렉션 값 연관 필드를 가져올 경우 당연히 묵시적 내부 조인은 생긴다 --> 하지만, 더이상 탐색은 할 수 없음 select t.members.username from Team t --> (X) --> 원한다면 명시적 조인을 해야함 select m.username from Team t join Member m
묵시적 조인
은경로 표현식
에 의해 생기는데 이는 실무에서 조심해야 함조인
은 명시적으로 일어나야 나중에sql 튜닝
이나유지보수
하기에 좋음- 실무에서는
묵시적 조인
이 최대한 일어나지 않게 하자(미사용 권장
)컬렉션 값 연관필드
에 대한조인
은 더이상탐색
이불가능
하다
- 연관된 엔티티나 컬렉션을 SQL 한번에 함께 조회하는 기능
SQL 조인
종류 XJPQL
에서성능 최적화
를 위해 제공하는 기능join fetch
명령어 사용
[ 설명 ]
- fetch join 대상이
엔티티(단수)
즉시 로딩
처럼 연관된 객체를 함께 조회해서영속성 컨텍스트
1차 캐시에 저장됨하나의 SQL
로 가져오는 것이 핵심!
-->N+1문제
의해결방안
으로 많이 사용/* JPQL -> 회원을 조회하면서 연관된 팀도 함께 조회 (하나의 SQL로!) */ 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 // M.*과 T.*은 모든 컬럼을 생략으로 표시한 것 실제로는 이렇게 나오지 X
[ 예시 ]
- 현재상황
: 팀에 소속된 회원들의 정보를 가져오면서, 해당 팀의 정보를 함께 조회하고 싶음
(member + team)
- fetch join 미사용 일 경우
String jpql = "select m from Member m join m.team"; List<Member> members = em.createQuery(jpql, Member.class) .getResultList(); for (Member member : members) { // 일반 조인을 사용 --> 지연로딩 설정, 즉 이 시점에 반복문이 돌면서 쿼리가 나옴! System.out.println("username = " + member.getUsername() + ", " + "teamName = " + member.getTeam().name()); }
: 만약 데이터가 3개가 있고 모두 다른 팀이라면, 반복문에서 3번의 쿼리가 추가적으로 더 발생한다 --> N+1 문제 발생
- fetch join 사용일 경우
String jpql = "select m from Member m join fetch m.team"; List<Member> members = em.createQuery(jpql, Member.class) .getResultList(); for (Member member : members) { // 페치 조인 사용 --> 추가적인 쿼리가 나가지 않음! System.out.println("username = " + member.getUsername() + ", " + "teamName = " + member.getTeam().name()); }
: 이미
fetch join
으로한방 쿼리
로 조회 했으니영속성 컨텍스트
에 정보들이 존재함
-->추가적인 쿼리
가 발생하지 않는다
(fetch 전략
이LAZY
여도fetch join
이 우선권을 가진다!)
[ 설명 ]
fetch join
대상이컬렉션(복수)
일대다 관계
or다대다 관계
에서 발생/* JPQL -> 팀을 조회하면서 소속된 멤버들도 함께 조회 (하나의 SQL로!) */ select t from Team t join fetch t.members /* 실제 수행되는 SQL */ select M.*,T.* from Team t inner join Member m on t.id=m.team_id // M.*과 T.*은 모든 컬럼을 생략으로 표시한 것 실제로는 이렇게 나오지 X
[ 예시 ]
- 현재 상황
: 팀의 정보를 가져오는데 소속된 멤버들의 정보도 함께 원하는 상황
- 결과
:일대다 관계
이기 때문에 우리가 받는 teams 결과 리스트에 팀A는 2개가 들어가 있음
즉, 원하는 정보가 아닌중복 정보
가 나온다!
(조인을 해서 team에 대한 정보를 가져오는 것이니까 속한 멤버 수만큼 나옴)/* ===================요청================== */ 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) { System.out.println("teamname = " + team.getName() + ", team = " + team); for (Member member : team.getMembers()) { //페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함 System.out.println(“-> username = " + member.getUsername()+ ", member = " + member); } } /* ===================결과================== */ teamname = 팀A, team = Team@0x100 -> username = 회원1, member = Member@0x200 -> username = 회원2, member = Member@0x300 teamname = 팀A, team = Team@0x100 -> username = 회원1, member = Member@0x200 -> username = 회원2, member = Member@0x300
: 같은 팀A에 대한 정보가 중복되어 나오는 일이 발생함!
(조인을 하면 어쩔 수 없음 --> 그래서중복 제거
를 위해 필요한게DISTINCT
)
[ 설명 ]
SQL
의DISTINCT
는 중복된 결과를 제거하는 명령
-->JPQL
에서는 객체 중심이라서중복 엔티티
를 제거해줄 수 없음
JPQL
에서 새롭게DISTINCT
를 제공
SQL
에DISTINCT
기능- 애플리케이션에서
엔티티 중복
제거
[ 예시 ]
- 앞 선 예제에서
엔티티의 중복
으로 여러개가 출력되었음DISTINCT
옵션을 넣어서 실행한 결과/* ===================요청================== */ String jpql = "select DISTINCT 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) { System.out.println("teamname = " + team.getName() + ", team = " + team); for (Member member : team.getMembers()) { //페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함 System.out.println(“-> username = " + member.getUsername()+ ", member = " + member); } } /* ===================결과================== */ [DISTINCT 추가시 결과] teamname = 팀A, team = Team@0x100 -> username = 회원1, member = Member@0x200 -> username = 회원2, member = Member@0x300
일반 조인
실행시연관된 엔티티
를 함께 조회하지 않는다(지연로딩)[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'
:
Team 엔티티
만 조회하고 연관 객체인member
에 대해 조회하지 X
페치 조인
시연관된 엔티티
를 함께 조회[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'
페치 조인
대상에는 별칭을 줄 수 없다
: 표준으로는 지원 X,Hibernate
에서는 가능하긴 한데권장 X
/* 페치 조인 대상에 별칭을 부여한 경우 */ select t from Team t join fetch t.members m where m.age>10 --> 만약 member의 원래 와야할 data가 5개인데 걸러져서 가져오면 정합성 이슈 때문에 문제 발생 가능성이 생김 --> 매우매우 위험한 것임 --> 페치조인 대상에는 별칭을 주어 사용하지 말자;
둘 이상의 컬렉션
은페치 조인
할 수X
만약 Team에 있는 members에 대한 orders를 조회하는 경우! team -> member -> order 이렇게 2개의 컬렉션을 타게 된다 1:N + 1:N 관계가 되는데 이는 매우 위험하다! 1:N만 되어도 데이터가 뻥튀기 되는데 '일대다대다' 관계는 가늠하기 힘들고 위험성이 크다
컬렉션
을 페치 조인 하면페이징 API
를 사용할 수 없다
-->일대일 / 다대일
같은 단일 값 연관 필드들은패치조인
해도 페이징 가능!일대다 관계에서 조회시 데이터가 뻥튀기 된다고 말했다. 만약 이 상태에서 페이징으로 데이터의 개수 1개를 원한다고 가정하자. 팀A의 결과는 2개인데 그중에 1개만 반환되어 뒤에 잘려버린 member에 대한 정보는 다음페이지로 간다 즉, 올바른 페이징 로직이 되지 않아서 위험함 이를 해결하기 위한 방안 3가지 1) 다대일 관계로 쿼리를 짜면 페이징 가능 2) @Batchsize 사용 3) DTO 사용 // 2,3번은 필요하다면 나중에 더찾아보자!
페치 조인
은객체 그래프
를유지
할 때 사용하면 효과적일반조인
과 달리하나의 SQL
로 연관된 객체의 정보를 조회한다
--> 대부분의N+1 문제
를 해결할 수 있음여러 테이블을 조인
해서 엔티티가 가진 모양이 아닌전혀 다른 결과
를 내야하면,
페치 조인
보다는일반조인
을 사용하고,필요한 데이터만 조회
해DTO
로 받는게 효과적
-->최적화
에 관한 이야기