์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

STEP 02 - ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก๋ณด๊ธฐ (Post List View)

๋ฏธ๋กœ910 2024. 10. 8. 10:01
๐Ÿ’ก
1. JPA์—์„œ์˜ ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ•™์Šตํ•œ๋‹ค.
2. N+1 ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ํ•™์Šตํ•˜๊ธฐ: ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ์ธํ•œ N+1 ๋ฌธ์ œ๋ฅผ ํ™•์ธํ•˜๊ณ  ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šด๋‹ค.
3. ๋ฐฐ์น˜ ์‚ฌ์ด์ฆˆ(Batch Size) ์„ค์ • ์ดํ•ดํ•˜๊ธฐ: default_batch_fetch_size๋ฅผ ์„ค์ •ํ•˜์—ฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ•™์Šตํ•œ๋‹ค.
4. ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก๋ณด๊ธฐ ์ปจํŠธ๋กค๋Ÿฌ ๋ฐ ๋ทฐ ๊ตฌํ˜„ํ•˜๊ธฐ: ์‹ค์ œ๋กœ ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์„ ํ‘œ์‹œํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์™€ ํ™”๋ฉด์„ ์ž‘์„ฑํ•œ๋‹ค.

 

1. ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก๋ณด๊ธฐ ์ฟผ๋ฆฌ ์ž‘์„ฑ (Eager Fetching)

๋ชฉํ‘œ: EAGER ํŽ˜์น˜ ์ „๋žต์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๊ณ , ์—ฐ๊ด€๋œ User ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋กœ๋”ฉ๋˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

package com.tenco.blog_v1.board;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;

@RequiredArgsConstructor
@Repository // IoC
public class BoardRepository  {

    private final EntityManager em;

    /**
     * ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ ๋ฉ”์„œ๋“œ
     * @param id ์กฐํšŒํ•  ๊ฒŒ์‹œ๊ธ€ ID
     * @return ์กฐํšŒ๋œ Board ์—”ํ‹ฐํ‹ฐ, ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด null ๋ฐ˜ํ™˜
     */
    public Board findById(int id) {
         return em.find(Board.class, id);
    }

    /**
     * JPQL์˜ FETCH ์กฐ์ธ ์‚ฌ์šฉ - ์„ฑ๋Šฅ ์ตœ์ ํ™”
     * ํ•œ๋ฐฉ์— ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ฆ‰, ์ง์ ‘ ์กฐ์ธํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ ์˜ต๋‹ˆ๋‹ค.
     * @param id
     * @return
     */
    public Board findByIdJoinUser(int id) {
        // JPQL -> Fetch join ์„ ์‚ฌ์šฉํ•ด ๋ณด์ž.
        String jpql = " SELECT b FROM Board b JOIN FETCH b.user WHERE b.id = :id ";
        return em.createQuery(jpql, Board.class)
                .setParameter("id", id)
                .getSingleResult();
    }

    /**
     * ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ
     * @return ๊ฒŒ์‹œ๊ธ€ ๋ฆฌ์ŠคํŠธ
     */
    public List<Board> findAll() {
        TypedQuery<Board> jpql = em.createQuery(" SELECT b FROM Board b ORDER BY b.id DESC ", Board.class);
        return jpql.getResultList();
    }

}

JPQL ์ฟผ๋ฆฌ: "SELECT b FROM Board b ORDER BY b.id DESC"

ํ•ด์„:

  • SELECT b: Board ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•˜์—ฌ b๋ผ๋Š” ๋ณ„์นญ(alias)์œผ๋กœ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
  • FROM Board b: ๋ฐ์ดํ„ฐ ์†Œ์Šค๋กœ Board ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๋ณ„์นญ b๋ฅผ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.
  • ORDER BY b.id DESC: b.id๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค.

์š”์•ฝ:

  • Board ์—”ํ‹ฐํ‹ฐ์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ id ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.

 

Board ์—”ํ‹ฐํ‹ฐ ์ฝ”๋“œ ์ˆ˜์ •
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_id")
    private User user; // ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ์ž ์ •๋ณด

 

