이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편]을 수강하며 정리한 글입니다.
👉 기본 환경
- Language: Java
- DB: H2 Database
- IDE: IntelliJ
⭐ N + 1 문제
: 연관관계가 설정된 엔티티 사이에서 하나의 엔티티를 조회했을 때, 조회된 엔티티의 개수(N 개)만큼 연관된 엔티티를 조회하기 위해 추가적인 쿼리가 발생하는 문제
- 1: 하나의 엔티티를 조회하기 위한 쿼리의 개수
- N: 연관된 데이터를 조회하기 위한 추가적인 쿼리의 개수
⌨️ 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Entity
public class Member extends BaseEntity {
// 생략
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
// 생략
}
|
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
|
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 member1 = new Member();
member1.setName("user1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setName("user2");
member2.setTeam(teamB);
em.persist(member2);
em.flush();
em.clear();
List<Member> members = em.createQuery("select m from Member m", Member.class)
.getResultList();
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.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
33
34
35
36
37
38
39
40
41
|
Hibernate:
/* select
m
from
Member m */ select
member0_.MEMBER_ID as member_i1_3_,
member0_.INSERT_MEMBER as insert_m2_3_,
member0_.createDate as createda3_3_,
member0_.UPDATE_MEMBER as update_m4_3_,
member0_.lastModifiedDate as lastmodi5_3_,
member0_.USERNAME as username6_3_,
member0_.TEAM_ID as team_id7_3_
from
Member member0_
Hibernate:
select
team0_.TEAM_ID as team_id1_5_0_,
team0_.INSERT_MEMBER as insert_m2_5_0_,
team0_.createDate as createda3_5_0_,
team0_.UPDATE_MEMBER as update_m4_5_0_,
team0_.lastModifiedDate as lastmodi5_5_0_,
team0_.name as name6_5_0_
from
Team team0_
where
team0_.TEAM_ID=?
Hibernate:
select
team0_.TEAM_ID as team_id1_5_0_,
team0_.INSERT_MEMBER as insert_m2_5_0_,
team0_.createDate as createda3_5_0_,
team0_.UPDATE_MEMBER as update_m4_5_0_,
team0_.lastModifiedDate as lastmodi5_5_0_,
team0_.name as name6_5_0_
from
Team team0_
where
team0_.TEAM_ID=?
|
📡 원인
1. Member 객체를 조회하기 위한 쿼리 발생
2. FetchType.EAGER의 경우, 필드값이 채워져 있어야하므로,
- TeamA를 가져오기 위한 Query1
- TeamB를 가져오기 위한 Query2
📰 해결 방법
1. FetchType.LAZY 선언
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Entity
public class Member extends BaseEntity {
// 생략
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
// 생략
}
|
🖨️발생한 쿼리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Hibernate:
/* select
m
from
Member m */ select
member0_.MEMBER_ID as member_i1_3_,
member0_.INSERT_MEMBER as insert_m2_3_,
member0_.createDate as createda3_3_,
member0_.UPDATE_MEMBER as update_m4_3_,
member0_.lastModifiedDate as lastmodi5_3_,
member0_.USERNAME as username6_3_,
member0_.TEAM_ID as team_id7_3_
from
Member member0_
|
2. fetch join 활용
* fetch join: 연관된 엔티티나 컬렉션을 함께 로딩하여 성능을 최적화하는 기능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Entity
public class Member extends BaseEntity {
// 생략
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
// 생략
}
|
🖨️발생한 쿼리
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
|
Hibernate:
/* select
m
from
Member m
join
fetch m.team */ select
member0_.MEMBER_ID as member_i1_3_0_,
team1_.TEAM_ID as team_id1_5_1_,
member0_.INSERT_MEMBER as insert_m2_3_0_,
member0_.createDate as createda3_3_0_,
member0_.UPDATE_MEMBER as update_m4_3_0_,
member0_.lastModifiedDate as lastmodi5_3_0_,
member0_.USERNAME as username6_3_0_,
member0_.TEAM_ID as team_id7_3_0_,
team1_.INSERT_MEMBER as insert_m2_5_1_,
team1_.createDate as createda3_5_1_,
team1_.UPDATE_MEMBER as update_m4_5_1_,
team1_.lastModifiedDate as lastmodi5_5_1_,
team1_.name as name6_5_1_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
|
cf. @ManyToOne, @OneToOne: default - FetchType.EAGER
▶ FetchType.LAZY 필수 기재
'Java > JPA' 카테고리의 다른 글
[JPA_Basic] 값 타입과 불변 객체 (0) | 2023.09.14 |
---|---|
[JPA_Basic] Cascade (0) | 2023.09.11 |
[JPA_Basic] Proxy (0) | 2023.09.08 |
[JPA_Basic] @MappedSuperclass (0) | 2023.09.05 |
[JPA_Basic] 상속관계 매핑 (0) | 2023.08.23 |