2023. 12. 15. 23:44ใ๐พBackEnd/๐ฑ Spring
์๋ก
- JPA๋ฅผ ํ์ตํ๋ฉด ๋ฌด์กฐ๊ฑด ๋ฃ๋ ํค์๋๋ N+1 ์ด๋ค. ๋ณดํต ๋ธ๋ก๊ทธ์์ ์๊ฐํ๋ ๋ฐฉ์์ fetch join์ ํตํ์ฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค๊ณ ์ด์ผ๊ธฐํ๋ค.
- ๋ฌผ๋ก ํ๋ฆฐ ๋ฐฉ์์ ์๋๋ค. ํ์ง๋ง ์ค์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค๋ฉด์ N+1 ๋ฌธ์ ๋ฅผ ๋ง์ด ๋ง๋๋ณด๋ฉด์ N+1์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์๋ค.
- ์ํฉ์ ๋ฐ๋ผ์ N+1 ๋ฌธ์ ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ์ ์ ํ๊ฒ ํด๊ฒฐํ๋๊ฒ ์ฑ๋ฅ ์ ํ์ ๋ฌธ์ ์ ๋์ํ ์ ์๋ค๊ณ ์๊ฐํ๋ค.
๊ธ์ ์์ํ๊ธฐ ์ด์ ์ ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํ๋ฉด
1:1 ์ฐ๊ด๊ด๊ณ : Fetch join
Collection ์ฐ๊ด๊ด๊ณ : default_batch_fetch_size
N๊ฐ์ ์ปฌ๋ ์ ์ fetch join์ ํ๋ฉด MultipleBagFetchException์ด ๋ฐ์ํ๋ค.
ํน์ ์ปฌ๋ผ์ ์กฐํํ ๊ฒฝ์ฐ์ join์ ํ๊ณ Projection์ Dto๋ก ๋งคํ์ ํ๋ค.
๋ณธ๋ก
1. ์ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์์ ํ๋์?
- JPA๋ฅผ ์ฒ์ ํ์ตํ๋ฉด ๊น์ํ๋ ๊ฐ์๋ฅผ ๋ณด๋ฉด ์ฒ์์ ๋์ค๋๊ฑด ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ฐ์ฒด์งํฅ ์ธ์ด๊ฐ์ ํจ๋ฌ๋ค์ ์ฐจ์ด๋ฅผ ์ด์ผ๊ธฐํ๋ค. JPA์์ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ์ผ๋ฉด ๋ ํผ๋ฐ์ค๋ฅผ ํตํ์ฌ ๊ด๊ณ๊ฐ ์๋ ๊ฐ์ฒด์ ์ ๊ทผํ ์ ์๋ค. ํ์ง๋ง ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ Select๋ฅผ ํตํด์ผ์ง๋ง ์ ๊ทผ์ ํ๋ค.
1-1.๊ฐ๋จํ ์ํฐํฐ ์ฝ๋
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "MEMBER", uniqueConstraints = {
@UniqueConstraint(name = "MEMBER_EMAIL", columnNames = {"email"}),
})
public class Member extends BaseEntity{
@OneToMany(mappedBy = "member",fetch = FetchType.EAGER)
private List<Request> requests = new ArrayList<>();
}
------------------------------------------------------------------------------------
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class Request {
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
}
- Fetch Type์ ๊ธฐ๋ณธ์ ์ผ๋ก ToMany์์๋ Lazy, ToOne์ Eager๋ก ์ค์ ์ด ๋์ด์ ธ ์์ต๋๋ค.
- ์ผ๋จ ํ์ฌ ์ํฐํฐ๋ฅผ ์ดํด๋ณด๋ฉด ํ์(1) : ๊ฒ์ํ(N)์ผ๋ก ๋์ด์ ธ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด ์ด์ N+1์ ๋ฐ์์ ์์ผ์ ๋ฌธ์ ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
2. N+1 ๋ฌธ์
2-1. Eager์์ N+1 ๋ฌธ์
@BeforeEach
void setUp(){
List<Request> requestArrayList = new ArrayList<>();
IntStream.range(0, 10)
.mapToObj(i -> new Request("title" + i))
.forEach(requestArrayList::add);
requestRepository.saveAll(requestArrayList);
List<Member> memberArrayList = new ArrayList<>();
IntStream.range(0, 10)
.mapToObj(i -> Member.builder()
.name("member" + i)
.requests(requestArrayList)
.build())
.forEach(memberArrayList::add);
memberRepository.saveAll(memberArrayList);
entityManager.clear();
}
@Test
@Transactional
public void fix() throws Exception {
System.out.println("===============================================================");
List<Member> memberList = memberRepository.findAll();
System.out.println("===============================================================");
assertThat(memberList.size()).isNull();
}
๊ฐ๋จํ๊ฒ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑ์ ํ๋ค. ์์ @BeforeEach๋ฅผ ํตํ์ฌ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ธํ ํ๊ณ 10๊ฐ์ ํ์์ ์ ์ฒด๋ฅผ ์กฐํํ๋ ์ฝ๋๋ฅผ ๋ง๋ค์๋ค. ์ด๊ฒ์ ์คํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋ฐ์ํ๋ค.
```ํ์ ์กฐํ
Hibernate: select m1_0.id,m1_0.name from member_table m1_0Request ์กฐํ
Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=?
Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=?
Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=?
Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=?
Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=?
Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=?
Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=?
Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=?
Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=?Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=?
```๋ฐ๋ก N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. ์ฒ์ ํ์์ ์กฐํํ๊ณ ๊ด๋ จ Request๋ฅผ 10๋ฒ ์กฐํํ๋ ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ๋ค.
2-2. Lazy์์ N+1๋ฌธ์
ํ์์ Fetch Type์ Lazy๋ก ๋ณ๊ฒฝํ๊ณ ๋๊ฐ์ด ํ ์คํธ๋ฅผ ์งํํ๋ฉด ํ๋ฒ๋ง ๋ฐ์ํ๋ค.
=============================================================== Hibernate: select m1_0.id,m1_0.name from member_table m1_0 ===============================================================
๊ทธ๋ฌ๋ฉด N+1 ๋ฌธ์ ๋ ํด๊ฒฐ์ด ๋์๋๊ฐ? ์์ง์ ์๋๋ค. ๋ค๋ฅธ ํ ์คํธ ์ฝ๋๋ฅผ ์คํ์ ํด๋ณด๊ฒ ๋ค.
=============================================================== Hibernate: select m1_0.id,m1_0.name from member_table m1_0 Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=? Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=? Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=? Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=? Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=? Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=? Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=? Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=? Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=? Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id=? ===============================================================
@Test @Transactional public void fix() throws Exception { System.out.println("==============================================================="); memberRepository.findAll().stream().flatMap(member -> member.getRequests().stream() .map(Request::getTitle)) .collect(Collectors.toList()); System.out.println("==============================================================="); assertThat(memberRepository.findAll().size()).isNull(); }
> ์ ๋ฆฌ
Eager๋ฅผ ์ฌ์ฉํ๋ Lazy๋ฅผ ์ฌ์ฉํ๋ ๊ฒฐ๊ตญ ๋์ผํ๊ฒ ๋ฐ์ํ๋ค. Lazy๋ฅผ ์ฌ์ฉํ๋ฉด ๋จ์ง ํ๋ก์ ๊ฐ์ฒด๋ก ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ N+1์ด ๋ฐ์ดํฐ ์ฌ์ฉํ๋ ์์ ์ผ๋ก ๋ฏธ๋ฃจ๋ ๊ฒ์ด์ง ํด๊ฒฐํ๋ ๊ฒ์ ์๋๋ค.
> N+1 ๋ฐ์ํ๋ ์ด์
jpaRepository์ ์ ์ํ ์ธํฐํ์ด์ค ๋ฉ์๋๋ฅผ ์คํํ๋ฉด JPA๋ ๋ฉ์๋ ์ด๋ฆ์ ๋ถ์ํด์ JPQL์ ์์ฑํ์ฌ ์คํํ๊ฒ ๋๋ค. JPQL์ SQL์ ์ถ์ํํ ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ ์ธ์ด๋ก์ ํน์ SQL์ ์ข
์๋์ง ์๊ณ ์ํฐํฐ ๊ฐ์ฒด์ ํ๋ ์ด๋ฆ์ ๊ฐ์ง๊ณ ์ฟผ๋ฆฌ๋ฅผ ํ๋ค.
<br/>
## 3. N+1 ๋ฌธ์ ํด๊ฒฐ
### 3-1. Fetch Join + Lazy Loading
- ๋ณดํต JPA๋ฅผ ์ฒ์ ์ฌ์ฉํ๋ฉด N+1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ์์ Fetch Join์ ์ฌ์ฉํด์ ํด๊ฒฐํ๋ค. ํ์ง๋ง ๋ง๋ฅ์ ์๋๋ค. ์ผ๋จ ๊ฐ๋จํ ์ฝ๋๋ฅผ ๋ณด๊ณ ์ฅ๋จ์ ์ ๋ํด์ ์ค๋ช
์ ํ๊ฒ ๋ค.
- ์ผ๋จ ๊ธฐ์กด์ ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ฒ ๋ค. ์์ Lazy๋ฅผ ์ฒ๋ฆฌํ์ฌ๋ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์์ ํ์๋ค. ์ด๊ฑธ Lazy Loading, Fetch Join์ ํตํ์ฌ ํด๊ฒฐํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
<br/>
- Repository
```java
@Query("select distinct m from Member m join fetch m.requests")
List<Member>findAllRelatedRequest();
- ํ ์คํธ ์ฝ๋
@Test @Transactional public void fix() throws Exception { System.out.println("==============================================================="); memberRepository.findAllRelatedRequest().stream().flatMap(member -> member.getRequests().stream() .map(Request::getTitle)) .collect(Collectors.toList()); System.out.println("==============================================================="); assertThat(memberRepository.findAll().size()).isNull(); }
- ๊ธฐ์กด์ findAll์์ ์๋กญ๊ฒ ๋ง๋ findAllRelatedRequest๋ก ๋ณ๊ฒฝ์ ํ์๋ค. ์ด๋ ๊ฒ ๋ณ๊ฒฝ์ ํ๋ ๊ธฐ์กด์ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ฟผ๋ฆฌ์์ ํ๋ฐฉ ์ฟผ๋ฆฌ๋ก ๋ณ๊ฒฝ์ด ๋์๋ค.
===============================================================
Hibernate: select distinct m1_0.id,m1_0.name,r1_0.member_id,r1_0.id,r1_0.title
from member_table m1_0
join request r1_0 on m1_0.id=r1_0.member_id
===============================================================
Fetch Join์ ๋ํ ํ๊ณ
1. Fetch join๊ณผ ์ผ๋ฐ Join์ ์ฐจ์ด ( ํจ๋ฌ๋ค์ ๋ถ์ผ์น ์ค์ฌ์ค )
N+1 ๋ฌธ์ ๋ฅผ fetch join์ผ๋ก ํด๊ฒฐํ ์ ์๋ค. ์ผ๋จ fetch join์ ๋ํด์ ์ค๋ช ํ์๋ฉด ์ฐ๋ฆฌ๊ฐ ์๊ณ ์๋ join๊ณผ ์ฐจ์ด๊ฐ ์๋ค.
- fetch join์ orm์์ ์ฌ์ฉํ๋ฉฐ ๋๋น ์คํค๋ง๋ฅผ ์ํฐํฐ๋ก ์๋ ๋ณํ > ์์์ฑ ์ปจํ ์คํธ์ ์์ํ๋ฅผ ํด์ค๋ค.
- ์ด๋ฌํ ํน์ง ๋๋ถ์ fetch join์ ํด์ ๊ฐ์ ธ์จ ์ฐ๊ด ๊ด๊ณ๊ฐ ์๋ 1์ฐจ ์บ์์ ์ ์ฅ์ด ๋๊ณ ๋ค์ ์กฐํ๋ฅผ ํ์ฌ๋ ์ฟผ๋ฆฌ๋ฅผ ์ํํ์ง ์๋๋ค.
- ํ์ง๋ง ์ผ๋ฐ join์ฟผ๋ฆฌ๋ ๋จ์ํ sql์์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๊ฐ๋ ์ด๊ธฐ ๋๋ฌธ์ ์์์ฑ ์ปจํ ์คํธ์ ๊ด๋ จ์ด ์๋ค. ์ด๊ฒ์ด ํจ๋ฌ๋ค์์ ์ฐจ์ด์ด๋ฉฐ fetch join์ ์ด๋ฅผ ์ค์ฌ์ฃผ๋ ์ญํ์ ํ๋ค.
2. Collection ์ฐ๊ด๊ด๊ณ Fetch Join์ ๋ฐ์ดํฐ ๋ปฅํ๊ธฐ (Distinct ์ถ๊ฐ)
- ์์ ๊ทธ๋ฆผ์ ๋ณด๋ฉด 1:n ๊ด๊ณ๊ฐ ๋์ด์ ธ ์๋ค. ์์ ์ฌ์ง์ฒ๋ผ ๋ฐ์ดํฐ๋ฅผ ์ค๋ณต ๋์ด ์กด์ฌํจ์ ๋๋ค. ์ด ๋๋ฌธ์ fetch join์ ํ๋ฉด n๊ฐ ๋งํผ ๊ด๊ณ๊ฐ ์์ฑ๋๋ค.
- ์ด ๋๋ฌธ์ distinct์ ์ ํ์ฉ์ ํด์ผ๋ฉ๋๋ค.
- ๊ทธ๋ฌ๋ฉด ์ฌ๊ฑฐ์ ๊ณ ๋ฏผํ ๋ถ๋ถ์ด ์๋ค.
SQL Distinct / JPQL Distinct ์ฐจ์ด
- SQL์์ Distinct์ ์ DB์์ ์ํ๋๋ฉฐ JOIN ๋ฐ์ํ ๋ฐ์ดํฐ ํํ์์ ๊ฐ ROW๋ฅผ ๋น๊ตํ์ฌ ๋ค๋ฅธ ๊ฒฝ์ฐ๋ง ๋จ๊ธด๋ค. ํ์ง๋ง JPA์ Distinct๋ ์ํฐํฐ๊ฐ์ฒด์ ๋ํด์ Distinct๋ฅผ ์ํ์ ํฉ๋๋ค.
3. N๊ฐ ์ปฌ๋ ์ Fetch Join์ MultipleBagFetchException
- ์ฒ์์ ์ปฌ๋ ์ ์ 2๊ฐ๋ฅผ FETCH JOIN์ ํ๋ฉด ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
- ์ฆ ํ๋๋ง FETCH JOIN์ ํด์ผํฉ๋๋ค.
- ํ ์คํธ๋ฅผ ์ํด์ ์ํฐํฐ๋ฅผ ํ๋ ์ถ๊ฐ๋ฅผ ์ํค๊ณ ์ค๋ฅ๋ฅผ ์ดํด๋ณด๊ฒ ๋ค.
@OneToMany(mappedBy = "member",fetch = FetchType.LAZY)
private List<Request> requests = new ArrayList<>();
@OneToMany(mappedBy = "member",fetch = FetchType.LAZY)
private List<Notice> notices = new ArrayList<>();
@Query("select distinct m from Member m join fetch m.requests join fetch m.notices")
List<Member>findAllRelatedRequest();
- ์ด๋ ๊ฒ ๊ด๊ณ๋ฅผ ๋งบ๊ณ ์ปฌ๋ ์ 2๊ฐ๋ฅผ fetch join์ ํ๊ฒ๋๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.example.localcache.domain.Member.notices, com.example.localcache.domain.Member.requests] at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:371) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:246)
- ๊ทธ๋ฌ๋ฉด ์ด๋ ๊ฒ ์ปฌ๋ ์ ์ ์ด๋ป๊ฒ ๋ฌธ์ ๋ฅผ ํด๊ฒฐ์ ํด์ผ๋๋๊ฐ?? ์ด๊ฒ์ batch size๋ก ํด๊ฒฐํ๋ฉด ๋๋ค. ๋ฐ์์ ์ดํด๋ณด๋ฉด ๋๋ค.
4. ํ์ด์ง ์ ํ(Out Of Memory)
fetch join์ ํ์ฌ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ํ์ด์ง์ ์ฒ๋ฆฌํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
์๋ํ๋ฉด ์ฟผ๋ฆฌ ์ํํ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ ์ดํ๋ฆฌ์ผ์ด์ ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ ค์ ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ์ํ์ ํ๊ธฐ ๋๋ฌธ์ด๋ค. ๋ง์ฝ์ ๋ง๊ฑด์ ๊ฐ์ ธ์ค๋ฉด ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฆฌ๊ฒ ๋๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๊ฐ ๋ฐ์์ ํฉ๋๋ค.
2022-01-16 12:37:18.309 WARN 39536 --- [ main] o.h.h.internal.ast.QueryTranslatorImpl : HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
3-2. default_batch_fetch_size, @BatchSize
- Lazy Loading์ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์กฐํํ ๋ where in์ ๋ก ๋ฌถ์ด์ ํ๋ฒ์ ์กฐํ ํ ์ ์๊ฒ ํด์ฃผ๋ ์ต์ ์ ๋๋ค. yml์ ์ ์ญ ์ต์ ์ผ๋ก ์ ์ฉํ ์ ์๊ณ @BatchSize๋ฅผ ํตํด ์ฐ๊ด๊ด๊ณ BatchSize๋ฅผ ๋ค๋ฅด๊ฒ ์ ์ฉํ ์ ์์ต๋๋ค.
batchsize๋ ๋ช์ผ๋ก ์ ์ฉ์ ํด์ผ๋๋์??
์ผ๋ฐ์ ์ผ๋ก 100~1000์ผ๋ก ์ค์ ์ ํฉ๋๋ค. ํ์ง๋ง dbms์ ๋ฐ๋ผ์ where in์ ์ 1000๊น์ง ์ ํํ๋ ๊ฒฝ์ฐ๊ฐ ์๊ธฐ ๋๋ฌธ์ 1000์ด์์ ์ค์ ํ์ง ์๋๋ค. ๊ทธ๋ ๋ค๊ณ ๋๋ฌด ํฌ๊ฒํ๋ฉด was์์ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋ฉํ๋์ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ์๋น์ค์ ๋ง๊ฒ ์ ์ ํ๊ฒ ์ค์ ์ ํด์ผํฉ๋๋ค.
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
show_sql: true
default_batch_fetch_size: 100
@BatchSize(size = 10)
@OneToMany(mappedBy = "member",fetch = FetchType.LAZY)
private List<Request> requests = new ArrayList<>();
===============================================================
Hibernate: select m1_0.id,m1_0.name from member_table m1_0
Hibernate: select r1_0.member_id,r1_0.id,r1_0.title from request r1_0 where r1_0.member_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
===============================================================
Hibernate: select m1_0.id,m1_0.name from member_table m1_0
Fetch join์ ํ๊ณ๋ฅผ Batch Size๋ก ํด๊ฒฐ
- ์ปฌ๋ ์ fetch join์ paging ๋ฌธ์ ๋ ์ฌ๋ฌ๊ฐ ์ปฌ๋ ์ ์ fetch join์ ํ ์ ์๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.
- ์ฟผ๋ฆฌ ์๋ก๋ fetch join์ด ํ๋ฐฉ์ผ๋ก ๋๊ฐ๊ธฐ ๋๋ฌธ์ ์ ๋ฆฌํ๋ค. ํ์ง๋ง batch size๋ in์ ์ ํ๊ณ ์ฌ์ด์ฆ์ ๋ฐ๋ผ์ ๋ช๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์๋ค.
- ๋ฐ์ดํฐ ์ ์ก๋ ๊ด์ ์์๋ Batch Size๊ฐ ์ ๋ฆฌํฉ๋๋ค. Fetch Join์ Join์ ํ๊ณ ๋์ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ์ค๋ณต ๋ฐ์ดํฐ๋ฅผ ๋ง์ด ๊ฐ์ ธ์์ผํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
๊ฒฐ๋ก
- ์ปฌ๋ ์ n๊ฐ ์กฐํ : batch size ํด๊ฒฐ
- ์ฟผ๋ฆฌ ๊ฐ์ : fetch join์ด 1๊ฐ์ ์ฟผ๋ฆฌ๋ก ํด๊ฒฐ > batch size๋ in์ + ์ถ๊ฐ์ ์ธ ์ฟผ๋ฆฌ ( ์ต์ ํ)
- ๋ฐ์ดํฐ ์ ์ก : fetch join์ join์ ํ๊ณ ์ค๋ณต ๋ฐ์ดํฐ๋ฅผ ๋ง์ด ๊ฐ์ ธ์ด batch size๊ฐ ์ ๋ฆฌ
3-3. @EntityGraph
- EntityGraph๋ ์ด๋ ธํ ์ด์ ๋ฐฉ์์ผ๋ก ํธํ๊ฒ N+1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค. ํ์ง๋ง ์ฌ๊ธฐ์ trade off๊ฐ ๋ฐ์ํ๋ค. ์ฌ์ค์ Lazy Loading์ Eager Loading์ผ๋ก ๋ถ๋ถ์ ์ผ๋ก ์ ํํ๋ ๊ธฐ๋ฅ์ ๋๋ค.
- ์ฌ๋ฌ 1:N ์ฐ๊ด๊ด๊ณ๋ฅผ ํ๋ฒ์ Joinํด ์ฌ ์ ์์ต๋๋ค. FetchJoin์ ๊ฒฝ์ฐ 1๊ฐ์ Collection๊น์ง๋ง ๊ฐ์ด Joinํ์ฌ ์กฐํํ ์ ์์ต๋๋ค.
@EntityGraph(attributePaths = {"requests"})
@Query("select o from Member o")
List<Member>findAllEntityGraph();
- ์ฌ๊ธฐ์ fetch join๊ณผ ์ฐจ์ด์ ์ EntityGraph๋ left outer join์ผ๋ก ๊ฐ์ ธ์ค๊ณ ์ปฌ๋ ์ fetch join์ ํด๊ฒฐํ ์ ์์ง๋ง ์ค๋ณต์ ์ธ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ์ฃผ์๋ฅผ ํด์ผ๋๋ค. ์ด๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ปฌ๋ ์ ์ ์๋ฃ๊ตฌ์กฐ๋ฅผ set ๋๋ jpql์์ distinct ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ค.
3-4. join์ฐ์ฐ > Projectionํ์ฌ ํน์ ์ปฌ๋ผ๋ง Dto๋ก ์กฐํ
select new ํจํค์ง ๊ฒฝ๋ก.Dto(์ํ๋ ํ๋)
from Member m
join m.request r
where m.id=r.id
- ์ฅ์ ์ผ๋ก๋ ๋ง์ ์ปฌ๋ผ์์ projectionํ์ฌ ํน์ ์ปฌ๋ผ๋ง ์กฐํ๋ฅผ ํ ์ ์๋ค. ์ปค๋ฒ๋ง ์ธ๋ฑ์ค๋ก ์ฒ๋ฆฌ๋ ์ ์๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ์ ์ธ ์ด์ ์ ๊ฐ์ ธ์ฌ ์ ์๋ค.
- ํ์ง๋ง ๋จ์ ์ผ๋ก๋ ์์์ฑ ์ปจํ ์คํธ์ ๋ฌด๊ดํ๊ฒ ๋์ํ๊ณ repository๊ฐ dto์ ์์กดํ๊ธฐ ๋๋ฌธ์ dao ๋ณ๊ฒฝ์ด ํ์ํ๋ค.
๊ฒฐ๋ก
๊ธ์ ์์ํ๊ธฐ ์ด์ ์ ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํ๋ฉด
1:1 ์ฐ๊ด๊ด๊ณ : Fetch join
Collection ์ฐ๊ด๊ด๊ณ : default_batch_fetch_size
N๊ฐ์ ์ปฌ๋ ์ ์ fetch join์ ํ๋ฉด MultipleBagFetchException์ด ๋ฐ์ํ๋ค.
ํน์ ์ปฌ๋ผ์ ์กฐํํ ๊ฒฝ์ฐ์ join์ ํ๊ณ Projection์ Dto๋ก ๋งคํ์ ํ๋ค.
์ฐธ๊ณ
https://junhyunny.github.io/spring-boot/jpa/jpa-fetch-join-paging-problem/
'๐พBackEnd > ๐ฑ Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[ Swagger ] Swagger UI๋ฅผ ํตํด API ๋ช ์ธ์ Postman ๊ณต์ (0) | 2023.04.21 |
---|---|
Mockito๋ ์๊ณ ํ ์คํธ ์ฝ๋ ์์ฑํ์ (0) | 2023.03.24 |
์ํธํ Bcrypt์ ๋ํ ์ฌ์ธต ๋ถ์ (2) | 2023.03.24 |
[ Object Mapper] Dozer์ ๋ํด์ ๋ค์ด๋ ๋ดค๋? (0) | 2023.03.14 |
[ RFC ] ๊ณต์ ๋ฌธ์๋ฅผ ํตํ์ฌ Delete์ Status๋ฅผ ์์๋ณด์ (0) | 2023.03.12 |