BoardController ์ˆ˜์ •
    @GetMapping("/")
    public String index(Model model) {

        //List<Board> boardList = boardNativeRepository.findAll();
        // ์ฝ”๋“œ ์ˆ˜์ •
        List<Board> boardList = boardRepository.findAll();
        model.addAttribute("boardList", boardList);
        return "index";
    }

 

index.mustache ์ˆ˜์ •
{{> layout/header}}

<div class="container p-5">
    <!-- ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์„ ๋ฐ˜๋ณต ์ถœ๋ ฅ (boardList๊ฐ€ null์ด ์•„๋‹ˆ๊ณ  ๋น„์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด ์ถœ๋ ฅ) -->
    {{#boardList}}
        <div class="card mb-3">
            <div class="card-body">
                <h4 class="card-title mb-3">{{title}} | ์ž‘์„ฑ์ž : {{user.username}}</h4>
                <a href="/board/{{id}}" class="btn btn-primary">์ƒ์„ธ๋ณด๊ธฐ</a>
            </div>
        </div>
    {{/boardList}} <!-- ๋ฐ˜๋“œ์‹œ ์„น์…˜์„ ๋‹ซ๋Š” ํƒœ๊ทธ๊ฐ€ ํ•„์š” -->

    <!-- ๊ฒŒ์‹œ๊ธ€์ด ์—†์„ ๊ฒฝ์šฐ ์ถœ๋ ฅํ•  ๋‚ด์šฉ -->
    {{^boardList}}
        <p>๊ฒŒ์‹œ๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.</p>
    {{/boardList}}

    <ul class="pagination d-flex justify-content-center">
        <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
        <li class="page-item"><a class="page-link" href="#">Next</a></li>
    </ul>
</div>

{{> layout/footer}}

 

1. N+1 ๋ฌธ์ œ๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”?

  • N+1 ๋ฌธ์ œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ N๊ฐœ์˜ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•œ ํ›„, ๊ฐ ์—”ํ‹ฐํ‹ฐ์— ์—ฐ๊ด€๋œ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง€์—ฐ ๋กœ๋”ฉ(Lazy Loading)์œผ๋กœ ์กฐํšŒํ•  ๋•Œ ์ถ”๊ฐ€์ ์ธ N๊ฐœ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ํ˜„์ƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ด 1 + N๊ฐœ์˜ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋˜๋ฉฐ, ์ด๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€ํ•˜์™€ ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ ์ฆ๊ฐ€๋กœ ์ธํ•ด ์„ฑ๋Šฅ ์ €ํ•˜์˜ ์›์ธ์ด ๋ฉ๋‹ˆ๋‹ค.

2. ์™œ ๋ฌธ์ œ๊ฐ€ ๋˜๋‚˜์š”?

  • ์„ฑ๋Šฅ ์ €ํ•˜: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐ„์— ๋ถˆํ•„์š”ํ•œ ํ†ต์‹ ์ด ๋งŽ์•„์ ธ ์‘๋‹ต ์‹œ๊ฐ„์ด ๊ธธ์–ด์ง‘๋‹ˆ๋‹ค.
  • ๋ฆฌ์†Œ์Šค ๋‚ญ๋น„: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์—ฐ๊ฒฐ ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ•˜๊ณ , CPU์™€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ๋Š˜์–ด๋‚ฉ๋‹ˆ๋‹ค.
  • ํ™•์žฅ์„ฑ ๋ฌธ์ œ: ๋ฐ์ดํ„ฐ ์–‘์ด ๋งŽ์•„์งˆ์ˆ˜๋ก(์˜ˆ: ๊ฒŒ์‹œ๊ธ€์ด ์ˆ˜์ฒœ ๊ฐœ ์ด์ƒ) ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ๋”์šฑ ์‹ฌ๊ฐํ•ด์ง‘๋‹ˆ๋‹ค.

3. N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ (Batch Size ์„ค์ •)

์Šคํ”„๋ง JPA์—์„œ default_batch_fetch_size ์„ค์ •์€ ๋ณต์žกํ•œ ์กฐํšŒ์ฟผ๋ฆฌ ์ž‘์„ฑ์‹œ, ์ง€์—ฐ๋กœ๋”ฉ์œผ๋กœ ๋ฐœ์ƒํ•ด์•ผ ํ•˜๋Š” ์ฟผ๋ฆฌ๋ฅผ IN์ ˆ๋กœ ํ•œ๋ฒˆ์— ๋ชจ์•„๋ณด๋‚ด๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

๋ชฉํ‘œ: ์ง€์—ฐ ๋กœ๋”ฉ ์‹œ ๋ฐœ์ƒํ•˜๋Š” N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด default_batch_fetch_size ์„ค์ •์„ ์‚ฌ์šฉํ•œ๋‹ค.

 

default_batch_fetch_size

  • ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ์ฟผ๋ฆฌ๋ฅผ IN ์ ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์— ๋ชจ์•„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ์„ค์ •์ด๋‹ค.
  • ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ฌ ์—”ํ‹ฐํ‹ฐ์˜ ์ˆ˜๋ฅผ ์ง€์ •ํ•œ๋‹ค.

 

์‹คํ–‰ ์ฟผ๋ฆฌ ํ™•์ธ
Hibernate: 
    select
        b1_0.id,
        b1_0.content,
        b1_0.created_at,
        b1_0.title,
        b1_0.user_id 
    from
        board_tb b1_0 
    order by
        b1_0.id desc
Hibernate: 
    select
        u1_0.id,
        u1_0.created_at,
        u1_0.email,
        u1_0.password,
        u1_0.username 
    from
        user_tb u1_0 
    where
        u1_0.id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

 


Batch Size ์„ค์ •๊ณผ Fetch Join(๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ๋ณด๊ธฐ์—์„œ ์‚ฌ์šฉ)์˜ ์ฐจ์ด์ 

 

Batch Size ์„ค์ •

  • ์„ค๋ช…: default_batch_fetch_size ์„ค์ •์„ ํ†ตํ•ด Hibernate๊ฐ€ ์ง€์—ฐ ๋กœ๋”ฉ ์‹œ ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•œ ๋ฒˆ์— ๋กœ๋”ฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด N+1 ๋ฌธ์ œ๋ฅผ ์™„ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋™์ž‘ ๋ฐฉ์‹: ์˜ˆ๋ฅผ ๋“ค์–ด, default_batch_fetch_size: 10์œผ๋กœ ์„ค์ •ํ•˜๋ฉด, Hibernate๋Š” ํ•œ ๋ฒˆ์— ์ตœ๋Œ€ 10๊ฐœ์˜ User ์—”ํ‹ฐํ‹ฐ๋ฅผ IN ์ ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ๋กœ๋”ฉํ•ฉ๋‹ˆ๋‹ค.
  • ์ฟผ๋ฆฌ ์ˆ˜: ๋ฉ”์ธ ์ฟผ๋ฆฌ 1๊ฐœ + ๊ด€๋ จ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐฐ์น˜๋กœ ๋กœ๋”ฉํ•˜๋Š” ์ฟผ๋ฆฌ 1๊ฐœ → ์ด 2๊ฐœ์˜ ์ฟผ๋ฆฌ ์‹คํ–‰.

Fetch Join ( ์ง€์—ฐ ๋กœ๋”ฉ๊ณผ ๊ด€๊ณ„ ์—†์ด ์ฆ‰์‹œ ๋กœ๋”ฉ ๋จ)

  • ์„ค๋ช…: JPQL์—์„œ JOIN FETCH๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ํ•จ๊ป˜ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
  • ๋™์ž‘ ๋ฐฉ์‹: Board์™€ User๋ฅผ ์กฐ์ธํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  • ์ฟผ๋ฆฌ ์ˆ˜: ๋ฉ”์ธ ์ฟผ๋ฆฌ 1๊ฐœ๋กœ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ๋กœ๋”ฉ → ์ด 1๊ฐœ์˜ ์ฟผ๋ฆฌ ์‹คํ–‰.

Fetch Join์„ ์‚ฌ์šฉํ•  ๋•Œ

  • ์ฆ‰์‹œ ๋กœ๋”ฉ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ: ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ฆ‰์‹œ ๋กœ๋”ฉํ•˜์—ฌ ํ•œ ๋ฒˆ์— ๋ชจ๋‘ ์‚ฌ์šฉํ•ด์•ผ ํ•  ๋•Œ.
  • N+1 ๋ฌธ์ œ ์™„์ „ ํ•ด๊ฒฐ์ด ํ•„์š”ํ•  ๋•Œ: ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ชจ๋‘ ํ•œ ๋ฒˆ์— ๋กœ๋”ฉํ•˜์—ฌ ์ฟผ๋ฆฌ ์ˆ˜๋ฅผ ์ตœ์†Œํ™”ํ•˜๊ณ ์ž ํ•  ๋•Œ.
  • ํŽ˜์ด์ง•์ด ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ: Fetch Join์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ํŽ˜์ด์ง•๊ณผ์˜ ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ํŽ˜์ด์ง•์ด ํ•„์š” ์—†๋‹ค๋ฉด Fetch Join์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Batch Size ์„ค์ •์„ ์‚ฌ์šฉํ•  ๋•Œ

  • ํŽ˜์ด์ง•๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ: ํŽ˜์ด์ง•์ด ํ•„์š”ํ•œ ์ƒํ™ฉ์—์„œ๋Š” Fetch Join ๋Œ€์‹  Batch Size ์„ค์ •์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๋ถ€๋ถ„์ ์œผ๋กœ ๋กœ๋”ฉ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ: ๋ชจ๋“  ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•œ ๋ฒˆ์— ๋กœ๋”ฉํ•˜์ง€ ์•Š๊ณ , ํ•„์š”ํ•œ ๋งŒํผ๋งŒ ๋ฐฐ์น˜๋กœ ๋กœ๋”ฉํ•˜๊ณ ์ž ํ•  ๋•Œ.
  • ๋ณต์žกํ•œ ์กฐ์ธ ์—†์ด๋„ ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ: ๋ณต์žกํ•œ ์กฐ์ธ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ ๋„ ์ฟผ๋ฆฌ ์ˆ˜๋ฅผ ์ค„์ด๊ณ ์ž ํ•  ๋•Œ Batch Size ์„ค์ •์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ฑ๋Šฅ ๋น„๊ต

Fetch Join์„ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ

  • ์ฟผ๋ฆฌ ์ˆ˜: 1๊ฐœ (๊ฒŒ์‹œ๊ธ€๊ณผ ์ž‘์„ฑ์ž ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์กฐํšŒ)
  • ์„ฑ๋Šฅ: ์ผ๋ฐ˜์ ์œผ๋กœ ๋” ๋น ๋ฆ„, ๋„คํŠธ์›Œํฌ ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€ํ•˜๊ฐ€ ์ค„์–ด๋“ฌ
  • ๋‹จ์ : ๋ฐ์ดํ„ฐ ์ค‘๋ณต ๊ฐ€๋Šฅ์„ฑ, ํŽ˜์ด์ง•๊ณผ์˜ ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ

Batch Size ์„ค์ •์„ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ

  • ์ฟผ๋ฆฌ ์ˆ˜: 2๊ฐœ (๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ + ๋ฐฐ์น˜๋กœ ์ž‘์„ฑ์ž ์กฐํšŒ)
  • ์„ฑ๋Šฅ: Fetch Join๋ณด๋‹ค๋Š” ์•ฝ๊ฐ„ ๋Š๋ฆด ์ˆ˜ ์žˆ์œผ๋‚˜, ํŽ˜์ด์ง•๊ณผ์˜ ํ˜ธํ™˜์„ฑ ๋“ฑ ์œ ์—ฐ์„ฑ์ด ๋†’์Œ
  • ์žฅ์ : ํŽ˜์ด์ง•๊ณผ์˜ ํ˜ธํ™˜์„ฑ, ๋ฐ์ดํ„ฐ ์ค‘๋ณต ๋ฌธ์ œ ์—†์Œ