이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편]을 수강하며 정리한 글입니다.
👉 기본 환경
- Language: Java
- DB: H2 Database
- IDE: IntelliJ
Fetch Join
- JPQL에서 성능 최적화를 위해 제공하는 기능
- 연관된 Entity나 Collection을 SQL 한 번으로 함께 조회하는 기능
1
2
3
4
5
6
|
-- JPQL
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;
|
Join
⌨️ 코드
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
45
46
47
48
49
50
51
52
53
54
55
56
|
public class Main {
public static void main(String[] args) {
// 생략
try {
Team teamA = new Team();
teamA.setName("TeamA");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
Member memberA = new Member();
memberA.setName("MemberA");
memberA.setTeam(teamA);
em.persist(memberA);
Member memberB = new Member();
memberB.setName("memberB");
memberB.setTeam(teamA);
em.persist(memberB);
Member memberC = new Member();
memberC.setName("memberC");
memberC.setTeam(teamB);
em.persist(memberC);
em.flush();
em.clear();
String query = "select m from Member m ";
List<Member> members = em.createQuery(query, Member.class)
.getResultList();
for (Member m : members)
System.out.println("member: " + m);
tx.commit(); // transaction 종료 후 commit
} catch (Exception e) {
tx.rollback(); // 문제가 생길 경우, rollback 진행
e.printStackTrace();
} finally {
em.close(); // tx에 문제가 생기더라도 em 반드시 종료
}
emf.close();
// 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
}
}
|
🖨️발생한 쿼리
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Hibernate:
/* select
m
from
Member m */ select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.name as name3_0_,
member0_.TEAM_ID as team_id5_0_,
member0_.type as type4_0_
from
Member member0_
|
@ManyToOne 관계로 fetchType.LAZY 설정
- Team이 사용되지 않으면 select이 실행되지 않음
일반 조인 실행 시, 연관된 엔티티를 함께 조회하지 않음
⌨️ 코드
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
|
public class Main {
public static void main(String[] args) {
// 생략
try {
// 생략
String query = "select m from Member m ";
List<Member> members = em.createQuery(query, Member.class)
.getResultList();
for (Member m : members)
System.out.println("member: " + m + ", member.team" + m.getTeam().getName());
tx.commit(); // transaction 종료 후 commit
} catch (Exception e) {
tx.rollback(); // 문제가 생길 경우, rollback 진행
e.printStackTrace();
} finally {
em.close(); // tx에 문제가 생기더라도 em 반드시 종료
}
emf.close();
// 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
}
}
|
🖨️발생한 쿼리
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
|
Hibernate:
/* select
m
from
Member m */ select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.name as name3_0_,
member0_.TEAM_ID as team_id5_0_,
member0_.type as type4_0_
from
Member member0_
Hibernate:
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
member: Member{id=3, name='MemberA', age=0}, member.teamTeamA
member: Member{id=4, name='memberB', age=0}, member.teamTeamA
Hibernate:
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
member: Member{id=5, name='memberC', age=0}, member.teamTeamB
|
🚨 N+1 문제 발생
- Member 객체에 포함된 Team 개수만큼 select query 발생
- SQL: MemberA + TeamA
- 1차 캐시: MemberB + TeamA
- SQL: MemberC + TeamB
Fetch Join
⌨️ 코드
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
|
public class Main {
public static void main(String[] args) {
// 생략
try {
// 생략
String query = "select m from Member m join fetch m.team ";
List<Member> members = em.createQuery(query, Member.class)
.getResultList();
for (Member m : members)
System.out.println("member: " + m + ", member.team" + m.getTeam().getName());
tx.commit(); // transaction 종료 후 commit
} catch (Exception e) {
tx.rollback(); // 문제가 생길 경우, rollback 진행
e.printStackTrace();
} finally {
em.close(); // tx에 문제가 생기더라도 em 반드시 종료
}
emf.close();
// 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
}
}
|
🖨️발생한 쿼리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
Hibernate:
/* select
m
from
Member m
join
fetch m.team */ select
member0_.id as id1_0_0_,
team1_.id as id1_3_1_,
member0_.age as age2_0_0_,
member0_.name as name3_0_0_,
member0_.TEAM_ID as team_id5_0_0_,
member0_.type as type4_0_0_,
team1_.name as name2_3_1_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.id
member: Member{id=3, name='MemberA', age=0}, member.teamTeamA
member: Member{id=4, name='memberB', age=0}, member.teamTeamA
member: Member{id=5, name='memberC', age=0}, member.teamTeamB
|
Fetch Join으로 회원과 팀을 함께 조회하여 지연 로딩 X
Collection Fetch Join
⌨️ 코드
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
|
public class Main {
public static void main(String[] args) {
// 생략
try {
// 생략
String query = "select t from Team t join fetch t.members ";
List<Team> teams = em.createQuery(query, Team.class)
.getResultList();
for (Team t : teams){
System.out.println("Team: " + t.getName());
for(Member m: t.getMembers()){
System.out.println(" -> Member: " + m);
}
}
tx.commit(); // transaction 종료 후 commit
} catch (Exception e) {
tx.rollback(); // 문제가 생길 경우, rollback 진행
e.printStackTrace();
} finally {
em.close(); // tx에 문제가 생기더라도 em 반드시 종료
}
emf.close();
// 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
}
}
|
🖨️발생한 쿼리
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
|
Hibernate:
/* select
t
from
Team t
join
fetch t.members */ select
team0_.id as id1_3_0_,
members1_.id as id1_0_1_,
team0_.name as name2_3_0_,
members1_.age as age2_0_1_,
members1_.name as name3_0_1_,
members1_.TEAM_ID as team_id5_0_1_,
members1_.type as type4_0_1_,
members1_.TEAM_ID as team_id5_0_0__,
members1_.id as id1_0_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
Team: TeamA
-> Member: Member{id=3, name='MemberA', age=0}
-> Member: Member{id=4, name='memberB', age=0}
Team: TeamA
-> Member: Member{id=3, name='MemberA', age=0}
-> Member: Member{id=4, name='memberB', age=0}
Team: TeamB
-> Member: Member{id=5, name='memberC', age=0}
|
🚨 record 수에 맞춰서 Team이 중복되어 출력됨
Distinct
- SQL의 DISTINCT 기능(데이터가 다르면 의도한 DISTINCT가 제대로 동작하지 X)
- 애플리케이션 엔티티 중복 제거(같은 식별자를 가진 엔티티 제거)
⌨️ 코드
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
|
public class Main {
public static void main(String[] args) {
// 생략
try {
// 생략
String query = "select distinct t from Team t join fetch t.members ";
List<Team> teams = em.createQuery(query, Team.class)
.getResultList();
for (Team t : teams){
System.out.println("Team: " + t.getName());
for(Member m: t.getMembers()){
System.out.println(" -> Member: " + m);
}
}
tx.commit(); // transaction 종료 후 commit
} catch (Exception e) {
tx.rollback(); // 문제가 생길 경우, rollback 진행
e.printStackTrace();
} finally {
em.close(); // tx에 문제가 생기더라도 em 반드시 종료
}
emf.close();
// 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
}
}
|
🖨️발생한 쿼리
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
|
Hibernate:
/* select
distinct t
from
Team t
join
fetch t.members */ select
distinct team0_.id as id1_3_0_,
members1_.id as id1_0_1_,
team0_.name as name2_3_0_,
members1_.age as age2_0_1_,
members1_.name as name3_0_1_,
members1_.TEAM_ID as team_id5_0_1_,
members1_.type as type4_0_1_,
members1_.TEAM_ID as team_id5_0_0__,
members1_.id as id1_0_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
Team: TeamA
-> Member: Member{id=3, name='MemberA', age=0}
-> Member: Member{id=4, name='memberB', age=0}
Team: TeamB
-> Member: Member{id=5, name='memberC', age=0}
|
컬렉션 내 중복 제거
🤓 member1_TEAM_ID 가 select절에 2번 쓰인 이유
- 하나의 별칭(team_id5_0_0__)은 Team 객체를 구성하는데 사용
- 다른 하나의 별칭(team_id5_0_1_)은 Member 객체를 구성하는데 사용
- 같은 필드에 대해 서로 다른 용도로 별칭을 부여함으로써, 하나의 SQL 쿼리 결과로 여러 개의 객체를 올바르게 생성할 수 있음
따라서 실제 데이터베이스에서 members1_.TEAM_ID 필드가 두 번 조회되는 것은 아니며, 하이버네이트가 내부적으로 같은 필드를 서로 다른 용도로 활용하기 위해 별칭을 두 번 부여한 것
'Java > JPA' 카테고리의 다른 글
[JPA_Basic] Bulk 연산 (0) | 2023.09.29 |
---|---|
[JPA_Basic] Fetch Join 2 (0) | 2023.09.28 |
[JPA_Basic] 경로 표현식 (0) | 2023.09.26 |
[JPA_Basic] JPQL 타입 표현과 기타식 (1) | 2023.09.25 |
[JPA_Basic] 조인 (0) | 2023.09.23 |