
[ 정의 ]
.(점)을 찍어객체 그래프를탐색하는 것
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로 받는게 효과적
-->최적화에 관한 이야기