์•Œ๋ฆผ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๋Šฅ์„ ๊ณ ๋ คํ•œ Fetch ์ „๋žต ์ˆ˜๋ฆฝ (@EntityGraph)

์•Œ๋ฆผ ์‹œ์Šคํ…œ(Notification)์„ ๊ธฐํšํ•˜๋ฉด์„œ ๊ฐ€์žฅ ์šฐ๋ ค๋˜์—ˆ๋˜ ๋ถ€๋ถ„์€ โ€œ์‚ฌ์šฉ์ž๊ฐ€ ์•Œ๋ฆผ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ๋•Œ ๋ฐœ์ƒํ•  DB ๋ถ€ํ•˜โ€ ์˜€์Šต๋‹ˆ๋‹ค. ์•Œ๋ฆผ์€ ๋‹จ์ˆœ ํ…์ŠคํŠธ ์ •๋ณด๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, โ€˜๋ˆ„๊ฐ€(Sender)โ€™ ๋ณด๋ƒˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ํ•„์ˆ˜์ ์œผ๋กœ ํ•จ๊ป˜ ๋…ธ์ถœ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

1. ๊ธฐํš ๋‹จ๊ณ„์—์„œ์˜ ๊ณ ๋ ค์‚ฌํ•ญ: N+1 ๋ฌธ์ œ์˜ ์„ ์ œ์  ๋ฐฉ์–ด

JPA๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ด€ ๊ด€๊ณ„(ManyToOne)๋ฅผ ๋งบ์„ ๋•Œ, ๊ธฐ๋ณธ ์ „๋žต์ธ Lazy Loading(์ง€์—ฐ ๋กœ๋”ฉ)์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‹œ๋‚˜๋ฆฌ์˜ค๊ฐ€ ์˜ˆ์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

  1. ์•Œ๋ฆผ ๋ชฉ๋ก ์กฐํšŒ ์š”์ฒญ: ์‚ฌ์šฉ์ž๊ฐ€ ์•Œ๋ฆผํ•จ์— ๋“ค์–ด์˜ต๋‹ˆ๋‹ค.

  2. ์•Œ๋ฆผ ๋ฐ์ดํ„ฐ ๋กœ๋“œ: DB์—์„œ ์ตœ๊ทผ ์•Œ๋ฆผ 20๊ฐœ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. (SELECT * FROM notification ...)

  3. ๋ฐœ์‹ ์ž ์ •๋ณด ๋กœ๋“œ: 20๊ฐœ์˜ ์•Œ๋ฆผ์„ ํ™”๋ฉด์— ๊ทธ๋ฆฌ๊ธฐ ์œ„ํ•ด, ๊ฐ ์•Œ๋ฆผ๋งˆ๋‹ค ์—ฐ๊ฒฐ๋œ Sender(User) ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ 20๋ฒˆ ์ถ”๊ฐ€๋กœ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ด๋Š” ์กฐํšŒ ํ•œ ๋ฒˆ์— ์ด 21๋ฒˆ(1+N)์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๋น„ํšจ์œจ์ ์ธ ๊ตฌ์กฐ์ด๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ๋ชฐ๋ฆด ๊ฒฝ์šฐ DB ์ปค๋„ฅ์…˜ ํ’€์„ ๋น ๋ฅด๊ฒŒ ๊ณ ๊ฐˆ์‹œํ‚ฌ ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

2. ๊ตฌํ˜„ ์ „๋žต: @EntityGraph๋ฅผ ํ†ตํ•œ Eager Fetching

์šฐ๋ฆฌ๋Š” ์•Œ๋ฆผ ์กฐํšŒ ์‹œ Sender ์ •๋ณด๊ฐ€ โ€œ์„ ํƒ์ โ€ ์ด ์•„๋‹ˆ๋ผ โ€œํ•„์ˆ˜์ โ€ ์œผ๋กœ ํ•„์š”ํ•˜๋‹ค๋Š” ์ ์— ์ฃผ๋ชฉํ–ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ฟผ๋ฆฌ ์‹คํ–‰ ์‹œ์ ์— ์—ฐ๊ด€๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๋Š” Fetch Join ์ „๋žต์„ ์ฑ„ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ Implementation Code (NotificationRepository.java)

public interface NotificationRepository extends JpaRepository<NotificationEntity, Long> {
 
