๐ก
1. Fetch ์ ๋ต ์ดํดํ๊ธฐ: EAGER์ LAZY (Fetch) ์ ๋ต์ ์ฐจ์ด์ ๊ณผ ๋์ ๋ฐฉ์์ ์ดํดํ๋ค.
2. Lazy Loading ๋์ ๋ฐฉ์ ์ดํดํ๊ธฐ: ์ง์ฐ ๋ก๋ฉ์ด ์ด๋ป๊ฒ ์๋ํ๊ณ , ์ธ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋์ง ํ์ตํ๋ค.
3. ์ง์ ์กฐ์ธ(Fetch Join) ์ฌ์ฉํ๊ธฐ: ํ์ํ ๊ฒฝ์ฐ ์ง์ ์กฐ์ธ์ ์ฌ์ฉํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ด๋ค.
1. ๊ฒ์๊ธ ์์ธ๋ณด๊ธฐ ๊ตฌํ (Eager Fetching)
๋ชฉํ: EAGER ํ์น ์ ๋ต์ ์ฌ์ฉํ์ฌ ๊ฒ์๊ธ ์์ธ๋ณด๊ธฐ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ณ , ์ฐ๊ด๋ ๊ฐ์ฒด๊ฐ ์ฆ์ ๋ก๋ฉ๋๋ ๊ฒ์ ํ์ธํฉ๋๋ค.
package com.tenco.blog_v1.board;
import com.tenco.blog_v1.user.User;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.sql.Timestamp;
@NoArgsConstructor
@Entity
@Table(name = "board_tb")
@Data
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // ๊ธฐ๋ณธํค ์ ๋ต db ์์
private Integer id;
private String title;
private String content;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private User user; // ๊ฒ์๊ธ ์์ฑ์ ์ ๋ณด
// created_at ์ปฌ๋ผ๊ณผ ๋งคํํ๋ฉฐ, ์ด ํ๋๋ ๋ฐ์ดํฐ ์ ์ฅ์ ์๋์ผ๋ก ์ค์ ๋จ
@Column(name = "created_at", insertable = false, updatable = false)
private Timestamp createdAt;
@Builder
public Board(Integer id, String title, String content, User user, Timestamp createdAt) {
this.id = id;
this.title = title;
this.content = content;
this.user = user;
this.createdAt = createdAt;
}
}
fetch = FetchType.EAGER ๋ก ์ค์ ํ์ฌ Board ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์ฐ๊ด๋ User ์ํฐํฐ๋ ์ฆ์ ๋ก๋ฉํฉ๋๋ค.
package com.tenco.blog_v1.board;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@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);
}
}
{{> layout/header}}
<div class="container p-5">
<!-- ์์ , ์ญ์ ๋ฒํผ -->
<div class="d-flex justify-content-end">
<a href="/board/{{board.id}}/update-form" class="btn btn-warning me-1">์์ </a>
<form action="/board/{{board.id}}/delete" method="post">
<button class="btn btn-danger">์ญ์ </button>
</form>
</div>
<div class="d-flex justify-content-end">
<b>์์ฑ์</b> : {{board.user.username}}
</div>
<!-- ๊ฒ์๊ธ๋ด์ฉ -->
<div>
<h2><b>{{board.title}}</b></h2>
<hr />
<div class="m-4 p-2">
{{board.content}}
</div>
</div>
<!-- ๋๊ธ -->
<div class="card mt-3">
<!-- ๋๊ธ๋ฑ๋ก -->
<div class="card-body">
<form action="/reply/save" method="post">
<textarea class="form-control" rows="2" name="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-outline-primary mt-1">๋๊ธ๋ฑ๋ก</button>
</div>
</form>
</div>
<!-- ๋๊ธ๋ชฉ๋ก -->
<div class="card-footer">
<b>๋๊ธ๋ฆฌ์คํธ</b>
</div>
<div class="list-group">
<!-- ๋๊ธ์์ดํ
-->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-primary text-white rounded">cos</div>
<div>๋๊ธ ๋ด์ฉ์
๋๋ค</div>
</div>
<form action="/reply/1/delete" method="post">
<button class="btn">๐</button>
</form>
</div>
<!-- ๋๊ธ์์ดํ
-->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-primary text-white rounded">ssar</div>
<div>๋๊ธ ๋ด์ฉ์
๋๋ค</div>
</div>
<form action="/reply/1/delete" method="post">
<button class="btn">๐</button>
</form>
</div>
</div>
</div>
</div>
{{> layout/footer}}
Fetch ์ ๋ต ์ดํดํ๊ธฐ: EAGER์ LAZY (Fetch) ์ ๋ต์ ์ฐจ์ด์ ๊ณผ ๋์ ๋ฐฉ์์ ์ดํดํ๋ค.
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.title,
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
board_tb b1_0
left join
user_tb u1_0
on u1_0.id=b1_0.user_id
where
b1_0.id=?
Hibernate:
select
b1_0.user_id,
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.title
from
board_tb b1_0
where
b1_0.user_id=?
Hibernate:
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.title,
b1_0.user_id
from
board_tb b1_0
where
b1_0.id=?
์ง์ฐ ๋ก๋ฉ์ ํ๋๋ผ๋ ๊ฒฐ๊ตญ ์ฌ์ฉํ๋ ์์ ์ ๊ฐ์ฒด ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ ๋๋ค.
<div class="d-flex justify-content-end">
<b>์์ฑ์</b> : {{ board.user.username }}
</div>
์์ฝ
EAGER ์ ๋ต๊ณผ Lazy ์ ๋ต์ ๋ํ ์ฐจ์ด์ ์ ์ดํด ํ๋ค.
ํ์ง๋ง ๋ ๋ค ์ฌ์ฉํ๋๋ผ๋ N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ ํ ์ ์๋ค.
: ์ง์ฐ ๋ก๋ฉ์ด๋๋ผ๊ณ ์ฐ๊ด๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ ์์ผ ๋๋ค๋ฉด —> ์ฟผ๋ฆฌ๊ฐ ๋๋ฒ ์์ฑ ํธ์ถ ๋๋ค (N + 1 ๋ฌธ์ ๋ฐ์)
BoardController ์ฝ๋ ์์
// ํน์ ๊ฒ์๊ธ ์์ฒญ ํ๋ฉด
// ์ฃผ์์ค๊ณ - http://localhost:8080/board/1
@GetMapping("/board/{id}")
public String detail(@PathVariable(name = "id") Integer id, HttpServletRequest request) {
// JPA API ์ฌ์ฉ
// Board board = boardRepository.findById(id);
// JPQL FETCH join ์ฌ์ฉ
Board board = boardRepository.findByIdJoinUser(id);
request.setAttribute("board", board);
return "board/detail";
}
BoardRepository ์ฝ๋ ์ถ๊ฐ
package com.tenco.blog_v1.board;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@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_tb b JOIN FETCH b.user WHERE b.id = :id ";
return em.createQuery(jpql, Board.class)
.setParameter("id", id)
.getSingleResult();
}
}
User.board ๋ LAZY๋ก ๋ณ๊ฒฝํด์ผ ํจ !!
Hibernate:
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.title,
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
board_tb b1_0
join
user_tb u1_0
on u1_0.id=b1_0.user_id
where
b1_0.id=?
JPQL์ด๋?
- JPQL์ Java Persistence Query Language์ ์ฝ์๋ก, JPA์์ ์ฌ์ฉ๋๋ ๊ฐ์ฒด ์งํฅ ์ฟผ๋ฆฌ ์ธ์ด์ ๋๋ค.
- SQL๊ณผ ์ ์ฌํ์ง๋ง, ํ ์ด๋ธ์ด ์๋ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค.
- JPQL์ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ ๋ฆฝ์ ์ธ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์์ด, ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฒค๋์ ์ข ์๋์ง ์์ต๋๋ค.
- JPQL์ JPA ํ์ค ์คํ์ ์ผ๋ถ๋ก, ๋๋ถ๋ถ์ JPA ๊ตฌํ์ฒด(Hibernate ๋ฑ)์์ ์ง์ํฉ๋๋ค.
JPQL - Fetch Join ํ์ฉ (๊ฒ์๊ธ ์์ธ ๋ณด๊ธฐ)
- Fetch Join์ด๋:
- JPQL์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ผ๋ก, ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ํจ๊ป ์กฐํํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
- ์ง์ฐ ๋ก๋ฉ ์ค์ ๊ณผ ๊ด๊ณ์์ด ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ฆ์ ๋ก๋ฉํฉ๋๋ค.
- ์ฌ์ฉ ์ด์
- N+1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ ํ์๋ฅผ ์ค์ด๊ณ ์ฑ๋ฅ์ ์ต์ ํํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
- ์ฌ์ฉ ๋ฐฉ๋ฒ:
- JPQL ์ฟผ๋ฆฌ์์ JOIN FETCH ๊ตฌ๋ฌธ์ ์ฌ์ฉํ์ฌ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํจ๊ป ์กฐํํฉ๋๋ค.
Fetch Join ์ฌ์ฉ ์ ์ฃผ์์ฌํญ
- ๋ฐ์ดํฐ ์ค๋ณต:
- Fetch Join์ผ๋ก ์ฌ๋ฌ ์ฐ๊ด ์ํฐํฐ๋ฅผ ์กฐ์ธํ๋ฉด ๊ฒฐ๊ณผ๊ฐ ์ค๋ณต๋ ์ ์์ผ๋ฏ๋ก, ํ์ํ ์ํฐํฐ๋ง ์ ํ์ ์ผ๋ก ์กฐ์ธํด์ผ ํฉ๋๋ค.
- ํ์ด์ง ์ ํ:
- JPA์์๋ Fetch Join์ ์ฌ์ฉํ ์ํ์์ ํ์ด์ง์ ์ง์ํ์ง ์์ต๋๋ค.
- ๋ฐ์ดํฐ๊ฐ ๋ง์ ๊ฒฝ์ฐ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์ฆ๊ฐํ ์ ์์ผ๋ฏ๋ก ์ฃผ์ํด์ผ ํฉ๋๋ค.
- ์ ์ ํ ์ฌ์ฉ:
- ๋ฌด๋ถ๋ณํ ์ฌ์ฉ์ ์คํ๋ ค ์ฑ๋ฅ์ ์ ํ์ํฌ ์ ์์ผ๋ฏ๋ก, ํ์ํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํด์ผ ํฉ๋๋ค.
JPQL๊ณผ SQL์ ์ฐจ์ด์
- JPQL์ ๊ฐ์ฒด ์งํฅ ์ฟผ๋ฆฌ ์ธ์ด๋ก, ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ์ฟผ๋ฆฌํฉ๋๋ค.
- SQL์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ๊ณผ ์ปฌ๋ผ์ ๋์์ผ๋ก ์ฟผ๋ฆฌํฉ๋๋ค.
- JPQL์ ์ํฐํฐ์ ๊ทธ ์ฌ์ด์ ์ฐ๊ด ๊ด๊ณ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ ๋ฆฝ์ ์ธ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
์ฐ๋ฆฌ๋ ์ด๋ค ์ ๋ต์ ์ ํํด์ผ ํ๋?
- ๊ธฐ๋ณธ์ ์ผ๋ก Lazy Fetching์ ์ฌ์ฉํ์ฌ ๋ถํ์ํ ๋ฐ์ดํฐ ๋ก๋ฉ์ ๋ฐฉ์งํฉ๋๋ค.
- ํ์ํ ๊ฒฝ์ฐ Fetch Join ๋ฑ์ ์ฌ์ฉํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํฉ๋๋ค.
์์ฝ
- Eager Fetching: ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ฆ์ ๋ก๋ฉํ์ฌ, ์ํฐํฐ ์กฐํ ์ ํจ๊ป ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- ์ฅ์ : ์ฐ๊ด๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ค.
- ๋จ์ : ๋ถํ์ํ ๋ฐ์ดํฐ๊น์ง ๋ก๋ฉ๋์ด ์ฑ๋ฅ์ด ์ ํ๋ ์ ์๋ค.
- Lazy Fetching: ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ค์ ๋ก ์ ๊ทผํ ๋๊น์ง ๋ก๋ฉ์ ์ง์ฐ์ํต๋๋ค.
- ์ฅ์ : ํ์ํ ์์ ์๋ง ๋ฐ์ดํฐ๋ฅผ ๋ก๋ฉํ์ฌ ์ฑ๋ฅ์ ํฅ์์ํจ๋ค.
- ๋จ์ : ์ง์ฐ ๋ก๋ฉ ์์ ์ ์ถ๊ฐ์ ์ธ SQL ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.
- Fetch Join: ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํจ๊ป ๋ก๋ฉํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํฉ๋๋ค.
- ์ฌ์ฉ ์ ์ฃผ์์ : ๋๋ฌด ๋ง์ด ์ฌ์ฉํ๋ฉด ์คํ๋ ค ์ฑ๋ฅ์ด ์ ํ๋ ์ ์์ผ๋ฏ๋ก ํ์ํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํฉ๋๋ค.
Fetch ์ ๋ต ์ ํ ๊ธฐ์ค
- Lazy Fetching์ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ์ฌ ๋ถํ์ํ ๋ฐ์ดํฐ ๋ก๋ฉ์ ๋ฐฉ์งํฉ๋๋ค.
- Eager Fetching์ ๋ฐ๋์ ํจ๊ป ๋ก๋ฉํด์ผ ํ๋ ์ฐ๊ด ๋ฐ์ดํฐ๊ฐ ์์ ๋ ์ ์คํ ์ฌ์ฉํฉ๋๋ค.
Fetch Join ์ฌ์ฉ ์ ์ฃผ์์ฌํญ
- Fetch Join์ ์ฆ์ ๋ก๋ฉ๊ณผ ์ ์ฌํ๊ฒ ์๋ํ์ง๋ง, ์ํ๋ ์์ ์ ์ ์ฉํ ์ ์์ต๋๋ค.
- ๋ณต์กํ ์ฟผ๋ฆฌ๋ ๋ฐ์ดํฐ ์์ด ๋ง์ ๊ฒฝ์ฐ ์คํ๋ ค ์ฑ๋ฅ์ด ์ ํ๋ ์ ์์ผ๋ฏ๋ก ์ฃผ์ํด์ผ ํฉ๋๋ค.
N+1 ๋ฌธ์
- ์ง์ฐ ๋ก๋ฉ์ ์ฌ์ฉํ ๋ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ์กฐํํ๋ฉด, ์์์น ๋ชปํ ๋ง์ ์์ SQL ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
- ์ด๋ฅผ N+1 ๋ฌธ์ ๋ผ๊ณ ํ๋ฉฐ, Fetch Join ๋ฑ์ ์ฌ์ฉํ์ฌ ํด๊ฒฐํ ์ ์์ต๋๋ค.