๋ค์ด๊ฐ๊ธฐ ์
์ด์ ์๊ฐ์ ์์๋ดค๋ N+1 ํด๊ฒฐ๋ฒ์ ์ด์ด์ FetchJoin์ ์ด์ฉํด์ ํด๊ฒฐํ ์ ์์์ต๋๋ค. ํ์ง๋ง Fetch Join์ด๋ผ๊ณ ๋ค ํด๊ฒฐํ ์ ์๋ ๊ฒ์ ์๋๋๋ค. ์ด๋ฒ ์๊ฐ์๋ Fetch Join์ ์ฌ์ฉํ์ ๋ ์ด๋ ํ ์ฌ์ด๋ ์ดํํธ๊ฐ ์๋์ง ์์๋ณด๊ณ ๊ทธ ํด๊ฒฐ์ฑ ์ ๋ํด์ ์์๋ด ๋๋ค.
1. FetchJoin, EntityGraph ์ฌ์ฉ ์ Pagination์ ์ฌ์ฉํ ์ ์๋ค.
FetchJoin๊ณผ EntityGraph ๋ ๋ค ๋์ผํ ์ฆ์์ด ๋ฐ์ํฉ๋๋ค. Fetch Join๋ง ํ ์คํธ๋ฅผ ํด๋ณด๊ฒ ์ต๋๋ค.
@Query(
value = "select t from Team t join fetch t.members",
countQuery = "select count(t) from Team t"
)
List<Team> findTeamsFetchJoin(Pageable pageable);
@DisplayName("๋ชจ๋ ํ์ ํ ๋, ํ์ด์ง๋ค์ด์
์ด ์๋๋ค.")
@Test
void team_findAll_Pagination_test() {
clearPersistenceContext();
System.out.println("----------team_findAll_test start-----------");
List<Team> teamList = teamRepository.findTeamsFetchJoin(PageRequest.of(0, 1));
assertThat(teamList).hasSize(1);
System.out.println("----------team_findAll_test mid-----------");
teamList.stream()
.map(Team::getMembers)
.map(List::stream)
.forEach(memberStream -> memberStream
.map(Member::getName)
.forEach(System.out::println)
);
System.out.println("----------team_findAll_test end-----------");
}
๋ก๊ทธ๋ฅผ ์ดํด๋ณด๋ฉด LIMIT ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ง ์๊ณ , firstResult/maxResults specified with collection fetch; applying in memory
๋ผ๋ ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
์ด ๋ง์ ์ฆ์จ, Full Scanํด์ ์ ๋ถ ๋ค ๋ค๊ณ ์์ ์ ํ๋ฆฌ์ผ์ด์ ๋จ์์ ๋ฉ๋ชจ๋ฆฌ ์์ ์ฌ๋ ค๋๊ณ ํ์ด์ง๋ค์ด์ ์ฒ๋ฆฌ๋ฅผ ํ๋ค๋ ๋ป์ ๋๋ค. ๋ง์ฝ ๋ช์ฒ๋ง๊ฑด์ด๋ผ๋ฉด ์ด๋ป๊ฒ ๋ ๊น์? OOM(OutOfMemory)์ด ๋ฐ์ํด์ ์ฅ์ ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ๋งค์ฐ ํฝ๋๋ค!
๊ทธ๋ ๋ค๋ฉด ์ด๋ป๊ฒ ํด๊ฒฐํด์ผ ํ ๊น์? ๋ฉ๋ชจ๋ฆฌ ์์ ์ฌ๋ ค๋๊ณ Pagination์ ํ๋ค๋์ ์ ๋๋ฌด๋ ์น๋ช ์ ์ด๊ธฐ ๋๋ฌธ์ N+1์ ์ด์ ์ ํฌ๊ธฐํ๋๊ฒ ๋ง๋ค๊ณ ์๊ฐํฉ๋๋ค. ํ์ง๋ง ์ด๊ฑด ์ํฉ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค.
- ๋ง์ฝ Pagination์ ์ฌ์ฉํ์ง ์๋๋ค๋ฉด, FetchJoin or EntityGraph๋ก N+1 ํด๊ฒฐ
- ๋ง์ฝ Pagination์ ์ค์์ ํ๋ค๋ฉด, N+1 ํฌ๊ธฐ (ํ์ง๋ง Batch Size๋ฅผ ์ด์ฉํ๋ค๋ฉด N+1์ ๋ํด ์ต์ํ์ ์ฑ๋ฅ์ ๋ณด์ฅ์ํฌ ์ ์์ต๋๋ค)
์๋์์๋ ํ์์ ๋ฐฉ๋ฒ์ ์ดํด๋ด ๋๋ค.
ํด๊ฒฐ๋ฐฉ๋ฒ - Fetch Join ์ ๊ฑฐ
์ผ๋จ Fetch Join์ ํฌ๊ธฐํด์ผํ๋ ๊ฒ์ ๋ถ๊ฐํผํฉ๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ findAll(Pageable) ๋ฉ์๋๋ฅผ ์ด์ฉํ๊ฒ ์ต๋๋ค.
/**
* Returns a {@link Page} of entities meeting the paging restriction provided in the {@link Pageable} object.
*
* @param pageable the pageable to request a paged result, can be {@link Pageable#unpaged()}, must not be
* {@literal null}.
* @return a page of entities
*/
Page<T> findAll(Pageable pageable);
@DisplayName("๋ชจ๋ ํ์ ์กฐํํ ๋, ๊ธฐ๋ณธ์ ๊ณต findAll + Pageable์ Limit ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.")
@Test
void team_default_findAll_Pagination_test() {
clearPersistenceContext();
System.out.println("----------team_findAll_test start-----------");
Page<Team> result = teamRepository.findAll(PageRequest.of(0, 1));
List<Team> teamList = result.getContent();
assertThat(teamList).hasSize(1);
System.out.println("----------team_findAll_test mid-----------");
teamList.stream()
.map(Team::getMembers)
.map(List::stream)
.forEach(memberStream -> memberStream
.map(Member::getName)
.forEach(System.out::println)
);
System.out.println("----------team_findAll_test end-----------");
}
๋ค ์ ์์ ์ผ๋ก offset ์ฟผ๋ฆฌ๋ฅผ ํตํด pagination์ ์ฒ๋ฆฌํ๊ณ ์์ต๋๋ค.
๊ทธ๋ ๋ค๋ฉด fetch join์ ์ ๊ฑฐํ @Query๋ฅผ ์ฌ์ฉํ๋ฉด Pagination์ด ์ ๋์ํ๋์ง ๋ณผ๊น์?
@Query(
value = "select t from Team t",
countQuery = "select count(t) from Team t"
)
List<Team> findTeamsWithoutFetchJoin(Pageable pageable);
@DisplayName("๋ชจ๋ ํ์ ์กฐํํ ๋, Fetch Join ์ ๊ฑฐ + Pageable์ Limit ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.")
@Test
void team_findAll_Exclusive_FetchJoin_with_Pagination_test() {
clearPersistenceContext();
System.out.println("----------team_findAll_test start-----------");
Page<Team> result = teamRepository.findTeamsWithoutFetchJoin(PageRequest.of(0, 1));
List<Team> teamList = result.getContent();
assertThat(teamList).hasSize(1);
System.out.println("----------team_findAll_test mid-----------");
teamList.stream()
.map(Team::getMembers)
.map(List::stream)
.forEach(memberStream -> memberStream
.map(Member::getName)
.forEach(System.out::println)
);
System.out.println("----------team_findAll_test end-----------");
}
์ง์ ๋ง๋ ์ฟผ๋ฆฌ๋ ์ ์์ ์ผ๋ก Pagination์ด ๋์ํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. (์ถ๊ฐ ํ: Batch Size๋ฅผ ์ด์ฉํ๋ค๋ฉด N+1์ ๋ํด ์ต์ํ์ ์ฑ๋ฅ์ ๋ณด์ฅ์ํฌ ์ ์์ต๋๋ค)
Limit ๊ฒฐ์ ๋๋ ์์
SimpleJpaRepository์์ ํ์ด์ง ์์ฒญ์ด ์์ผ๋ฉด offSet๊ณผ MaxResults๋ฅผ ์ง์ ํด์ฃผ๊ณ ์์ต๋๋ค. ๊ทธ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ์ค์ ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆด ๋ Offset๊ณผ First or Top์ ์ด์ฉํด์ Limit๋ฅผ ๊ตฌํํฉ๋๋ค. (์์ธํ ๊ธ์ ๋ค์์ ์ฐธ์กฐ)
๋ฒ์ธ - ~ToOne
๊ด๊ณ์์๋ ํ์ด์ง ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค.
์์์ Fetch Join + Pagination์ ์ฌ์ฉํ๋ฉด ์๋๋ ์ผ์ด์ค๋ค์ ๋ชจ๋ ~ToMany
๊ด๊ณ์ผ ๋ ์
๋๋ค. ์๋์์ ์๊ฐ๋๋ฆฌ๋ ๊ฒ์ ๊ทผ๋ณธ์ ์ธ ํด๊ฒฐ๋ฐฉ๋ฒ์ ์๋์ง๋ง, ~ToOne
๊ด๊ณ์์๋ ํ์ด์ง ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ ํ
์คํธํ๋ ๊ฒ์
๋๋ค.
@Query(value = "select m from Member m join fetch m.team")
List<Member> findMembersFetchJoin(Pageable pageable);
@DisplayName("~ToOne๊ด๊ณ์์๋ ํ์ด์ง์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค")
@Test
void member_findAll_Pagination_test() {
clearPersistenceContext();
System.out.println("----------member_findAll_test start-----------");
List<Member> memberList = memberRepository.findMembersFetchJoin(PageRequest.of(0, 2));
assertThat(memberList).hasSize(2);
System.out.println("----------member_findAll_test mid-----------");
memberList.stream()
.map(Member::getTeam)
.map(Team::getName)
.forEach(System.out::println);
System.out.println("----------member_findAll_test end-----------");
}
๋ณด์๋ ๊ฒ๊ณผ ๊ฐ์ด ~ToOne
๊ด๊ณ์์๋ ๋ฌธ์ ์์ด ์กฐํ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
2. MultipleBagFetchException - ํ๋์ ์ํฐํฐ์์ 2๊ฐ ์ด์์ ์ปฌ๋ ์
์ ์กฐํํ ๋ (~ToMany
๊ด๊ณ)
์ ERD ์ฒ๋ผ Team์ด members, sponsors๋ฅผ ๊ฐ๋ ๊ตฌ์กฐ์ ๋๋ค.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Team {
...
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Member> members = new ArrayList<>();
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Sponsor> sponsors = new ArrayList<>();
}
public interface TeamRepository extends JpaRepository<Team, Long> {
...
@Query(value = "select t from Team t join fetch t.members join fetch t.sponsors")
Page<Team> findTeamsFetchJoinTwoCollection(Pageable pageable);
}
@DisplayName("๋ ์ด์์ ์ปฌ๋ ์
์ ํ์น์กฐ์ธํ ์ ์๋ค.")
@Test
void Over_Two_collection_fetchJoin_Not_Allowed() throws Exception {
System.out.println("--------Over_Two_collection_fetchJoin_Not_Allowed START-------------");
// when
List<Team> result = teamRepository.findTeamsFetchJoinTwoCollection(PageRequest.of(0, 3)).getContent();
assertThat(teams).hasSize(3);
// then
System.out.println("--------Over_Two_collection_fetchJoin_Not_Allowed MID-------------");
teams.forEach(team -> {
System.out.println(team.getName());
team.getMembers().stream().map(Member::getName).forEach(System.out::println);
team.getSponsors().stream().map(Sponsor::getName).forEach(System.out::println);
});
System.out.println("--------Over_Two_collection_fetchJoin_Not_Allowed END-------------");
}
๋ ์ด์์ ์ปฌ๋ ์
์ Fetch Join ํ๋ ค๋ cannot simultaneously fetch multiple bags
์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ด๋ป๊ฒ ํ๋ฉด MultipleBagFetchException์ ํผํ ์ ์์๊น์?
ํด๊ฒฐ๋ฐฉ๋ฒ 1. Set ์๋ฃํ์ผ๋ก ๋ณ๊ฒฝ
๋ ์ด์์ ์ปฌ๋ ์ ์ ์กฐํํ๋ฉด ์ปฌ๋ ์ X์ปฌ๋ ์ ์ฆ ์นดํ ์์๊ณฑ์ด ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ JPA์์ ์์ธ๋ฅผ ๋์ง๊ฒ ํ์ต๋๋ค. ๊ทธ๋์ ์ค๋ณต์ด ๋ฐ์ํ์ง ์๊ธฐ ์ํด Set ์๋ฃํ์ผ๋ก ๋ณ๊ฒฝํ๋ฉด ํด๊ฒฐํ ์ ์์ต๋๋ค.
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Member> members = new LinkedHashSet<>();
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Sponsor> sponsors = new LinkedHashSet<>();
์์๋ฅผ ๋ณด์ฅํด์ฃผ๊ธฐ ์ํด LinkedHashSet์ ์ฌ์ฉํฉ๋๋ค.
@Query(value = "select t from Team t join fetch t.members join fetch t.sponsors")
List<Team> findTeamsFetchJoinTwoCollection();
๋์ผํ ํ ์คํธ๋ฅผ ๋๋ ค๋ณด๋ฉด ์ ์์ ์ผ๋ก Fetch Join์ด ์ํํ์ฌ N+1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.
์ฃผ์ํ ์ ์ด ์์ต๋๋ค. Fetch Join์ ๊ธฐ๋ณธ์ ์ผ๋ก Inner Join
์ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋ง์ฝ sponsor๋ member๋ฅผ ๊ฐ์ง๊ณ ์์ง ์๋ team์ด ์๋ค๋ฉด? ์๋ง ํ
์คํธ๋ฅผ ๊นจ์ง๊ฒ ๋ ๊ฒ์
๋๋ค. ํ๋ฒ ํ์ธํด๋ณผ๊น์?
@BeforeEach
void setup() {
...
Sponsor sponsor21 = Sponsor.builder().name("sponsor21").build();
Sponsor sponsor22 = Sponsor.builder().name("sponsor22").build();
Team team2 = Team.builder().name("team2").build();
team2.addMember(Member.builder().name("member2-1").build());
team2.addMember(Member.builder().name("member2-2").build());
// team2.addSponsor(sponsor21);
// team2.addSponsor(sponsor22);
...
}
ํ ์คํธ์ฉ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ํ ๋, ์ผ๋ถ๋ฌ team2์ sponsor๋ฅผ ์ถ๊ฐํ๋ ์ฝ๋๋ฅผ ์ฃผ์์ฒ๋ฆฌํ๊ณ ํ ์คํธ๋ฅผ ๋๋ ค๋ณด๊ฒ ์ต๋๋ค.
ํ ์คํธ๊ฐ ๊นจ์ง๊ฒ ๋ฉ๋๋ค. ์ด๋ ์ด์ฐ๋ณด๋ฉด ๋น์ฐํ ๊ฒฐ๊ณผ์ ๋๋ค. Inner Join์ ์ด์ฉํด์ Team์ ์กฐํํด์ค๊ธฐ ๋๋ฌธ์ ํด๋น๋์ง ์๋๋ค๋ฉด ์กฐํ๊ฐ ๋์ง ์์ ๊ฒ ์ ๋๋ค. ๋ง์ฝ member, sponsor๊ฐ ์๋ Team๋ ๊ฐ์ ธ์์ผ ํ๋ค๋ฉด inner join์ด ์๋ left join์ ์ฌ์ฉํ๋ฉด ๋ ๊ฒ์ ๋๋ค.
@Query(value = "select t from Team t left join fetch t.members left join fetch t.sponsors")
Page<Team> findTeamsFetchJoinTwoCollection(Pageable pageable);
left join์ ํตํด์ ๋ชจ๋ ํ์ ๋ค ๊ฐ์ ธ์ค๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๊ณผ์ฐ ์ด ๋ฐฉ๋ฒ์ด ์ต์ ์ผ๊น์? ์ ๋ฐฉ๋ฒ์๋ Pagination์ In Memory์์ ํ๋ค๋ ์ ์ด ์์ง ํด๊ฒฐ๋์ง ์์์ต๋๋ค.
ํด๊ฒฐ๋ฐฉ๋ฒ 2. BatchSize
ํน์ ์๋ฌ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋๋ฉ์ธ ๋ ์ด์ด์ ์๋ฃํ์ ๋ณ๊ฒฝํ๋ ๊ฒ์ ์ข์ ํด๊ฒฐ์ฑ ์ด ์๋๋ผ๊ณ ์๊ฐํฉ๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ List ์๋ฃํ์ ์ฌ์ฉํด์ผํ๊ณ Pagination, ๋ ์ด์์ ์ปฌ๋ ์ ์ Fetch Join์ ํ๋ค๋ ๊ฐ์ ํ์ Batch Size๋ฅผ ์ด์ฉํด ํด๊ฒฐํ ์ ์์ต๋๋ค.
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource(properties = "spring.jpa.properties.hibernate.default_batch_fetch_size=10")
public class Team_MultipleBagEx_Solution2_Test {
...
@DisplayName("๋ ์ด์์ ์ปฌ๋ ์
์ ํ์น์กฐ์ธํ ์ ์๋ค -> Fetch Join(x) + BatchSize๋ฅผ ์ด์ฉํด ํด๊ฒฐ")
@Test
void Over_Two_collection_Not_Used_fetchJoin_and_Use_BatchSize() throws Exception {
System.out.println("--------Over_Two_collection_Not_Used_fetchJoin_and_Use_BatchSize START-------------");
// when
List<Team> teams = teamRepository.findAll(PageRequest.of(0, 3)).getContent();
assertThat(teams).hasSize(3);
// then
System.out.println("--------Over_Two_collection_Not_Used_fetchJoin_and_Use_BatchSize MID-------------");
teams.forEach(team -> {
System.out.println(team.getName());
team.getMembers().stream().map(Member::getName).forEach(System.out::println);
team.getSponsors().stream().map(Sponsor::getName).forEach(System.out::println);
});
System.out.println("--------Over_Two_collection_Not_Used_fetchJoin_and_Use_BatchSize END-------------");
}
}
@TestPropertySource(properties = "spring.jpa.properties.hibernate.default_batch_fetch_size=10")
๋ฅผ ์ด์ฉํด์ ํด๋น ํ
์คํธ์ BatchSize๋ฅผ 10์ผ๋ก ์ค์ ํ์์ต๋๋ค.
๋ก๊ทธ๋ฅผ ์ดํด๋ณด๋ฉด, Paging์ด ์ ์ฒ๋ฆฌ๋์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ Team์ ์ํ member, sponsor๋ฅผ ์ฌ์ฉํ ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์๊ณ , where in
์ ์ ํตํด Eager Loading์ ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๋ง์ฝ BatchSize๋ฅผ ์ ์ฉํ์ง ์๊ณ ํ ์คํธ๋ฅผ ๋๋ ธ๋ค๋ฉด ๋ช๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์๊น์? ์ ์ฒด ํ์ ๊ฐ์๊ฐ 3์ด๋ผ๋ฉด 4์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ ๊ฒ์ ๋๋ค.(N+1 ๋ฌธ์ )
4๊ฐ์ ์ฟผ๋ฆฌ๊ฐ 3๊ฐ๋ก ์ค์ด๋ค์๋๋ฐ 1๊ฐ๋ฐ์ ์ฐจ์ด๊ฐ ์๋๋ค? ๋ผ๊ณ ์๊ฐํ์ค ์ ์์ต๋๋ค. ํ์ง๋ง ์๋ ํ์ ๊ฐ์ด ํ์ ๊ฐ์๊ฐ ๋ง์์๋ก ์ฟผ๋ฆฌ ์๋ ๋์ ๋๊ฒ ์ค์ด๋ญ๋๋ค.
์ ์ฒด ํ์ ๊ฐ์ | ๋ฐ์ ์ฟผ๋ฆฌ ์ (BatchSize ๋ฏธ์ ์ฉ) |
๋ฐ์ ์ฟผ๋ฆฌ ์ (BatchSize=1000 ์ ์ฉ) |
์ฟผ๋ฆฌ ์ฑ๋ฅ |
3 | 4 | 3 | 25% ๊ฐ์ |
1,000 | 1,001 | 3 | 99.7% ๊ฐ์ |
10,000 | 10,001 | 21 | 99.8% ๊ฐ์ |
100,000 | 100,001 | 201 | 99.8% ๊ฐ์ |
์ต๋ 1000 ๋ถ์ 1๋ก ์ฟผ๋ฆฌ์๋ฅผ ์ค์ผ ์ ์์ต๋๋ค. Fetch Join์ ์ฌ์ฉํ ์ ์๋ค๋ฉด Fetch Join์ ์ฌ์ฉํ๊ณ , ๋ง์ฝ ๋ ์ด์์ ์ปฌ๋ ์ ์ ์กฐํํด์ผํ๋ค๋ฉด ๊ฐ์ฅ ๋ง์ ์ปฌ๋ ์ ์ Fetch Join์ ์ ์ฉํ๊ณ ๋๋จธ์ง ์ปฌ๋ ์ ์ ๋ํด์๋ Batch Size๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์ต์ ์ผ ๊ฒ ๊ฐ์ต๋๋ค.
3. FetchJoin ๋์์ ๋ณ์นญ์ ์ฌ์ฉํ์ง ๋ง์.
JPA ํ์ค ์คํ์๋ Fetch Join ๋์์ ๋ณ์นญ์ด ์์ต๋๋ค. ํ์ง๋ง JPA ๊ตฌํ์ฒด์ธ ํ์ด๋ฒ๋ค์ดํธ๋ ๋ณ์นญ์ ํ์ฉํฉ๋๋ค. ์ฆ ๋ณ์นญ์ ์ฌ์ฉ์ ํ ์ ์์ง๋ง ์ฌ๋ฌ ๋ฌธ์ ์ ๋ค์ด ์๊ธฐ ๋๋ฌธ์ ์กฐ์ฌํด์ ์ฌ์ฉํด์ผํฉ๋๋ค.
Fetch Join ๋์์ ON์ ์์ ์ฌ์ฉํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
@DisplayName("join fetch๋ on์ ์ฌ์ฉ์ org.hibernate.query.SemanticException: Fetch join has a 'with' clause (use a filter instead) ๋ฐ์")
@Test
void fetchJoin_NotAllow_On_condition() throws Exception {
System.out.println("--------fetchJoin_NotAllow_On_condition START-------------");
// when
List<Team> teams = em.createQuery("select t from Team t join fetch t.members m on m.name = 'member11'", Team.class).getResultList();
// then
System.out.println("--------fetchJoin_NotAllow_On_condition MID-------------");
teams.forEach(team -> {
team.getMembers().stream().map(Member::getName).forEach(System.out::println);
team.getSponsors().stream().map(Sponsor::getName).forEach(System.out::println);
});
System.out.println("--------fetchJoin_NotAllow_On_condition END-------------");
}
์ ํ ์คํธ๋ฅผ ์คํํ๋ฉด on์ ๋์ ์ filter๋ฅผ ์ฌ์ฉํ๋ผ๊ณ ์๋ฌ๋ฅผ ๋ฐ์์ํต๋๋ค.
๊ทธ๋ผ where ์ ์ ์กฐ๊ฑด์ ๊ฑธ๋ฉด ์๋๋?
์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ ์๊ตฌ์ฌํญ์ด ์๋ค๊ณ ๊ฐ์ ํด๋ณด๊ฒ ์ต๋๋ค.
- Team์ ์กฐํํ๋๋ฐ ํด๋น Team์ ์ํ ๋ชจ๋ Member๋ ํ์ํ ์ํฉ
- ์ฟผ๋ฆฌ ์ต์ ํ๋ฅผ ์ํด Team.members์ ๋ํด์ Fetch Join ์ ์ฉ
- ์ด๋ฆ์ด “member11”๋ก ์์ํ๋ member๋ฅผ ๊ฐ์ ธ์์ผํจ
- ์ ์๊ตฌ์ฌํญ์ผ๋ก ๋ง๋ค์ด์ง ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์
select t from Team t join fetch t.members m where m.name like 'member1%'
๋ง์ฝ team์ ์ํ member๊ฐ member1, member12, member2๋ผ๊ณ ๊ฐ์ ํ์ ๋ ์ ๊ฒฐ๊ณผ๋ member1, member12๊ฐ ๋์ฌ ๊ฒ์ ๋๋ค. ์ด๋ ๊ฒ ๋๋ฉด JPA ์ ์ฅ์์๋ DB์ ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ด ๊นจ์ง๊ฒ ๋๊ณ , ์ต์ ์ ๊ฒฝ์ฐ member2๊ฐ DB์์ ์ญ์ ๋ ์๋ ์์ต๋๋ค.
์ ๋ฆฌํ์๋ฉด JPA์ ์ํฐํฐ๋ DB์ ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ ์ ์งํด์ผ ํ๊ธฐ ๋๋ฌธ์ ์์๋ก ๋ฐ์ดํฐ๋ฅผ ๋นผ๊ณ ์กฐํํ๋ค๋ฉด DB์ ํด๋น ๋ฐ์ดํฐ๊ฐ ์๋ค๊ณ ํ๋จํ๋ ๊ฒ๊ณผ ๋์ผํฉ๋๋ค.
๊ทธ๋ผ ํ์ด๋ฒ๋ค์ดํธ๋ ์ ํ์ฉํ ๊น?
์ ํ์ด๋ฒ๋ค์ดํธ์์๋ Fetch Join ๋์์ ๋ณ์นญ์ ํ์ฉํ ๊น์? ๋ง์ฝ ์กฐํํ๋ ๋ฐ์ดํฐ๊ฐ DB์์ ์ผ๊ด์ฑ ๋ฌธ์ ๊ฐ ์๋ค๋ฉด ์ฌ์ฉํด๋ ๋ฉ๋๋ค!
์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ ์ฟผ๋ฆฌ๋ ์์ ํ๋ค๊ณ ํ ์ ์์ต๋๋ค.
select m
from Member m
join fetch m.team t
where t.name = 'team1'
join fetch ๋์์ธ Team์ ์ด๋ฆ์ ํํฐ๋งํด์ ๊ฐ์ ธ์ค๋ ์ฟผ๋ฆฌ์ ๋๋ค. ๊ฒฐ๋ก ์ ์ผ๋ก ~ToOne ๊ด๊ณ์ธ ์ปฌ๋ ์ ์ ๋ํด์ ํํฐ๋งํ๋ ๊ฒ์ ์์ ํ๋ค๊ณ ํ ์ ์๋๊ฑฐ์ฃ . ํ์ง๋ง ์กฐํ ์ฉ๋๋ก๋ง ์ฌ์ฉํ๋ค๋ ๊ฐ์ ํ์ ๋ง์๋๋ฆฌ๋ ๊ฒ์ ๋๋ค. ์ค๋ฌด์์๋ DB์ ์ผ๊ด์ฑ์ด ๊นจ์ง๋๋ผ๋, ์กฐํ ์ฉ๋๋ก๋ง ์ฌ์ฉํ๋ค๋ฉด Fetch Join ๋์์ ๋ณ์นญ์ ์ฌ์ฉํ์ฌ๋ ๋ฉ๋๋ค. (๋์ 2์ฐจ ์บ์ ๋ฑ๋ฑ ์กฐ์ฌ..)
์ ๋ฆฌํ์๋ฉด..
- Fetch Join
- ํ์ด์ง API๋ฅผ ์ฌ์ฉํ ์ ์๋ค
- ํด๊ฒฐ๋ฐฉ๋ฒ: ํ์ด์ง ํ์ ์: Fetch Join ์ ๊ฑฐ + Batch Size, ๋ถ ํ์ ์: Fetch Join ์ฌ์ฉ
- ๋ ์ด์์ ์ปฌ๋ ์
์ Fetch Join ํ ์ ์๋ค.
- ํด๊ฒฐ๋ฐฉ๋ฒ: ๋ฐ์ดํฐ๊ฐ ๋ง์ ์ชฝ์ Fetch Join ๊ฑธ๊ณ , ๋๋จธ์ง๋ Batch Size๋ฅผ ์ด์ฉํด ์ต์ํ์ ์ฑ๋ฅ ๋ณด์ฅ
- Fetch Join ๋์ ON, Where์ ์ฌ์ฉ ์กฐ์ฌ
- ํด๊ฒฐ๋ฐฉ๋ฒ: Fetch Join ์ ์กฐํ ์ฉ๋๋ก๋ง ์ฌ์ฉํ๋ค๋ฉด OK, ์๋๋ผ๋ฉด ์ ๋ ๊ธ์ง
- ํ์ด์ง API๋ฅผ ์ฌ์ฉํ ์ ์๋ค
์ ๋ฆฌํ๋ค๋ณด๋ ๋ด์ฉ์ด ๋๋ฌด ๊ธธ์ด์ก๋ค์.. JPA๋ ์ ๋ง ์์๋ ๋์ด ์๋ ๊ฒ ๊ฐ์ต๋๋ค..
๋ถ์กฑํ ๊ธ ์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค.
REFERENCES
MultipleBagFetchException ๋ฐ์์ ํด๊ฒฐ ๋ฐฉ๋ฒ
JPA N+1 ๋ฐ์์์ธ๊ณผ ํด๊ฒฐ๋ฐฉ๋ฒ - Yun Blog | ๊ธฐ์ ๋ธ๋ก๊ทธ
JPA ๋ชจ๋ N+1 ๋ฐ์ ์ผ์ด์ค๊ณผ ํด๊ฒฐ์ฑ
fetch join ์ ๋ณ์นญ๊ด๋ จ ์ง๋ฌธ์ ๋๋ค - ์ธํ๋ฐ | ์ง๋ฌธ & ๋ต๋ณ