-
[SpringBoot] N+1 ์ ๊ณ ๋ คํ์ฌ ํ์ด์ง ์ฟผ๋ฆฌ ์์ฑํ๊ธฐSpringBoot 2024. 1. 1. 13:43
๐ญ ๋ค์ด๊ฐ๋ฉฐ
์๋ก์ด ํ๋ก์ ํธ๋ฅผ ์งํํ๊ฒ ๋๋ค. ํ๋ก์ ํธ๋ ๊ต๋ด์์ ์ด๋ฆฌ๋ ์คํฌ์ธ ๊ฒฝ๊ธฐ๋ค์ ๊ฒฐ๊ณผ๋ฅผ ์ค์๊ฐ์ผ๋ก ํ์ธํ ์ ์๋๋ก ํ๋ ์๋น์ค์ด๋ค.
๊ฒฝ๊ธฐ๋ค์ ๋ชฉ๋ก์ ์กฐํํ ๋, QueryDSL ์ ์ฌ์ฉํ์ฌ ํ์ด์ง ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ค.
ํ๋ก์ ํธ์์์ ์ปจ๋ฒค์ ์, ๊ฒฝ๊ธฐ์ ์ฐธ์ฌํ๋ ํ๋ค์ ๋ฐ์ดํฐ๋ฅผ ์์ ๋ ฌํ์ฌ ๋ฐํํด์ผ ํ๋ค. ๊ทธ๋์ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ค.
List<Game> games = gameDynamicRepository.findAllByLeagueAndStateAndSports(leagueId, state, sportIds, pageRequest); return games.stream() .map(game -> new GameResponseDto(game, gameTeamRepository.findAllByGameWithTeam(game).stream() .sorted(comparingLong(GameTeam::getId)).toList(), game.getSport())) .toList();
๋ค์๊ณผ ๊ฐ์ ์ฝ๋ฉํธ๋ฅผ ๋ฐ์๋ค.
"์ด๋ ๊ฒ ๋๋ฉด N+1 ์ด ๋ฐ์ํ๊ฒ ๊ตฐ์ ์นํฌ๋.."
N+1 ๋ฌธ์ ์ ๋ํด์๋ ์ตํ ์๊ณ ์์์ง๋ง, ์ ์ ๋ด๊ฐ ์ฝ๋๋ฅผ ์งค ๋๋ ๋ณ๋ค๋ฅธ ์์์ ํ์ง ์๊ฑฐ๋ ๋ฌธ์ ์ง์ ์ ์ฐพ์ง ๋ชปํ๋ค๋ ์๊ฐ์ ํ๊ฒ ๋์ด ์ด ๊ธฐํ์ ์ ๋๋ก ์ก์๋ณด์! ํ๊ณ ์๊ฐํ๊ฒ ๋๋ค.
โ ํ์ฌ ์๊ตฌ์ฌํญ
- games: ๊ฒฝ๊ธฐ
- game_teams : games, teams ์ ์ฐ๊ฒฐํ๋ ์ค๊ฐ ํ ์ด๋ธ
- teams : ํ์ ์ ๋ณด
์กฐ๊ฑด์ ํด๋นํ๋ ๊ฒฝ๊ธฐ๋ฅผ
1. ๊ฒฝ๊ธฐ์ ์ฐธ์ฌํ๋ ํ๋ค์ id ์์ผ๋ก
2. ๊ฒฝ๊ธฐ๋ค์ ํ์ด์งํ์ฌ ๋ฐํ๋์ด์ผ ํ๋ค.
๐ N+1 ๋ฌธ์ ๋ ๋ฌด์์ผ๊น?
์ฐ์ , games ์ game_teams ๋ ์์ ๊ฐ๋ค.
games : game_teams = 1:N ์ ๊ด๊ณ๋ฅผ ๋งบ๊ณ ์์ผ๋ฉฐ games ์ชฝ์์๋ game_teams ๋ฅผ ์ฐธ์กฐํ์ง ์๊ณ ์๋ค.
๋ํ, game_teams ๋ teams ์ games ๋ฅผ ์ฐ๊ฒฐํ๋ ์ค๊ฐํ ์ด๋ธ์ด๋ค. ํ์ ์ ๋ณด๋ teams ์ ๋ด๊ฒจ ์๋ค.
List<Game> games = gameDynamicRepository.findAllByLeagueAndStateAndSports(leagueId, state, sportIds, pageRequest); return games.stream() .map(game -> new GameResponseDto(game, gameTeamRepository.findAllByGameWithTeam(game).stream() .sorted(comparingLong(GameTeam::getId)).toList(), game.getSport())) .toList();
์์ ์ธ๊ธํ ์ฝ๋๋ฅผ ์คํํ ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์๋ค.
๋ด๊ฐ ์์ํ๋ ์ํฉ์ ๊ฒฝ๊ธฐ ์ค ์กฐ๊ฑด์ ๋ง๋ ๊ฒฝ๊ธฐ๋ค์ ์ฐพ๋ ์ฟผ๋ฆฌ ํ๋๊ฐ ๋๊ฐ๋ ๊ฒ์ด์๋ค.
๊ทธ๋ฌ๋, ์ค์ ๋ก ํ์ธํด๋ณด๋ GameTeam ๊ฐ์ฒด๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ๊ฐ ์์ญ๊ฐ๊ฐ ๋๊ฐ ๊ฒ์ ๋ณผ ์ ์์๋ค. ๐คฏ๐คฏ๐คฏ
N+1 ๋ฌธ์ ๋ ์ด์ ๊ฐ์ด ์๋ํ์ง ์์ ์ฟผ๋ฆฌ๊ฐ ์์ญ๊ฐ๊ฐ ๋๊ฐ๋ ๊ฒ์ ์๋ฏธํ๋ค.
์ด๋ 1:N ์กฐ์ธ ์ํฉ์์ 1 ์ชฝ์์ ์กฐํ๋ฅผ ํ ๊ฒฝ์ฐ, ์๋ํ ์ฟผ๋ฆฌ ํ๊ฐ(์ค์ ๋ก ๊ฐ์ฒด๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ)์ N ๊ฐ๋งํผ ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ๋ ๊ฒ์ด๋ค.
๋์ ๊ฒฝ์ฐ๋ฅผ ์์๋ก ๋ค๋ฉด, ๊ฒ์์ ๊ฐ์๋งํผ ํ์ ์กฐํํ๋ ์ฟผ๋ฆฌ๊ฐ N๊ฐ๋งํผ ๋ฐ์ํ๋ค.
์ง์ฌ์ ๋ฐ๋ ค๋๋ฌผ๊ณผ์ ๊ด๊ณ๋ก ์๋ฅผ ๋ค์๋ฉด ์ง์ฌ์ ๋ชจ๋ ๋ฐ๋ ค๋๋ฌผ์ ์กฐํํ๋ ค๊ณ ํ๋๋ N๊ฐ๋งํผ ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ๋ ๊ฒ์ด๋ค.
์ง๊ธ ์ด ๋ฌธ์ ๋ ์ ๋ฐ์ํ๊ณ ์์๊น?
๐ N+1 ์ด ๋ฐ์ํ๋ ์ด์
return games.stream() .map(game -> new GameResponseDto(game, gameTeamRepository.findAllByGameWithTeam(game).stream() .sorted(comparingLong(GameTeam::getId)).toList(), game.getSport())) .toList();
๐ ์ ์ด ์ฝ๋์์๋ N+1 ์ด ๋ฐ์ํ๋๊ฐ?
์์ ์ฝ๋๋ฅผ ๋ณด๋ฉด, GameTeam ์ id ๋ฅผ ์กฐํํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
ํ์ฌ games, ์ฆ ๊ฒฝ๊ธฐ๋ค์ ๋ํด์ ๋ฐ๋ณต์ ์ผ๋ก ํด๋น ๊ฒฝ๊ธฐ์ ์ฐธ์ฌํ๊ณ ์๋ ํ๋ค์ ์ฐพ์์ id ์์ผ๋ก ์ ๋ ฌํ์ฌ dto ์ ์ธ์์ ๋ฃ๊ณ ์๋ค. ์ด ๊ณผ์ ์์ GameTeam ์ id ๋ฅผ ์กฐํํ๊ฒ ๋๋, ๊ฒฝ๊ธฐ ํ๋ ๋น (1) ๊ฒฝ๊ธฐ์ ์ฐธ์ฌํ๋ ํ์ ๊ฐ์๋งํผ (N) ์ฟผ๋ฆฌ๊ฐ ์ฌ๋ฌ๊ฐ ๋๊ฐ๊ฒ ๋๋ ๊ฒ์ด๋ค.
games ๋ ์ฌ๋ฌ๊ฐ์ game_teams ๋ฅผ ๊ฐ์ง๊ณ ์๊ณ , ๊ฒฝ๊ธฐ ๋ชฉ๋ก(games)์ ์กฐํํ ๋๋ ํ๋ก์ ๊ฐ์ฒด๊ฐ ์์ฑ๋๋ค. ๊ทธ๋ฌ๋, ์ค์ ๋ก game_teams ๊ฐ์ฒด์ ์ ๊ทผํ์ฌ id ๋ฅผ ์กฐํํ๋ ์๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ๊ฒ ๋๋ ๊ฒ์ด๋ค.
๐ N+1 ๋ฌธ์ ๋ ๊ทผ๋ณธ์ ์ผ๋ก ์ ๋ฐ์ํ๋๊ฐ?
์ด๋ ๊ฒ ์ฐ๋ฆฌ์ ์์๊ณผ๋ ๋ค๋ฅด๊ฒ ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ๋ ์ด์ ๋ ๊ฐ์ฒด์งํฅ๊ณผ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ ํจ๋ฌ๋ค์ ์ฐจ์ด๋ก ์ธํด์์ด๋ค.
๊ฐ์ฒด๋ A ๊ฐ B ๋ฅผ ์ฐธ์กฐํ๊ณ ์๋ค๋ฉด ์ง์ ์ ์ผ๋ก ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค. ์๋ฅผ ๋ค์ด, GameTeam ์ด Team ์ ์ฐธ์กฐํ๊ณ ์์ผ๋ GameTeam.team ์ ํตํด ๋ฐ๋ก team ์ผ๋ก์ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค. ๋ ํผ๋ฐ์ค๋ง ๊ฐ๊ณ ์๋ค๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ด์์ Random Access ๋ฅผ ํตํด ๋ฐ๋ก ์ ๊ทผํ ์ ์๋ ๊ฒ์ด๋ค.
๊ทธ๋ฌ๋ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ฒฝ์ฐ์๋ SQL ์ฟผ๋ฆฌ๋ง์ ํตํด์ ํ์ํ ์ ๋ณด๋ฅผ ์กฐํํ ์ ์๋ค.
๐ N+1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ ๋ฐฉ๋ฒ
fetch join ์ ์ฌ์ฉํ๋ค.
fetch join ์ ์ฐ๊ด๋ ์ํฐํฐ๋ ์ปฌ๋ ์ ๋ค์ ํ๋ฒ์ ๊ฐ์ ธ์ฌ ์ ์๋๋ก JPQL ์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ด๋ค.
์๋ฅผ ๋ค์ด์, game ์ ์กฐํํ ๋ ์ฐ๊ด๋ game_teams ๊ฐ์ฒด๋ค๋ ํ๋์ ์ฟผ๋ฆฌ๋ก ์กฐํํด์ค๋ ๊ฒ์ด๋ค.
SELECT g FROM Game g JOIN FETCH g.gameTeams WHERE g.id = :gameId
๊ทธ๋ฌ๋ fetch join ์ ์ฌ์ฉํ ๋๋ ๋ช ๊ฐ์ง์ ์ฃผ์ํ ์ ๋ค์ด ์กด์ฌํ๋ค.
Distinct ์ ์ ์ฌ์ฉํด์ผ ํ๋ค.
๋ ์ฝ๋ Game GameTeam 1 Game1 GameTeam1 2 Game1 GameTeam2 3 Game2 GameTeam3 fetch join ์ ์คํํ ๊ฒฐ๊ณผ์ ์์๋ ์์ ๊ฐ๋ค. ์ด๋ ์ค๋ณต๋์ด์ ์กด์ฌํ๋ค. ๋ฐ๋ผ์ ๋ฐ๋์ distinct ๋ฅผ ํด์ค์ผ ํ๋ค.
SQL ์์์ distinct ๋ join ๋์ด์ ๋ฐ์ํ ๊ฒฐ๊ณผ์ ๋ํด ์ค๋ณต์ ์ ๊ฑฐํ๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๊ฐ ์๋ํ ๋๋ก ์ค๋ณต์ด ์ ๊ฑฐ๋์ง ์์ ์ ์๋ค. SQL ์์ผ๋ก๋ Game1 + GameTeam1 ์ด ์๋ row ์ Game1๊ณผ GameTeam2 ๊ฐ ์๋ row ๋ ๋์ผํ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋ฌ๋ JPQL ์์ผ๋ก์ distinct ๋ ๊ฐ์ฒด ์์ฒด์ ๋ํ ์ค๋ณต์ ์ ๊ฑฐํ๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๊ฐ ์๋ํ ๋๋ก ์ค๋ณต์ ์ ๊ฑฐํ ์ ์๋ค.
Paging ์ด ๋ถ๊ฐ๋ฅํ๋ค.
fetch join ์ ์ฌ์ฉํ๊ฒ ๋๋ฉด, ๋ฉ๋ชจ๋ฆฌ ์์ ๋ชจ๋ ๊ฐ์ฒด๊ฐ ์กด์ฌํ๊ฒ ๋๊ณ ๊ทธ ์ํ์์ ํ์ด์ง์ ์คํํ๊ฒ ๋๋ค.
๋ง์ฝ, fetch join ์ ์ฌ์ฉํ์ฌ Game ์ ๊ธฐ์ค์ผ๋ก ํ์ด์ง์ ํ๊ฒ ๋๋ฉด, ํ ์ด๋ธ์ ์กด์ฌํ๋ ๋ชจ๋ ์ฐ๊ด๋ ๋ฐ์ดํฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ๋ก ๋ก๋ฉํด์ผ ํ๋ค.
์ด๋ก ์ธํด ๋ฉ๋ชจ๋ฆฌ ๋ด๋ถ์์ ํ์ด์ง์ด ์ผ์ด๋๊ฒ ๋๊ณ , ์ด๋ ๋ฉ๋ชจ๋ฆฌ ๊ณผ๋ถํ๋ก ์ด์ด์ ธ ์ฅ์ ์์ธ์ด ๋ ์ ์๋ค.
โ ์ ํํ์ง ์์ ์ด์
1. ํ์ด์ง์ด ํ์ํ ํ์ฌ ์ํฉ์์ ์ ํฉํ์ง ์๋ค.
2. ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๊ฐ ๋งบ์ด์ง ๊ฒฝ์ฐ์๋ง ๊ฐ๋ฅํ๋ค. ๊ทธ๋ฌ๋ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ์ง ์๊ธฐ๋ก ๋ด๋ถ์ ์ผ๋ก ๋ ผ์ํ๊ธฐ์ ์์ ๋ฐฉ๋ฒ ์ญ์๋ ์ ์ธํ๋ค. ์ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ์ง ์๊ธฐ๋ก ํ๋์ง๋ ๋ค์ ์ ํ์ง์์ ๋ ์์ธํ ์ค๋ช ํ๊ฒ ๋ค.
BatchSize ๋ฅผ ํ์ฉํ๋ค.
@BatchSize(size = 100) @OneToMany(mappedBy = "game") private List<GameTeam> gameTeams = new ArrayList<>();
BatchSize ๋ ์์ ๊ฐ์ด ํ์ฉํ ์ ์๋ค. ์ด๋ Game (1) ์ชฝ์์ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ์ผ๋ฉด ์ ํด๋ ๊ฐ์(size) ๋งํผ in ์ ๋ก GameTeam(N) ์ ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์จ๋ค.
spring: jpa: properties: default_batch_fetch_size: 100
์ด๋ ์์ฒ๋ผ ์ง์ ์ํฐํฐ์ ์ฌ์ด์ฆ๋ฅผ ์ ์ํ ์๋ ์๊ณ , ์ ์ญ์ ์ผ๋ก ์ค์ ํ๊ธฐ ์ํด ์์ฒ๋ผ yaml ์ ๊ธฐ์ ํ๋ ๊ฒ๋ ๊ฐ๋ฅํ๋ค.
์์ ๋ฐฉ๋ฒ์ ์ ์ฉํ๊ฒ ๋๋ฉด ์ฟผ๋ฆฌ๊ฐ ๋ค์๊ณผ ๊ฐ์ด ๋๊ฐ ๊ฒ์ด๋ค.
// games ์กฐํ ์ฟผ๋ฆฌ select * from game_teams where game_teams.game IN (games ์กฐํ ์ฟผ๋ฆฌ๋ก ์๊ฒ ๋ game ์ id)
BatchSize ์ fetch join ๋น๊ต
BatchSize ๋ ์ค์ ํด๋์ ์ฌ์ด์ฆ์ ๋นํด ์กฐํ๋์ด์ผ ํ๋ ์ํฐํฐ์ ๊ฐ์๊ฐ ๋ ๋ง๋ค๋ฉด, ์ฟผ๋ฆฌ๊ฐ ์ฌ๋ฌ๊ฐ๊ฐ ๋๊ฐ ์ ์๋ค. ์ด๋ก ์ธํด ์ฟผ๋ฆฌ ๊ฐ์์ ๊ด์ ์์๋ fetch join ์ด ์ ๋ฆฌํ๋ค. ์๋ฅผ ๋ค์ด, ๋ง์ฝ batch size ๊ฐ 10์ธ๋ฐ ์กฐํ๋์ด์ผ ํ๋ ์ํฐํฐ์ ๊ฐ์๊ฐ 100๊ฐ๋ผ๋ฉด 10๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ์ผ ํ๋ค.
๊ทธ๋ฌ๋, fetch join ์ join ์ ๋จผ์ ํ๊ณ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ์ค๋ณต๋ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ์ง๋ง batch size ์ ๊ฒฝ์ฐ์๋ join ์ ํ์ง ์๊ณ ์กฐํํ๊ธฐ ๋๋ฌธ์ ์ค๋ณต๋๋ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ์ง ์๋๋ค. ๋ฐ๋ผ์ ๋ฐ์ดํฐ ์ ์ก๋์ ๊ด์ ์์๋ BatchSize ๊ฐ ์ ๋ฆฌํ๋ค.
โ ์ ํํ์ง ์์ ์ด์
ํด๋น ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ด์ ์, ํ ๋ด์์ API ์คํ ์์ผ๋ก ํ์ํ ์๋ต ํํ์ ๋ง์ถฐ์ ๋ฐํํ๊ธฐ ์ํด์ ์๋ฐฉํฅ ๋งคํ์ ๋งบ์ง ์๊ธฐ๋ก ๋ ผ์๋ฅผ ๋ง์น ์ํ์๋ค. ์ด๋ API ์คํ ์์ ๋ณ๊ฒฝ์ฌํญ์ด (ex. ๊ฒฝ๊ธฐ์ ๋ชฉ๋ก์ ๋ฐํํ ๋ ๊ฒฝ๊ธฐ์ ์ฐธ์ฌํ๋ ํ์ ๋ชฉ๋ก์ ๋ฐํํ ํ์๊ฐ ์์ด์ง ๊ฒฝ์ฐ) ์ฐ๊ด๊ด๊ณ์ ์ํฅ์ ๋ฏธ์น๋ ๊ฒ์ ์ณ์ง ์์ ์์กด๊ด๊ณ๋ผ๊ณ ์๊ฐํ๊ธฐ ๋๋ฌธ์ด์๋ค.
๋น์ง๋์ค ๋ก์ง ์์ผ๋ก ํด๋น ๊ฐ์ฒด๊ฐ ๋ถ๋ช ํ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ๊ณ ์๋ค๊ฑฐ๋, ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ฐ์ด ํด์ ๊ฐ์ฒด๋ฅผ ๋์์ ๊ด๋ฆฌํ ํ์์ฑ์ด ๋๊ปด์ง๋ ๊ฒฝ์ฐ์๋ง ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ๋๋ก ํ๊ธฐ๋ก ํ๋ค.
Projection ์ ์ด์ฉํด dto ๋ก ๋ณํํ๊ธฐ
select new ํจํค์ง ๊ฒฝ๋ก.GameResponseDto(์ํ๋ ํ๋) from Game g join g.game_teams gt where gt.game_id = g.id
์์ ๊ฐ์ด ์ง์ ์ ์ผ๋ก ํ์ํ ํ๋๋ค์ ๋ช ์ํ์ฌ ๋ฐ๋ก dto ๋ก ๋ณํํ๊ฒ ๋๋ฉด, ํ์ํ ์ปฌ๋ผ๋ง ๋ช ์ํ์ฌ ์กฐํํ ์ ์๋ค.
๊ทธ๋ฌ๋ ์ด๊ฐ ๋์์ ์๋ฏธํ๋ ๊ฒ์, ํ์ํ ์ปฌ๋ผ์ ๋ชจ๋ ๋ช ์ํด์ผ ํ๊ธฐ์ API ์คํ์ด ๋ฐ๋๊ฒ ๋๋ฉด ์์ ์ฝ๋๋ ์์ ๋์ด์ผ ํ๋ ๊ฒ์ด๋ค.
์ฆ, DTO ์ ๋ณ๊ฒฝ์ด DAO ๊น์ง ๋ณ๊ฒฝ์ ์ ํํ๊ฒ ๋์ด ์ข์ง ์์ ๋ฐฉ๋ฒ์ด ๋ ์ ์๋ค.
์ด๋ฅผ ํ์ฉํ๊ธฐ ์ํด์๋, DTO ์ ๋ณ๊ฒฝ์ด DAO ๊น์ง ๋ณ๊ฒฝ์ ์ ํํ๊ฒ ๋๋ ์ฟผ๋ฆฌ์ ๊ทธ๋ ์ง ์์ ์ฟผ๋ฆฌ๋ฅผ ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํด์ผ ํ๋ค.
โ ์ ํํ์ง ์์ ์ด์
์์ ๋ฐฉ๋ฒ ์ญ์๋, ์ฟผ๋ฆฌ๋ฅผ ๋ณด๋ฉด ์๊ฒ ์ง๋ง ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ์ด์ผ ํ๋ค.
๊ทธ๋ ๊ธฐ์ ์ด ๋ฐฉ๋ฒ ์ญ์๋ ์ ํํ์ง ์์๋ค.
N+1 ๋ฌธ์ ๋ฅผ ๊ณ ๋ คํ์ฌ ์ฝ๋๋ฅผ ์ง๊ธฐ
List<Game> games = gameDynamicRepository.findAllByLeagueAndStateAndSports(queryRequestDto, pageRequest); List<GameTeam> gameTeams = gameTeamQueryRepository.findAllByGameIds( games.stream() .map(Game::getId) .toList() ); Map<Game, List<GameTeam>> groupedByGame = gameTeams.stream() .collect(groupingBy(GameTeam::getGame)); return games.stream() .map(game -> new GameResponseDto(game, groupedByGame.getOrDefault(game, new ArrayList<>()), game.getSport())) .toList();
fetch join ์ ํ์ด์ง์ผ๋ก ์ธํด ์ฌ์ฉํ ์ ์๊ณ , BatchSize ๋ ์๋ฐฉํฅ ๊ด๊ณ๋ฅผ ๋งบ์ง ์์ ํ์ฉํ ์ ์๊ธฐ์ ๊ฒฐ๊ตญ N+1 ๋ฌธ์ ๋ฅผ ๊ณ ๋ คํด ์ฝ๋๋ฅผ ์ง๊ธฐ๋ก ๊ฒฐ์ ํ๋ค. ์์์ ์ธ๊ธํ ๋ฐฉ๋ฒ๋ค์ ๋ชจ๋ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ์ด์ผ ๊ฐ๋ฅํ ๋ฐฉ๋ฒ๋ค์ด์๊ธฐ์ ํด๋น ๋ฌธ์ ๋ฅผ ์ผ๋์ ๋๊ณ ์ฝ๋๋ฅผ ์ง๋ ๊ฒ์ ํตํด ํด๊ฒฐํ ์ ์์๋ค.
์ฐ์ , ๊ฒฝ๊ธฐ ์ ์ฒด๋ฅผ ์กฐํํด games ์ ๋ด๋๋ค. ๊ทธ๋ฆฌ๊ณ GameTeam ์ ์ฒด ์ค์ ํด๋น ๊ฒฝ๊ธฐ์ ์ฐธ์ฌํ๊ณ ์๋ ํ๋ค์ ์ ๋ถ ์กฐํํด์จ๋ค. ์ด๋ ๊ฒฝ๊ธฐ์ id ๋ค์ stream ์ ์ด์ฉํด ๋ฆฌ์คํธ๋ก ๋ฐํํ๋ค. ์ด๋ฅผ ํตํด, ์กฐ๊ฑด์ ๋ง๋ ๊ฒฝ๊ธฐ ๊ฐ์ฒด๋ค๊ณผ ๊ฒฝ๊ธฐ์ ์ฐธ์ฌํ๋ ํ ๊ฐ์ฒด๋ค์ ์ ๋ถ ์์์ฑ ์ปจํ ์คํธ์ 1์ฐจ ์บ์์ ๋ด๊ธฐ๊ฒ ๋์๋ค. ์ฆ, ๋ ์ด์ ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ ์ผ์ ์๋ค. ์ดํ groupedByGame ์ ๊ฒฝ๊ธฐ์ ์ฐธ์ฌํ๋ ํ๋ค์ ๊ฒฝ๊ธฐ๋ฅผ key ๋ก ํ์ฌ ๊ทธ๋ฃนํํ๋ค.
์ด๋ฅผ ํตํด ์ฟผ๋ฆฌ๋ฅผ ๋๋ฒ๊น์ง ์ค์ผ ์ ์๊ฒ ๋์๋ค.
gameTeamQueryRepository.findAllByGameIds ๋ฅผ ์คํํ ๋์ ์ฟผ๋ฆฌ ๐ ์ฐธ๊ณ ์๋ฃ
https://velog.io/@xogml951/JPA-N1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EC%B4%9D%EC%A0%95%EB%A6%AC
JPA N+1 ๋ฌธ์ ์ ํด๊ฒฐ๋ฒ ์ด์ ๋ฆฌ
JPA๋ก ๊ฐ๋ฐ ๊ณผ์ ์์ N+1๋ฌธ์ ๋ฅผ ๋ง๋๋ฉด์ ํด๋งธ๋ ๋ถ๋ถ๋ค๊ณผ ์ค์ ํ๋ก๋์ ์์ ์ด๋ฅผ ์ ๋๋ก ์ฒ๋ฆฌํ์ง ๋ชปํ๋ฉด ์ฑ๋ฅ์ ์ธ ์ ํ๋ฟ๋ง ์๋๋ผ ์ฅ์ ๋ก ์ด์ด์ง ์ ์๋ค๋ ๋ถ๋ถ์ ๊นจ๋ซ๊ณ ์ ๋ฆฌ ํด๋ณด๊ณ ์ ํฉ
velog.io
'SpringBoot' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