๋ธ๋ก๊ทธ์์ ๊ฒ์๊ธ ๋ชฉ๋ก์ ์กฐํํ๊ธฐ ์ํด JpaRepository์ JPQL์ ์ฌ์ฉํ๋ค.
@Query("""
SELECT p FROM Post p
WHERE p.status = :status
ORDER BY p.createdAt DESC, p.id DESC
""")
Slice<Post> findPublishedPosts(@Param("status") PostStatus status, Pageable pageable);
์ค์ ๋ก ์คํ๋ SQL์ ๋ค์๊ณผ ๊ฐ๋ค.
-- 1. Post ๋ชฉ๋ก ์กฐํ (1ํ)
Hibernate:
select
p1_0.id,
p1_0.category_id,
p1_0.content,
-- ๋ฑ๋ฑ Post ํ๋๋ค
from
post p1_0
where
p1_0.status=?
order by
p1_0.created_at desc,
p1_0.id desc
fetch
first ? rows only
-- 2. ๊ฐ Post์ Category ์กฐํ (์ถ๊ฐ ๋ฐ๋ณต)
Hibernate:
select
c1_0.id,
c1_0.created_at,
-- ๋ฑ๋ฑ Category ํ๋๋ค
from
category c1_0
where
c1_0.id=?
Hibernate:
select
c1_0.id,
c1_0.created_at,
-- ๋ฑ๋ฑ Category ํ๋๋ค
from
category c1_0
where
c1_0.id=?
-- ์ดํ ํ์ํ ์นดํ ๊ณ ๋ฆฌ ๊ฐ์๋งํผ ๋ฐ๋ณต
๊ฒ์๊ธ ๋ชฉ๋ก์ ์กฐํํ๋ ์ฟผ๋ฆฌ๊ฐ 1ํ ์คํ๋ ํ ๊ฐ ๊ฒ์๊ธ์ ๋ํด ์ฐ๊ด๋ category ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฐ๋ณต ์คํ๋์๋ค. ์ด๋ฌํ ํ์์ N+1 ๋ฌธ์ ๋ผ๊ณ ํ๋ค.
N+1 ๋ฌธ์ ๋ ์ต์ด์ ๋จ์ผ ์กฐํ ์ฟผ๋ฆฌ(1๋ฒ) ์คํ ์ดํ ํด๋น ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ก ๋ฐํ๋ ๋ฐ์ดํฐ์ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์กฐํํ๊ธฐ ์ํด ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ๋ณต์ ์ผ๋ก ๋ฐ์ํ๋ ์ฑ๋ฅ ์ ํ ํ์์ ์๋ฏธํ๋ค.
JPA ํ๊ฒฝ์์ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ ์ฃผ์ ์์ธ์ JPQL์ ์ฟผ๋ฆฌ ๋ณํ ๋ฐฉ์๊ณผ JPA ๊ตฌํ์ฒด์ ์ฐ๊ด ๋ฐ์ดํฐ ๋ก๋ฉ ์ ๋ต ๊ฐ์ ์ฐจ์ด์ ์๋ค.
JPQL์ ์ฐ๊ด๊ด๊ณ ๋งคํ ์ ๋ณด๋ฅผ ๊ธฐ์ค์ผ๋ก ์๋์ผ๋ก ์กฐ์ธ์ ์์ฑํ์ง ์์ผ๋ฉฐ ์์ฑ๋ ์ฟผ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก SQL์ ์์ฑํ๋ค. ๊ทธ ๊ฒฐ๊ณผ ์ฐ๊ด ๋ฐ์ดํฐ๋ฅผ ํฌํจํ์ง ์์ ์ํ๋ก ์ง์ ๋ ๋์ ํ
์ด๋ธ๋ง ์กฐํํ๋ 1ํ์ SQL์ด ์คํ๋๋ค.
์ดํ JPA ๊ตฌํ์ฒด๋ ๋ฐํ๋ ์ํฐํฐ๋ฅผ ์์์ฑ ์ปจํ
์คํธ์ ์ ์ฅํ๋ ๊ณผ์ ์์ ์ํฐํฐ ๋งคํ ์ ๋ณด์ Fetch ์ ๋ต์ ํ์ธํ๋ค. ์ด๋ ์ฐ๊ด ๋ฐ์ดํฐ๊ฐ ์์ง ๋ก๋ฉ๋์ง ์์๋ค๋ฉด ์ถ๊ฐ์ ์ธ ์กฐํ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์๋ค.
Fetch ์ ๋ต์ด ์ฆ์ ๋ก๋ฉ(EAGER)์ผ๋ก ์ค์ ๋ ๊ฒฝ์ฐ์๋ ์ฐ๊ด ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ์กฐํํ๊ธฐ ์ํด ์ถ๊ฐ ๋จ๊ฑด ์กฐํ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ฉฐ ์ง์ฐ ๋ก๋ฉ(LAZY)์ผ๋ก ์ค์ ๋ ๊ฒฝ์ฐ์๋ ๋น์ฆ๋์ค ๋ก์ง์์ ์ฐ๊ด ์ํฐํฐ์ ์ค์ ์ ๊ทผํ๋ ์์ ์ ๊ฐ๋ณ ์กฐํ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.
์ด๋ ๋ฐ์ํ๋ ์ถ๊ฐ ์ฟผ๋ฆฌ์ ํ์๋ ์์์ฑ ์ปจํ
์คํธ์ ์ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ค. JPA๋ ์ฐ๊ด ์ํฐํฐ ์กฐํ ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฟผ๋ฆฌ๋ฅผ ์ ์กํ๊ธฐ ์ 1์ฐจ ์บ์๋ฅผ ๋จผ์ ํ์ธํ๋ฏ๋ก ์ฌ๋ฌ ์ํฐํฐ๊ฐ ๋์ผํ ์ฐ๊ด ์ํฐํฐ๋ฅผ ์ฐธ์กฐํ ๊ฒฝ์ฐ ์บ์๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํ์ฌ ์ฟผ๋ฆฌ ์คํ์ ์๋ตํ๋ค.
์ฆ ์ค์ ์ถ๊ฐ ์ฟผ๋ฆฌ๋ ์ฐธ์กฐํ๋ ์ฐ๊ด ์ํฐํฐ์ ๊ณ ์ ํ ๊ฐ์๋งํผ๋ง ์คํ๋๊ณ ์ต์
์ ๊ฒฝ์ฐ N๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.
JOIN FETCH๋ฅผ ์ฌ์ฉํ๋ฉด SQL ๋ ๋ฒจ์์ ์ฐ๊ด ์ํฐํฐ๋ฅผ ํจ๊ป ์กฐํํ ์ ์์ด N+1 ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ ์ ์๋ค.
@Query("""
SELECT p FROM Post p
LEFT JOIN FETCH p.category
WHERE p.status = :status
ORDER BY p.createdAt DESC, p.id DESC
""")
Slice<Post> findPublishedPosts(@Param("status") PostStatus status, Pageable pageable);
category๊ฐ nullable ๊ด๊ณ์ด๊ธฐ ๋๋ฌธ์ LEFT JOIN์ ์ฌ์ฉํ์๋ค.
@EntityGraph๋ JPA์์ ์ ๊ณตํ๋ ์ด๋
ธํ
์ด์
์ผ๋ก ์ฟผ๋ฆฌ ์์ฒด๋ ์ ์งํ๋ฉด์ ์ฐ๊ด ์ํฐํฐ์ Fetch ์ ๋ต๋ง ๋ณ๊ฒฝํ ์ ์๋ค.
@EntityGraph(attributePaths = { "category" })
@Query("""
SELECT p FROM Post p
WHERE p.status = :status
ORDER BY p.createdAt DESC, p.id DESC
""")
Slice<Post> findPublishedPosts(@Param("status") PostStatus status, Pageable pageable);
๋ ๋ฐฉ๋ฒ ๋ชจ๋ left join์ด ํฌํจ๋ ๋จ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๊ฒ ๋๋ค.
Hibernate:
select
p1_0.id,
c1_0.id,
c1_0.created_at,
-- ๋ฑ๋ฑ ํ๋๋ค
from
post p1_0
left join
category c1_0
on c1_0.id=p1_0.category_id
where
p1_0.status=?
order by
p1_0.created_at desc,
p1_0.id desc
Fetch Join์ @Query์ ๋ช
์์ ์ผ๋ก ์์ฑํ๋ฉด ์กฐ์ธ ํ์
์ด๋ ์กฐ๊ฑด์ ๋ช
ํํ๊ฒ ์ ์ดํ ์ ์๋ค๋ ์ฅ์ ์ด ์๋ค.
ํ์ง๋ง ํ์ฌ ๊ธ ๋ชฉ๋ก ์กฐํ ์ฟผ๋ฆฌ๋ ๋น๊ต์ ๋จ์ํ๊ธฐ ๋๋ฌธ์ ๊ฐ๋
์ฑ์ด ๋ ์ข์ @EntityGraph๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ์๋ค.
์์ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ ์ธ์๋ ์ํฐํฐ๋ฅผ ์กฐํํ์ง ์๊ณ ํ์ํ ํ๋๋ง DTO๋ก ์ง์ ์กฐํํ๋ DTO Projection ๋ฐฉ์์ ์ฌ์ฉํ ์๋ ์๋ค.
@Query("""
SELECT new com.example.project.dto.PostResponse(
p.id,
p.title,
c.name
)
FROM Post p
LEFT JOIN p.category c
WHERE p.status = :status
""")
Slice<PostResponse> findPublishedPostDtos(
@Param("status") PostStatus status, Pageable pageable
);
์ด ๋ฐฉ์์ ์ฒ์๋ถํฐ ํ์ํ ํ๋๋ง JOINํ์ฌ ์กฐํํ๊ธฐ ๋๋ฌธ์ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ฌ์ง๊ฐ ์๋ค. ๋ํ SELECT * ๊ฐ ์๋๋ผ SELECT p.id, p.title ... ์ฒ๋ผ ํ์ํ ์ปฌ๋ผ๋ง ์กฐํํ๋ฏ๋ก DB์ ์ ํ๋ฆฌ์ผ์ด์
๊ฐ์ ๋ฐ์ดํฐ ์ ์ก๋์ ์ค์ผ ์ ์๋ค.
๋ค๋ง JPQL ๋ด๋ถ์์ DTO์ ์ ์ฒด ํจํค์ง ๊ฒฝ๋ก๋ฅผ ๋ช
์ํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๊ฐ๋
์ฑ์ด ๋ค์ ๋จ์ด์ง ์ ์๋ค. ๋ํ ์กฐํ๋ ๊ฒฐ๊ณผ๋ ์์ ์ํ๊ฐ ์๋๊ธฐ ๋๋ฌธ์ ๊ฐ์ ์์ ํ๋๋ผ๋ JPA์ ๋ณ๊ฒฝ ๊ฐ์ง(Dirty Checking)๊ฐ ์ ์ฉ๋์ง ์๋๋ค.