    // [Strategy] ์•Œ๋ฆผ ์กฐํšŒ ์‹œ Sender ์ •๋ณด๋Š” ํ•„์ˆ˜์ด๋ฏ€๋กœ Fetch Join์œผ๋กœ ํ•œ ๋ฒˆ์— ์กฐํšŒ
    // JPQL์„ ์ง์ ‘ ์งœ๋Š” ๊ฒƒ๋ณด๋‹ค ๊ฐ€๋…์„ฑ์ด ์ข‹์€ @EntityGraph ์‚ฌ์šฉ
    @EntityGraph(attributePaths = {"sender"})
    Slice<NotificationEntity> findAllByReceiverUserIdOrderByCreatedAtDesc(Long receiverId, Pageable pageable);
    
}

๐Ÿ’ก ์„ค๊ณ„ ์˜๋„ ๋ฐ ํšจ๊ณผ

  1. ์ฟผ๋ฆฌ ๋น„์šฉ ์ตœ์†Œํ™”: @EntityGraph๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ LEFT OUTER JOIN์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. N๋ฒˆ์˜ ๋„คํŠธ์›Œํฌ ์™•๋ณต ๋น„์šฉ์„ ๋‹จ 1๋ฒˆ์œผ๋กœ ์ค„์—ฌ ์‘๋‹ต ์†๋„๋ฅผ ํš๊ธฐ์ ์œผ๋กœ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

  2. ์œ ์ง€๋ณด์ˆ˜์„ฑ: JPQL๋กœ SELECT n FROM NotificationEntity n JOIN FETCH n.sender ...๋ผ๊ณ  ๊ธธ๊ฒŒ ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹ , ์–ด๋…ธํ…Œ์ด์…˜ ํ•˜๋‚˜๋กœ **โ€œ์ด ์ฟผ๋ฆฌ๋Š” Sender ๋ฐ์ดํ„ฐ๋„ ์ฆ‰์‹œ ํ•„์š”ํ•˜๋‹คโ€**๋Š” ์˜๋„๋ฅผ ๋ช…ํ™•ํžˆ ๋“œ๋Ÿฌ๋ƒˆ์Šต๋‹ˆ๋‹ค.

โš™๏ธ ๋น„์œ : ๋ฐฐ๋‹ฌ ๊ธฐ์‚ฌ์˜ ํšจ์œจ์  ๋™์„ 

์ƒํ™ฉ: ์•„ํŒŒํŠธ 101ํ˜ธ๋ถ€ํ„ฐ 120ํ˜ธ๊นŒ์ง€ ํƒ๋ฐฐ(์•Œ๋ฆผ)๋ฅผ ๋ฐฐ๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ๊ธฐ๋ณธ ์ „๋žต(Lazy): ๋ฐฐ๋‹ฌ ๊ธฐ์‚ฌ๊ฐ€ 101ํ˜ธ์— ๋ฌผ๊ฑด์„ ์ฃผ๊ณ , ๋‹ค์‹œ ๋ฌผ๋ฅ˜์„ผํ„ฐ(DB)๋กœ ๋Œ์•„๊ฐ€์„œ 102ํ˜ธ ๋ฌผ๊ฑด์„ ๊ฐ€์ ธ์˜ค๋Š” ์‹์ž…๋‹ˆ๋‹ค. (์ด 21๋ฒˆ ์™•๋ณต)

  • ์ฑ„ํƒํ•œ ์ „๋žต(EntityGraph): ์ถœ๋ฐœํ•  ๋•Œ ์ปค๋‹ค๋ž€ ์นดํŠธ์— 101ํ˜ธ~120ํ˜ธ ๋ฌผ๊ฑด๊ณผ ์ˆ˜์ทจ์ธ ์ •๋ณด(Sender)๋ฅผ ํ•œ ๋ฒˆ์— ์‹ค์–ด์„œ(Join) ์•„ํŒŒํŠธ๋กœ ์˜ต๋‹ˆ๋‹ค. (๋‹จ 1๋ฒˆ ์ด๋™)

์ด๋Ÿฌํ•œ ์„ค๊ณ„๋ฅผ ํ†ตํ•ด ํŠธ๋ž˜ํ”ฝ์ด ์ฆ๊ฐ€ํ•ด๋„ DB ๋ถ€ํ•˜๋ฅผ ์ผ์ • ์ˆ˜์ค€์œผ๋กœ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ–ˆ์Šต๋‹ˆ๋‹ค.