jeonyoungho Oct 28, 2021 2021-10-28T00:00:00+09:00
Oct 29, 2021 2021-10-29T11:08:47+09:00 6 min
본 포스팅은 인프러의 JPA 기본편을 수강하고 정리하는 내용입니다.
JPQL - 페치 조인(fetch join)
실무에서 정말정말 중요함
- SQL 조인 종류X
- JPQL에서
성능 최적화
를 위해 제공하는 기능 - 연관된 엔티티나 컬렉션을
SQL 한 번에 함께 조회
하는 기능 - join fetch 명령어 사용
- 페치 조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로
엔티티 페치 조인
- 회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에)
- SQL을 보면 회원 뿐만 아니라 팀(T.*)도 함께 SELECT
JPQL
1
| select m from Member m join fetch m.team
|
1
2
| SELECT M.*, T.* FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID=T.ID
|
페치 조인 사용 코드(예시)
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
32
33
34
35
36
37
38
39
40
41
42
43
44
| Team teamA = new Team();
teamA.setName("teamA");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("teamB");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setAge(10);
member1.setType(MemberType.ADMIN);
member1.changeTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setAge(10);
member2.setType(MemberType.ADMIN);
member2.changeTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setAge(10);
member3.setType(MemberType.ADMIN);
member3.changeTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query = "select m from Member m";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member = " + member.getUsername() + ", " + member.getTeam().getName());
//회원1, 팀A (SQL 호출)
//회원2, 팀A (1차 캐시)
//회원3, 팀B (SQL 호출)
//회원 100명 -> N(첫 쿼리로 받아온 데이터의 갯수) + 1(Member를 가져온 첫 쿼리)
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| String query = "select m from Member m";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member = " + member.getUsername() + ", " + member.getTeam().getName());
//회원1, 팀A (프록시가 아닌 실제 객체)
//회원2, 팀A (프록시가 아닌 실제 객체)
//회원3, 팀B (프록시가 아닌 실제 객체)
//회원 100명 -> N(첫 쿼리로 받아온 데이터의 갯수) + 1(Member를 가져온 첫 쿼리)
}
|
컬렉션 페치 조인
1
2
3
| select t
from Team t join fetch t.members
where t.name = ‘팀A'
|
1
2
3
4
5
| SELECT T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M
ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'
|
컬렉션 페치 조인 사용 코드
1
2
3
4
5
6
7
8
9
10
11
| String query = "select t from Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
// 페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println("team = " + team.getName() + " | " + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println("- members = " + member);
}
}
|
일대다 조인은 데이터 뻥튀기 될 수 있다.
팀A에 연관된 Member가 2개 있다.
그럼 위의 이미지처럼 팀 입장에선 하나지만 멤버는 두명이기에 두 개를 만들어낸다.
같은 ID값을 가지기에 영속성 컨텍스트에는 1개의 TeamA객체만 올라간다. 결론적으로 같은 주소값을 가진 두 개가 출력된다.
페치 조인과 DISTINCT
SQL의 DISTINCT
는 중복된 결과를 제거하는 명령- JPQL의 DISTINCT 2가지 기능 제공
- 1)SQL에 DISTINCT를 추가
- 2)
애플리케이션에서 엔티티 중복 제거
1
2
3
| select distinct t
from Team t join fetch t.members
where t.name = ‘팀A’
|
- SQL에 DISTINCT를 추가하지만 데이터가 다르므로 SQL 결과 에서 중복제거 실패(레코드의 모든 레코드값이 일치하지 않으므로)
- DISTINCT가 추가로 애플리케이션에서 중복 제거시도
- 같은 식별자를 가진
Team 엔티티 제거
1
2
3
| teamname = 팀A, team = Team@0x100
-> username = 회원1, member = Member@0x200
-> username = 회원2, member = Member@0x300
|
Note: 다대일 관계는 아무 문제 없다. 일대다만 데이터가 뻥튀기 된다.
페치 조인과 일반 조인의 차이
- 일반 조인 실행시 연관된 엔티티를 함께 조회하지 않음
- 조인 쿼리만 실행되는거지 실제 select 절에 연관 엔티티 관련된게 없음
[JPQL]
1
2
3
| select t
from Team t join t.members m
where t.name = ‘팀A'
|
1
2
3
4
| SELECT T.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'
|
- JPQL은 결과를 반환할 때 연관관계 고려X
- 단지 SELECT 절에 지정한 엔티티만 조회할 뿐
- 여기서는 팀 엔티티만 조회하고, 회원 엔티티는 조회X
- 페치 조인을 사용할 때만 연관된 엔티티도 함께
조회(즉시 로딩)
- 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념