ย 3์ฃผ๊ฐ์ ํ๋ก์ ํธ๊ฐ ์ข
๋ฃ๋์๋ค. ์ด๋ฒ ํ๋ก์ ํธ๋ ์ค์ค๋ก ๊ต์ฅํ ์ป์ ๊ฒ ๋ง์ ํ๋ก์ ํธ์๋ค.
์ดํ ํ๋ก์ ํธ ํ๊ณ ์์ ๋ค๋ฃฐ ์์ ์ด์ง๋ง, ์ด๋ ต๊ณ ํ๋ ์ผ๋ค๋ ๋ง์์ผ๋ ๊ฒฐ๊ณผ์ ์ผ๋ก ์ด๋์ ๋ ๊ด์ฐฎ์ ํ๋ฆฌํฐ์ ๊ฒฐ๊ณผ๋ฌผ์ ๋ฝ์๋ธ ๋ฏํ์ฌ ์กฐ๊ธ ๋ง์์ด ๋์ธ๋ค.
ย ์ด๋ฒ ์ฐ๋ฆฌ ํ ํ๋ก์ ํธ ์ฃผ์ ๋ ํธํ
์์ฝ์ด๋ค. ์ด ํ๋ก์ ํธ์์ ํ์๋ ํธํ
๋ฐ ๊ฐ์ค ๋๋ฉ์ธ์ ์์
์ ๋งก์์ ์งํํ์๋ค.
๊ทธ๋ฌ๋ค๋ณด๋ ๊ฐ์ฅ ๋ง์ด ์๊ฐํด๋ณธ ๋ถ๋ถ์ด ๋ฐ๋ก ์ฑ๋ฅ์ด๋ค. ํธํ
๊ณผ ๊ฐ์ค์ด๋ผ๋ ๊ต์ฅํ ๋ง์ ์์ ๋ฐ์ดํฐ๋ค์ ์ด๋ป๊ฒ ํ๋ฉด ์ข ๋ ํจ๊ณผ์ ์ผ๋ก ๋ค๋ฃฐ ์ ์์๊น.
ย ์ง๊ธ ํ๋ก์ ํธ์๋ default_batch_fetch_size ๊ฐ ์ ์ฉ๋์ด์๋ค.
์ฒ์์๋ ์ด๊ฑฐ ํ๋๋ก N+1 ๋ฌธ์ ๋ฅผ ์ ๋ถ ํด๊ฒฐํ ์ ์๋ฆฌ๋ฅผ ๋ฃ๊ณ ๋์ฑ ์ต์ ํํ ์ ์๋ ์ฟผ๋ฆฌ ์์ฑ์ ์ง์คํ์ง๋ง, ๊ฐ์ฌ๋๊ณผ์ Q&A ์ดํ๋ก ๋ฐฐ์น๋ฅผ ์ ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ค.
ย ์ถ๊ฐ๋ก, 1:N ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด์ ์ด๋ค ๋ฐฉ๋ฒ์ด ๊ฐ์ฅ ์ข์๊น์ ๋ํด์๋ ๋ง์ ๊ณ ๋ฏผ์ด ์์๋ค.
JOIN ์ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉํ๋ ๊ฒ๊ณผ Batch Fetching ์ค ์ด๋ค ๊ฒ์ด ๋ ์ข์ ์ฑ๋ฅ์ ๋ฐํํ ์ ์์๊น.
DB์์ ์ผ๋ถ ๋ฐ์ดํฐ๋ง ๊ฐ์ ธ์ค๊ณ ๋ด๋ถ ๋ฉ๋ชจ๋ฆฌ์์ ๋ณํ ๊ณผ์ ์ ๊ฑฐ์น๋ ๊ฒ์ด ์ข์๊น, DB ์์ ๋ชจ๋ ์์
์ ๋๋ด๊ณ ๊ฐ์ ธ์ค๋ ๊ฒ์ด ๋ ์ข์๊น.
์ง๊ธ ์ด ํ๋ก์ ํธ์๋ ์ด๋ค ๊ฒ์ด ๋ ์ข์๊น.
ย ํ์ฌ๋ ํธํ
์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ฌ ๋ ํธํ
์ด๋ฏธ์ง์ ์ธ๋ค์ผ์ ๊ฐ์ ธ์ค๊ธฐ ์ํด, ์ด๋ฏธ์ง DB์ ์ฒซ๋ฒ์งธ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐ ์ํด์ ์กฐ๊ฑด๋ฌธ์ ์๋ธ์ฟผ๋ฆฌ๋ฅผ ์ถ๊ฐํ์๋ค.
์ง๊ธ ๋น์ฅ์ ์ฑ๊ธ ์๋ฒ์ ์๊ท๋ชจ ๋ฐ์ดํฐ์
์ ๊ฐ์ง๊ณ ์งํํ๋ ํ๋ก์ ํธ์ด๊ธฐ ๋๋ฌธ์ ์ด๋ฌํ ๋ฐฉ๋ฒ์ ์ ์ฉํ์ง๋ง, ์ง๊ธ์ ์ด๋ฌํ ๋ฐฉ์์ ์ดํ ๋ฐ์ดํฐ๊ฐ ๋์ด๋จ์ ๋ฐ๋ผ ์ฑ๋ฅ์ ํ๋ฅผ ์ผ์ผํฌ ์ ์๋ ์์ธ์ด ๋๊ธฐ์ ์์ ํด์ผํ ๋ถ๋ถ์ด๋ค.
ย ์ด๋ ๋ฏ, ์ด๋ป๊ฒ ๋ณด๋ฉด ๊ฐ๋จํ CRUD ์์
์ผ์ง๋ผ๋ ์ฑ๋ฅ์ ๋ํ ๊ณ ๋ ค๋ฅผ ํ๋ค๋ณด๋ ์๊ฐ๋ณด๋ค ๋ง์ ๊ณ ๋ฏผ์ด ํ์ํ๋ค.
ย ์์ผ๋ก ์์์ด ์ง๋ฉดํ ์ด๋ฌํ ๋ฌธ์ ๋ค์ ๋์ฑ ๋ ํจ๊ณผ์ ์ผ๋ก ํด๊ฒฐํ๊ธฐ ์ํด, N+1 ์ ๋ํ ํ์คํ ํ์ต์ด ํ์ํ๋ค.
ํ๋ก์ ํธ๊ฐ ๋๋ ๊ธฐ๋
์ผ๋ก N+1 ๋ฌธ์ ์ ๋ํด์ ์ ๋ฆฌํด๋ณด๊ณ ์ ํ๋ค.
ORM ํ๊ฒฝ์์ ๋ฐ์ํ๋ ์ฑ๋ฅ ๋ฌธ์ ๋ก, 1๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์คํํ ๋ ์ถ๊ฐ์ ์ผ๋ก N๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ ํ์JPA ์ ๊ฐ์ ORM ์์, ์ฐ๊ด๋ ๋ฐ์ดํฐ๋ฅผ Lazy Loading ์ผ๋ก ๊ฐ์ ธ์ฌ ๋ ๋ฐ์@Entity
@Builder
public class Hotel {
@Id
@GeneratedValue(strategy = GenerationType.Identity) // ์ดํ ์๋ต
private Long id;
@Column
private String hotelName;
@OneToMany(mappedBy = "hotel")
@Builder.Default
private List<Room> rooms = new ArrayList<>();
}
@Entity
public class Room {
// ...
private String roomName;
@ManyToOne
private Hotel hotel;
}
@OneToMany ์ ๊ธฐ๋ณธ FetchType ์ LazyhotelRepository.findAll() ๋ก ์ ์ฒด Hotel ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ฌ ์SELECT ์ฟผ๋ฆฌ๊ฐ 1๋ฒ ๋ฐ์Hotel ์๋ ํ์ฌ ์ค์ Room ๋ค์ ๊ฐ์ด ๋ค์ด์๋ ๊ฒ์ด ์๋, ํด๋น Room ๋ค์ ์ฃผ์๊ฐ์ ํฌํจํ๊ณ ์๋ Proxy ๊ฐ์ฒด๋ฅผ ๊ฐ๊ณ ์์Room ์ ์ ๋ณด๋ฅผ ์ป์ผ๋ ค๊ณ ํ ์Room ์ ๋ํ SELECT ์ฟผ๋ฆฌ๋ฅผ ์ฌ๊ธฐ์ ๋ค์ ์คํ๊ฒฐ๊ณผ์ ์ผ๋ก
Hotel์ ๊ฐ์ ธ์ฌ ๋ 1๋ฒ,Room์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ฌ ๋N๋ฒ, ์ดN+1๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ์คํ๋จ
์ฟผ๋ฆฌ์ ์ด ๊ฐ์๊ฐ ์ ๋ค๋ฉด ํฌ๊ฒ ๋ฌธ์ ๊ฐ ๋์ง ์์ ์ ์์ง๋ง, ํฐ ๊ท๋ชจ์์๋ ์ด๋ ๊ฒ ์ถ๊ฐ๋ก ์์ฑ๋ ์ฟผ๋ฆฌ๋ก ์ธํด ๋ฌธ์ ๊ฐ ์๊ธธ ๊ฐ๋ฅ์ฑ์ด ๋์
์ฆ, ์ฑ๋ฅ์ ํฐ ์ํฅ์ ๋ฏธ์น ์ ์๋ ์์ฃผ ์ค์ํ ๋ถ๋ถ
1:N ์ฐ๊ด๊ด๊ณ์์ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ์กฐํํ ๋ ์ฌ์ฉN+1 ์ ํด๊ฒฐํ๊ธฐ ์ํ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒRepsitory ์์ ํ ๋ฒ์ DTO ๋ก ๋ฐํ ์ ์ฌ์ฉํ ์ ์์Fetch Join ๋ฐฉ์์ ์ํฐํฐ ๊ทธ๋ํ๋ฅผ ํ ๋ฒ์ ๋ก๋ฉํ๋ ๋ฐฉ์์ด๊ธฐ ๋๋ฌธFetch Join ์ ์ํฐํฐ ์ ์ณฌ๋ฅผ ์กฐํํด์ผ ๋์ํ๋ฏ๋ก, DTO ์ ํจ๊ป ์ฌ์ฉ ๋ถ๊ฐ๋ฅ@Query("SELECT h FROM Hotel h JOIN FETCH h.rooms")
List<Hotel> findAllHotels();
JOIN Room r ON h.id = r.hotel_id ๊ฐ ๋ฐ์@BatchSize ๋ฅผ ํตํ ๋ฐฐ์น ์ฌ์ด์ฆ ์กฐ์ @OneToMany(mappedBy = "hotel")
@BatchSize(size = 50)
private List<Room> rooms;
Global ์ค์ ์ ํตํ ๋ฐฐ์น ์ฌ์ด์ฆ ์กฐ์ spring:
jpa:
properties:
hibernate.default_batch_fetch_size: 50
SELECT * FROM Room WHERE room_id IN (1, 2, 3.... 49, 50) ๊ฐ์ด ์คํ ( IN ์ด ์ฌ์ฉ๋จ)Hotel ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋ 50๊ฐ์ Room ๋ฐ์ดํฐ๋ฅผ ํจ๊ป ๊ฐ์ ธ์ดN+1 ๋ฌธ์ ๊ฐ ๋ฐ์Fetch Join ์ ์ฌ์ฉํ์ง ์๋ ๋ฐฉ๋ฒ@EntityGraph(attributePaths = {"Room"})
@Query("SELECT h FROM Hotel h JOIN FETCH h.rooms")
List<Hotel> findAllHotels();
Entity ์ ์ฌ๋ฌ๊ฐ์ @OneToMany ๊ฐ ๋ฌ๋ ค์๋ ๊ฒฝ์ฐ๋ ์์@OneToMany(mappedBy = "hotel")
@BatchSize(size = 50)
private List<Room> rooms;
@OneToMany(mappedBy = "hotel")
@BatchSize(size = 50)
private List<Review> reviews;
ํน์
spring:
jpa:
properties:
hibernate.default_batch_fetch_size: 50
@OneToMany ๊ด๊ณ๋ ๋ฐ๋ก ์กฐํํ๊ณ , ์ดํ์ ์ดํ๋ฆฌ์ผ์ด์
๋ ๋ฒจ์์ ์กฐ๋ฆฝํ๋ ๋ฐฉ์@Query("""
SELECT new com.ll.hotel.domain.hotel.hotel.dto.HotelWithImageDto(h, i)
FROM Hotel h
LEFT JOIN Image i
ON i.referenceId = h.id
AND i.imageType = :imageType
WHERE h.hotelStatus <> 'UNAVAILABLE'
AND (i.createdAt = (
SELECT MIN(i2.createdAt)
FROM Image i2
WHERE i2.referenceId = h.id
AND i2.imageType = :imageType
)
OR i is NULL)
AND h.streetAddress LIKE %:streetAddress%
""")
Page<HotelWithImageDto> findAllHotels(@Param("imageType") ImageType imageType,
@Param("streetAddress") String streetAddress, PageRequest pageRequest);
Image ํ
์ด๋ธ์ ์ด๋ฏธ์ง ํ์
์ด ํธํ
์ธ ์ด๋ฏธ์ง๋ค ์ค ํด๋นํ๋ ์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง(์ธ๋ค์ผ)๋ง ๊ฐ์ ธ์ค๊ธฐ ์ํ ์๋ธ์ฟผ๋ฆฌ@Query("""
SELECT h
FROM Hotel h
WHERE h.hotelStatus <> 'UNAVAILABLE'
AND h.streetAddress LIKE %:streetAddress%
""")
Page<Hotel> findHotels(@Param("streetAddress") String streetAddress, Pageable pageable);
@Query("""
SELECT i
FROM Image i
WHERE i.imageType = :imageType
AND i.createdAt IN (
SELECT MIN(i2.createdAt)
FROM Image i2
WHERE i2.referenceId IN :hotelIds
AND i2.imageType = :imageType
GROUP BY i2.referenceId
)
""")
List<Image> findFirstImages(@Param("imageType") ImageType imageType, @Param("hotelIds") List<Long> hotelIds);
ID ๋ฅผ ์ฌ์ฉํ์ฌ, ํด๋น ํธํ
๋ค์ ์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง๋ค๋ง ๋ฐฐ์น๋ก ์กฐํHotelWithImageDto ๋ก ๋ณํ
- ํ์ฌ๋ ๋ฐ์ดํฐ์ ์ด ๋ง์ง ์๊ณ ์ค์ ์๋น์ค๊ฐ ์๋ ํ๋ก์ ํธ ๋ ๋ฒจ์ด๊ธฐ ๋๋ฌธ์, ์ฟผ๋ฆฌ๋ฅผ ๋ฐ๋ก ๋ถ๋ฆฌํ์ง ์์
- ๋ฐ์ดํฐ์ ์ด ๊ทธ๋ ๊ฒ ํฌ์ง ์์ผ๋ฏ๋ก ๋ฉ๋ชจ๋ฆฌ์์ ์ฒ๋ฆฌํ๋ ๊ฒ ๋ ์ ๋ฆฌํ๋ค๊ณ ํ๋จ
- 3์ฐจ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๋ ์๊ฐํด๋ด์ผ ํ ๊ฒ ๊ฐ์
- ํ๋ ๊ณผํ์ ๋ฐ์ ์ผ๋ก ์ธํด ํธ๋ ์ด๋ ์คํ๋ก ์ ์ง๋ณด์์ฑ์ ์ฑ๊ธฐ๋ ๊ฒ ์ฌ๋งํด์ ์ข์ ๊ฒ์ผ๋ก ํ๋จ ์ค
์ฐธ๊ณ ) OpenAI. (2024).ChatGPT(4o)[Large language model].https://chatgpt.com/