๐ท Spring/๊ฐ๋
[Spring][JPA] ์ต๋ํ ์ฝ๊ฒ ์์๋ณด๋ @EntityGraph์ ๊ฐ๋
hyeong._.ing
2026. 4. 3. 13:25
์ฐ๊ด๊ด๊ณ๊ฐ ์๋ DB๊ฐ ์๋ ๊ฒฝ์ฐ
ํ์ชฝ์ @EntityGraph๋ฅผ ์ฌ์ฉํ๋ค.
@EntityGraph์ ์ญํ ์ด ๋ญ๊ธธ๋
์ฐ๊ด๊ด๊ณ ํ ์ด๋ธ์ ํ์ํ ๊น?
๋ฏธ๋ฆฌ ์์์ผํ ๊ฐ๋ ์ค๋ช
- @EntityGraph๋ฅผ ์๊ธฐ ์ํด์, ํ๋ก์(Proxy)์ Lazy(์ง์ฐ๋ก๋ฉ), Eager(์ฆ์๋ก๋ฉ)์ ๋ํด์ ์์์ผํ๋ค.
- ์๋ง @EntityGraph์ ๊ฐ๋ ์ด ๊ถ๊ธํ ์ด์ ๋, ์ฝ๋์ @ManyToOne๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ด๋ค.
- ์ด๋ @ManyToOne์ fetch = FetchType.Lazy๋ฅผ ์ค์ ํ ๊ฒฝ์ฐ @EntityGraph๊ฐ ํ์ํ๋ค. → ์ด ์กฐํฉ์ ๋ง์ด ์ด๋ค๋ ๊ฑธ ์๊ณ ์ผ๋จ์ ๋์ด๊ฐ์. ์ ์ด ์กฐํฉ์ ๋ง์ด ์ฐ๋์ง ๋ค์ ์ค๋ช ํ๊ฒ ๋ค.
- JPA๊ฐ DB์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋ ๋น์ฅ ํ์ ์๋ ์ฐ๊ด ๋ฐ์ดํฐ๋ ์ง์ง ๊ฐ์ฒด ๋์ ๊ฐ์ง ๊ฐ์ฒด๋ฅผ ๋ฃ์ด๋๋๋ฐ, ์ด๋ ์ด ๊ฐ์ง ๊ฐ์ฒด๋ฅผ ํ๋ก์๋ผ๊ณ ๋งํ๋ค.
- ๊ทธ๋ฌ๋ฉด @ManyToOne(fetch = FetchType.Lazy)๋ ํ๋ก์๋ฅผ ์ฌ์ฉํ๊ฒ ๋ค๋ ๋ง๋ก, ๊ฐ๋ฐ์๊ฐ ์ง์ง ๋ฐ์ดํฐ๋ฅผ ๋ฌ๋ผ๊ณ ์์ฒญํ๋ฉด ๊ทธ์ ์์ผ ์ง์ง ๊ฐ์ฒด๋ฅผ ์ฐพ์ ๋ณด๋ด์ฃผ๋ ๊ฒ์ด๋ค. → ๊ฒ์๊ธ์ ์์ฑ์ ์ด๋ฆ์ด ํฌํจ๋ ๊ฒฝ์ฐ, ๊ฒ์๊ธ ์กฐํ์ ์์ฑ์ ์ด๋ฆ์ ํ๋ก์๋ก ๊ฐ์ ธ์จ๋ค. ๊ทธ๋ฆฌ๊ณ ์์ฑ์ ์ด๋ฆ์ ์์ฒญํ๋ฉด ๊ทธ๋์์ผ ์ง์ ์ง์ง ๊ฐ์ฒด๋ฅผ ์ฐพ์์ ๋ค๊ณ ์จ๋ค.
- @ManyToOne์ ๊ธฐ๋ณธ๊ฐ์ Eager์ด๋ค. Eager์ ์ํ์ง ์์๋ ๊ด๋ จ๋ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ์ฐพ์๊ฐ์ง๊ณ ์ค๋ ๊ฒ์ด๋ค.
@EntityGraph
- ๊ฐ์ฒด ๊ทธ๋ํ๋ผ๋ ์ด๋ฆ๊ณผ ๊ฐ์ด, ์ํฐํฐ ๊ฐ์ ์ฐ๊ด๊ด๊ณ๋ฅผ ํ๊ณ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋๋ง ์๋ฏธ๊ฐ ์๋ค.
- DB ์ ์ฅ์์ JOIN์ ํ๊ฒ ๋ค๋ ๋ป์ด๋ค.
- JPA ์ ์ฅ์์๋ ํ๋ก์ ๋์ ์ง์ง ๊ฐ์ฒด๋ฅผ ํ๊บผ๋ฒ์ ์ฑ์ ๋ฃ๊ฒ ๋ค๋ ๋ป์ด๋ค.
- ๋ง์ฝ ์ฐ๊ด๊ด๊ณ๊ฐ ์ค์ ์ด ์๋ ํ ์ด๋ธ์ ์ฌ์ฉํ๋ค๋ฉด ๊ฒฝ๋ก ์์ฒด๊ฐ ์๊ธฐ ๋๋ฌธ์ ์ฌ์ฉํ ์ ์๋ค. → ์ด๋ด ๋๋ ์ง์ JOIN ์ฟผ๋ฆฌ๋ฅผ ์ง๊ฑฐ๋ ๋ฐ๋ก ์กฐํํด์ผํ๋ค.
- ์ค์ ๋ฐ์ดํฐ๊ฐ ์ฆ์ ํ์ํ ๊ณณ์์๋ง ์ฌ์ฉํ๋ค.
EAGER๊ณผ LAZY์ ์์ ์ฝ๋์ ๊ฒฐ๊ณผ
- ์์) 3๊ฐ์ ๊ฒ์๊ธ์ด ์๊ณ , ๊ฒ์๊ธ์ ์์ฑ์๊ฐ ํฌํจ๋๋ค๊ณ ๊ฐ์ ํ์.
- EAGER
// ์ํฐํฐ ์ค์
@Entity
public class Post {
// ์ฆ์๋ก๋ฉ
@ManyToOne(fetch = FetchType.EAGER)
private Member member;
}
// Repository (JPQL ์ฌ์ฉ ์)
@Query("select p from Post p")
List<Post> findAllJPQL();
๊ฒ์๊ธ์ ๊ฐ์ ธ์ค๋ผ๊ณ ํ์ ๋, EAGER๋ก ์ค์ ํด๋๋ฉด ์์ฑ์ ์ด๋ฆ๋ ๋ฌด์กฐ๊ฑด ํจ๊ป ๊ฐ์ ธ์จ๋ค.
1) JPA๋ @Query("select p from Post p")๋ฅผ ๋ณด๊ณ SQL์ ๋ง๋ ๋ค. ๊ทธ๋ ๊ฒ ์์ฑ๋ SQL์ SELECT * FROM post;๋ก ํฌ์คํธ๋ง ๊ฐ์ ธ์ค๋๋ก ํ๋ค.
2) DB๋ก๋ถํฐ ๊ฒ์๊ธ 3๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ๋ค. → ์ฟผ๋ฆฌ 1๋ฒ ๋ฐ์
3) Post๊ฐ EAGER์ธ ๊ฑธ ์๊ฒ ๋๋ค. ๋ฐ์ดํฐ๊ฐ ๋น์ด์ ธ ์๋ ๊ฑธ ์๊ณ ์์ฑ์๋ฅผ ์ฑ์ฐ๊ธฐ ์ํด ํ๋ํ๋ DB์ ๋ฌผ์ด๋ณธ๋ค. Post1์ ๋ณด๊ณ SELECT * FROM member WHERE id = ?;๋ฅผ ์คํํ๋ค. ๊ทธ๋ ๊ฒ ๋ชจ๋ ๊ฒ์๊ธ์ ๋๋ฉด์ ์์ฑ์์ ์ ๋ณด๋ฅผ ๋ฌผ์ด๋ณด๋ ๊ฒ์ด๋ค. ๊ฒ์๊ธ์ด 3๊ฐ๋ผ๋ฉด 3๋ฒ ๋ฌผ์ด๋ณธ๋ค. → 3๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์, ์ด๊ฑธ N+1 ๋ฌธ์ ๋ผ๊ณ ํ๋ค.
์์ฝํด๋ณด์๋ฉด, ๋จผ์ SQL์ ๋ ๋ฆฌ๊ณ ๋ณด๋ EAGER์ธ ๊ฑธ ๋์ค์ ์๊ฒ๋์ด์ ๋ถ๋ด๋ถ๋ด ๋ถ์กฑํ ๋ฐ์ดํฐ๋ฅผ ์ฑ์ฐ๋ ค๊ณ ์ถ๊ฐ ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆฌ๋ ๊ฒ์ด๋ค. ๊ทธ๋์ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ณ ๋ง์ฝ ์กฐํํ ๊ฒ์๊ธ์ด ์์ฒญ๋๊ฒ ๋ง๋ค๋ฉด ์ ๋๋ก ์๋ํ์ง ์์ ๊ฒ์ด๋ค.
- LAZY
// ์ํฐํฐ ์ค์
@Entity
public class Post {
// ์ง์ฐ๋ก๋ฉ
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
}
๊ฒ์๊ธ์ ๊ฐ์ ธ์ค๋ผ๊ณ ํ์ ๋, ์์ฑ์ ์ด๋ฆ์ด ํ๋ก์๋ก ์จ๋ค. ๊ทธ๋ฐ๋ฐ ์์ฑ์ ์ด๋ฆ์ ์์ฒญํ๊ฒ ๋๋ฉด ๊ทธ๋ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
1) SELECT * FROM post;๋ก ๊ฒ์๊ธ 3๊ฐ๋ฅผ ๊ฐ์ ธ์๋ค. ๊ทธ๋ฌ๋ ์์ฑ์ ์๋ฆฌ๋ ํ๋ก์ ์ํ์ด๋ค. → ์ฟผ๋ฆฌ 1๋ฒ ๋ฐ์
2) ์ด๋ฆ์ ๋ฌผ์ด๋ดค๋ค. 1๋ฒ ๊ฒ์๊ธ์ ์์ฑ์ ์ด๋ฆ ์ถ๋ ฅ์ ์๋ํ๋, ๋น์ด์๊ณ ๊ทธ๊ฑธ ์ฑ์ฐ๊ธฐ ์ํด SELECT * FROM member WHERE id = 1;์ ๋ณด๋ธ๋ค. ๊ทธ๋ ๊ฒ ๊ฒ์๊ธ์ ์๋งํผ ๋ฐ๋ณตํ๊ฒ ๋๋ค. → 3๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์, ์ด๊ฑธ N+1 ๋ฌธ์ ๋ผ๊ณ ํ๋ค.
LAZY๋ฅผ ์ผ์ด๋ ์ด๋ฐ ์ํฉ์ด ๋ฐ์ํ๋ฉด EAGER์ฒ๋ผ ์ฑ๋ฅ์ ๋จ์ด๋จ๋ฆฐ๋ค. ๊ทธ๋์ ์ฟผ๋ฆฌ๊ฐ ๋ง์ด ๋ฐ์ํ ๊ฒ ๊ฐ์ ์ํฉ์ @EntityGraph๋ฅผ ๋ถ์ฌ ํ ๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋ค๊ณ ์ค๋๋ก ํ๋ค.
@EntityGraph์ ์ฌ์ฉ ์ด์
- ๊ทธ๋๊น ๊ฒฐ๋ก ์ ์ผ๋ก LAZY๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ง์ ํ๋๋ผ๋ ๊ฐ์ง ๊ฐ์ฒด๋ฅผ ์ง์ง ๊ฐ์ฒด๋ก ๋ฐ๋ฆฌ๊ณ ์ฌ ๋ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ ๊ทธ๊ฑธ ๋ฐฉ์งํ๊ณ ์ @EntityGraph๋ฅผ ์ฌ์ฉํ๋ค.
- @EntityGraph๋ฅผ ์ ์ฉํ ๋ฉ์๋๋, ํด๋น ๋ฉ์๋๊ฐ ํธ์ถ๋ ๋ LAZY ์ค์ ์ ๋ฌด์ํ๊ณ SQL JOIN์ ํตํด ์ค์ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ๊ฐ์ ธ์จ๋ค. ์ ์ ์ธ ์ค์ ์ ๋์ ์ผ๋ก ๋ณ๊ฒฝํ ๊ฒ์ด๋ค.
@Entity
public class Post {
// ์ง์ฐ๋ก๋ฉ ์ค์
@ManyToOne(fetch = FetchType.LAZY)
private Member member;
}
public interface PostRepository extends JpaRepository<Post, Long> {
// member๋ ๊ฐ์ด ๋ฐ๋ ค์.
@EntityGraph(attributePaths = {"member"})
List<Post> findAll();
}
findAll( )์ธ ํน์ ๋ฉ์๋๋ฅผ ์คํํ ๋, @EntityGraph๋ฅผ ํตํด member๋ ๊ฐ์ด ๋ฐ๋ ค์ค๋ผ๊ณ ์์ฑํ๋ค. Post์ ์ ์ฒด๋ฅผ ์กฐํํ ๋ ์์ฑ์ ์ด๋ฆ๋ ํ ๋ฒ์ ์ค๋๋ก ํ๊ฑฐ๋, left outer join member m on p.member_id = m.id; ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๊ณ ์ด ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๋๋๋ค.
attributePaths๋ ์์ฑ ๊ฒฝ๋ก๋ฅผ ์ง์ ํ ๊ฒ์ด๋ค. ์กฐํํ๊ณ ์ถ์ ์ฐ๊ด ๊ด๊ณ์ ํ๋ ์ด๋ฆ์ attributePaths์ ๋ฐฐ์ด ํํ๋ก ๋ฃ๊ธฐ๋ง ํ๋ฉด ๋๋ค. ์ง๊ธ ์ด ์ฝ๋์์๋ ๊ฒ์๊ธ์ ์์ฑํ ์์ฑ์๋ฅผ ์๊ณ ์ถ์๊ฑฐ๋, ๊ทธ ์ ๋ณด๊ฐ ๋ค์ด์์ Member๋ฅผ ์ง์ ํ๋ค.
์์ชฝ ์ค๋ช ์ left outer join member m on p.member_id = m.id; ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค๊ณ ํ๋๋ฐ, Left Outer Join ์ฌ์ฉ์ด @EntityGraph์ ์ฃผ์ ํน์ง์ด๋ค. ์ด ๋ง์ ์ฝ๊ฒ ํ์ด์ ์ค๋ช ํ๋ฉด ์ผ์ชฝ ํ ์ด๋ธ์ ๋ชจ๋ ๋ณด์ฌ์ฃผ๊ณ ์ค๋ฅธ์ชฝ ํ ์ด๋ธ(์ฐ๊ด๋ฐ์ดํฐ)์ ์์ผ๋ฉด ๊ฐ์ ธ์ค๊ณ ์์ผ๋ฉด ๋น์๋๋ผ๋ ๋ฐฉ์์ ๋งํ๋ค. ๊ทธ๋ผ ์ Left Outer Join์ ์ฌ์ฉํ๋๊ฑธ๊น?
์๋ฅผ ๋ค์ด์ 3๋ช ์ ํ์์ด ๊ฐ์์ ์ ์ฒญํ๋ค๊ณ ๊ฐ์ ํ์. A ํ์์ ์ด์ฝ๋ฐ, B ํ์์ ์ฌํ, C ํ์์ ์ ์ฒญํ์ง ์์๋ค. ์ผ์ชฝ ํ ์ด๋ธ์ ํ์ A, B, C๊ฐ ์๊ณ ์ค๋ฅธ์ชฝ ํ ์ด๋ธ์ A(์ด์ฝ๋ฐ), B(์ฌํ)์ด ์๋ค. (C๋ ๊ฐ์์ ์ ์ฒญํ์ง ์์์ผ๋ ์ค๋ฅธ์ชฝ ํ ์ด๋ธ์ ์๋ค)
ineer join(๋ด๋ถ ์กฐ์ธ)์ [ ํ์๊ณผ ๊ฐ์์ด ๋ ๋ค ์๋ ๊ฒฝ์ฐ๋ง ๊ฐ์ ธ์ ]๋ผ์ ๊ฒฐ๊ณผ์ ์ผ๋ก A(์ด์ฝ๋ฐ)์ B(์ฌํ)๋ง ๋์ค๊ณ C๋ ์ฌ๋ผ์ง๋ค.
left outer join(์ผ์ชฝ ์ธ๋ถ ์กฐ์ธ)์ [ ํ์์ ๋ชจ๋ ๋ณด์ฌ์ค. ๋์ ๊ฐ์์ด ์์ผ๋ฉด ์์ ๋ถ์ด๊ณ ์์ผ๋ฉด null๋ก ๋ณด์ฌ์ค ]๋ผ์ A(์ด์ฝ๋ฐ), B(์ฌํ), C(NULL)๋ก ํ์ 3๋ช ๋ชจ๋ ์ ์ง๋๋ค. right outer join์ ๊ฐ์์ ๋ชจ๋ ๋ณด์ฌ์ฃผ๋๊ฑฐ๋๊น C๋ ๋๋ฝ๋๊ฒ ๋๋ค.
์ด๋ ๊ฒ @EntityGraph๊ฐ ์ left outer join์ ์ฌ์ฉํ๋์ง ์๊ฒ๋์๋ค. ๊ทธ๋ฌ๋ ์ผ์ชฝ ํ ์ด๋ธ์ ์ฃผ์ธ๊ณต์ ๋ฐฐ์นํด์ผํ๋ค. ํด๋น ์ฝ๋์์ ๊ฒ์๊ธ์ด ์ฃผ์ธ๊ณต์ด๋ค. ๊ฒ์๊ธ์ ์กฐํํ ๊ฑด๋ฐ, ์์ฑ์๊ฐ ํฌํจ๋์ด์์ผ๋ฉด ํจ๊ป ๋ณด์ฌ์ค๋ผ๊ณ ํด์ผํ๋ค. ๋ง์ฝ @EntityGraph๊ฐ right outer join์ผ๋ก ์ฌ์ฉํ๋ฉด ๊ฒ์๊ธ์ ์กฐํํ๋ ค๊ณ ํ๊ฑด๋ฐ, ๊ฒ์๊ธ์ ์์ฑํ์ง ์์ ๋ฉค๋ฒ๊น์ง ๋ชจ๋ ๋์ฌ ์ ์๊ฒ ๋๋ค.
Q. @EntityGraph๋ ์ธ์ ์จ์ผํ ๊น?
A. LAZY๋ฅผ ๊ธฐ๋ณธ ๊ฐ์ผ๋ก ์ค์ ํ ๊ณณ์์ ์ฐ๊ด ๋ฐ์ดํฐ๊ฐ ํ๊บผ๋ฒ์ ํ์ํ ๋ ํด๋น ๋ ํฌ์งํ ๋ฆฌ ๋ฉ์๋์๋ง @EntityGraph๋ฅผ ์ฌ์ฉํ๋๊ฒ ์ข๋ค. ๋ถ๋ชจ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์์ ์ํฐํฐ์ ํ๋๊ฐ ๋ฐ๋์ ํ์ํ ๊ฒฝ์ฐ, ์๋ฅผ ๋ค์ด์ ์ฃผ๋ฌธ ๋ด์ญ์ ์ถ๋ ฅํ ๋ ์ด๋ค ์ํ์ธ์ง ๋ฆฌ์คํธ๊ฐ ๊ผญ ํ์ํ ๊ฒ๊ณผ ๊ฐ์ด ์ด๋ฐ ์์ผ๋ก ๋ฌด์กฐ๊ฑด ์ฎ์ฌ ์์ ๋ ๋ฃ๋๊ฒ ์ข๋ค. ๋์ค์ ๋ชจ๋ํฐ๋ง์ ํด๋ณด๊ณ ์ฟผ๋ฆฌ๊ฐ ๋ง์ด ๋๊ฐ๋ ๊ฑธ ํ์ธํ๋ฉด ๊ทธ๋ ์ ํ์ ์ผ๋ก ์ถ๊ฐํด๋ ์ข๋ค.
Q. ์ฃผ์ํด์ผํ ์ ?
A. ์ค๋ณต ๋ฐ์ดํฐ์ด๋ค. ๋ง์ฝ ๊ฒ์๊ธ 1๊ฐ์ ๋๊ธ์ด 3๊ฐ๊ฐ ๋ฌ๋ ธ๋ค๊ณ ๊ฐ์ ํ์. left join์ ํ๋ฉด ๊ฒฐ๊ณผ ํ์ ๊ฒ์๊ธ ์ ๋ชฉ์ด 3๋ฒ ์ค๋ณตํด์ ๋ํ๋๊ฒ ๋๋ค. ๋ฆฌ์คํธ๋ก ๋ฐ์ผ๋ฉด ๋ฐ์ดํฐ๊ฐ ๋์ด๋ ์ ์์ด์, ์ด๋ฐ ๊ฒฝ์ฐ distinct ํค์๋๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ Set ์๋ฃํ์ ์จ์ ์ค๋ณต์ ์ ๊ฑฐํด์ผํ๋ค.