๋ชฉ์ฐจ
๋ฌธ์ ์ํฉ
ํ๋ก์ ํธ ์ ๋ฐ์์ JPA์ ์ง์ฐ ๋ก๋ฉ(Lazy Loading) ์ด ๊ธฐ๋ณธ์ผ๋ก ์ค์ ๋์ด ์์๊ธฐ ๋๋ฌธ์, ์ํ, ์ฃผ๋ฌธ, ๊ฒฐ์ , ํ๋ถ ๋ฑ ์ฃผ์ ๋๋ฉ์ธ ์กฐํ ์ N+1 ์ฟผ๋ฆฌ ๋ฌธ์ ๊ฐ ๋น๋ฒํ๊ฒ ๋ฐ์ํ์ต๋๋ค.
์๋ฅผ ๋ค์ด, ์ฃผ๋ฌธ(Order)์ ์กฐํํ ๋ ์ฐ๊ด๋ ์ฃผ๋ฌธ ์์ธ(OrderDetail), ๋ฐฐ์ก์ง(DeliveryAddress), ๊ฒฐ์ (Payment), ํ๋ถ(Refund) ์ ๋ณด๋ฅผ ํจ๊ป ๋ถ๋ฌ์ค๋ ๊ณผ์ ์์ ๊ฐ ์ฐ๊ด ๊ด๊ณ๋ง๋ค Lazy ๋ก๋ฉ์ด ๋ฐ์ํ๋ฉฐ, ๊ฒฐ๊ณผ์ ์ผ๋ก ํ API ํธ์ถ์ ์์ญ~์๋ฐฑ ๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ์คํ๋์์ต๋๋ค.
-- ์ฃผ๋ฌธ ๋ชฉ๋ก ์กฐํ (1ํ)
SELECT * FROM p_order;
-- ์ฃผ๋ฌธ๋ณ ์์ธ ์กฐํ (Nํ)
SELECT * FROM p_order_detail WHERE order_id = ?;
-- ์ฃผ๋ฌธ๋ณ ํ๋ถ ์กฐํ (Nํ)
SELECT * FROM p_refund WHERE order_id = ?;
-- ์ฃผ๋ฌธ๋ณ ๋ฐฐ์ก์ง ์กฐํ (Nํ)
SELECT * FROM p_delivery_address WHERE delivery_address_id = ?;
SQL
๋ณต์ฌ
์ด๋ก ์ธํด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ ํ์๊ฐ ๊ธ๊ฒฉํ ๋์ด๋๋ฉฐ ์๋ต ์ง์ฐ๊ณผ ํธ๋์ญ์
์ฒ๋ฆฌ ์๊ฐ์ด ์ฆ๊ฐํ์ต๋๋ค.
์์ธ ๋ถ์
1. Lazy Loading์ ๊ธฐ๋ณธ ๋์ ๋ฐฉ์
โข
JPA์ @ManyToOne, @OneToMany ๊ด๊ณ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ฐ ๋ก๋ฉ(LAZY) ์ ์ฌ์ฉ
โข
์ฆ, ์กฐํ ์์ ์๋ ์ฐ๊ด ๊ฐ์ฒด๊ฐ ํ๋ก์๋ก ๋จ์ ์๋ค๊ฐ ์ค์ ์ ๊ทผ ์์ (.getXXX())์ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ์คํ๋จ
โ ์ํฐํฐ ๊ทธ๋ํ๋ฅผ ๋จ์ํ ์ํํ๋ ๊ฒ๋ง์ผ๋ก๋ ์ฌ๋ฌ ๊ฐ์ SELECT๊ฐ ์คํ๋๋ ๊ตฌ์กฐ
2. DTO ์ค๊ณ ๋ฌธ์
โข
๊ธฐ์กด DTO ๊ตฌ์กฐ๋ ์ํฐํฐ ์์ฒด๋ฅผ ํ๋๋ก ๋ณด์ ํ๊ณ ์์ด,
DTO ์์ฑ ์ ์ฐ๊ด ๊ฐ์ฒด ์ ๊ทผ โ Lazy ์ฟผ๋ฆฌ ์ฐ์ ์คํ
โข
๊ฒฐ๊ณผ์ ์ผ๋ก DTO ๋ณํ ๊ณผ์ ์์๋ N+1 ์ฟผ๋ฆฌ๊ฐ ์ถ๊ฐ๋ก ๋ฐ์
public record RefundDetailResponse(
UUID orderId,
String orderNum,
List<ProductInfoDto> productList,
RecipientAddressDto recipientAddress,
UUID refundId,
LocalDateTime cancelDate,
RefundStatus refundStatus,
int refundPrice,
RefundReason refundReason
) {
public static RefundDetailResponse of(Order order, List<OrderDetail> orderDetails, Refund refund) {
List<ProductInfoDto> productList =
orderDetails.stream().map(ProductInfoDto::fromEntity).toList();
RecipientAddressDto recipientAddress =
RecipientAddressDto.fromEntity(order.getDeliveryAddress()); // Lazy ์ ๊ทผ
return new RefundDetailResponse(
order.getOrderId(),
order.getOrderNum(),
productList,
recipientAddress,
refund.getRefundId(),
refund.getCreatedAt(),
refund.getRefundStatus(),
refund.getPrice(),
refund.getReason());
}
}
Java
๋ณต์ฌ
ํด๊ฒฐ ๊ณผ์
1. Fetch Join ์ ์ฉ
โข
JPQL + Fetch Join์ ํตํด ํ์ํ ์ฐ๊ด ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ํจ๊ป ์กฐํ
@Query("""
SELECT o FROM Order o
JOIN FETCH o.deliveryAddress
JOIN FETCH o.orderDetails
LEFT JOIN FETCH o.refund
WHERE o.orderId = :orderId
""")
Optional<Order> findByIdWithRelations(@Param("orderId") UUID orderId);
Java
๋ณต์ฌ
๊ฒฐ๊ณผ:
Order, OrderDetail, DeliveryAddress, Refund๋ฅผ ๋จ์ผ SQL๋ก ์กฐํ
โ Lazy ๋ก๋ฉ ์ ๊ฑฐ, DB ์๋ณต ํ์ ๋ํญ ๊ฐ์
2. Batch Fetch Size ์ค์
โข
์ผ๊ด ๋ก๋ฉ(Batch Fetch)์ ํตํด N+1 ๋ฌธ์ ๋ฅผ ์ํ
โข
Hibernate ์ค์ ์ผ๋ก ํ ๋ฒ์ SELECT์ ์ฌ๋ฌ ์ํฐํฐ๋ฅผ ๋ฌถ์ด์ ๊ฐ์ ธ์ค๋๋ก ์ง์
spring:
jpa:
properties:
hibernate.default_batch_fetch_size: 100
YAML
๋ณต์ฌ
ํจ๊ณผ: ์ฌ๋ฌ ์ฐ๊ด ์ํฐํฐ๋ฅผ ๋ฌถ์ด์ ํ ๋ฒ์ SELECT
โ ๋๋ ๋ชฉ๋ก ์กฐํ์์๋ 100ํ ์ดํ์ ์ต์ ์ฟผ๋ฆฌ๋ก ๋ฐ์ดํฐ ๋ก๋ฉ ๊ฐ๋ฅ
3. DTO ๊ตฌ์กฐ ๊ฐ์
โข
DTO์์ ์ํฐํฐ๋ฅผ ์ง์ ์ฐธ์กฐํ์ง ์๊ณ , ๋ด๋ถ static record ๊ตฌ์กฐ๋ฅผ ํ์ฉํด ํ์ํ ๋ฐ์ดํฐ๋ง ๋
ธ์ถํ๋๋ก ๊ฐ์
public record RefundDetailResponse(
UUID orderId,
String orderNum,
List<ProductInfoDto> productList,
RecipientAddressDto recipientAddress,
UUID refundId,
LocalDateTime cancelDate,
RefundStatus refundStatus,
int refundPrice,
RefundReason refundReason) {
public record ProductInfoDto(
UUID orderDetailId, int productPrice, String productName, String optionTitle, int quantity) {
public static ProductInfoDto fromEntity(OrderDetail orderDetail) {
return new ProductInfoDto(
orderDetail.getOrderDetailId(),
orderDetail.getPrice(),
orderDetail.getProductName(),
orderDetail.getOptionName(),
orderDetail.getQuantity());
}
}
public record RecipientAddressDto(
String postalCode, String city, String sigungu, String roadName, String addressDetail,
String recipientName, String recipientContact) {
public static RecipientAddressDto fromEntity(DeliveryAddress deliveryAddress) {
return new RecipientAddressDto(
deliveryAddress.getAddress().getPostalCode(),
deliveryAddress.getAddress().getCity(),
deliveryAddress.getAddress().getSigungu(),
deliveryAddress.getAddress().getRoadName(),
deliveryAddress.getAddress().getAddressDetail(),
deliveryAddress.getRecipientName(),
deliveryAddress.getRecipientContact());
}
}
}
Java
๋ณต์ฌ
๋ชฉ์ :
โข
Lazy ๊ฐ์ฒด ์ ๊ทผ ๋ฐฉ์ง
โข
DTO๋ โ์ฝ๊ธฐ ์ ์ฉ ๋ทฐ ๋ชจ๋ธโ๋ก ์ฌ์ฉ โ ๋ถ๋ณ์ฑ ๋ณด์ฅ
โข
์กฐํ ์์ ์ ํ์ํ ๋ฐ์ดํฐ๋ง Selectํ์ฌ ์ ์ก๋ ๋ฐ ์ฒ๋ฆฌ ์๋ ๊ฐ์
๊ฐ์ ํจ๊ณผ
๊ตฌ๋ถ | ๊ฐ์ ์ | ๊ฐ์ ํ |
์ฟผ๋ฆฌ ์ | ์ต๋ 3N+1 | 1 ~ 2ํ |
DB ์๋ณต ํ์ | ์ํฐํฐ ์๋งํผ ๋ฐ๋ณต | ์ผ๊ด ์กฐํ๋ก ์ต์ํ |
์กฐํ ์ฑ๋ฅ | ๋๋ฆผ (Lazy ๋ก๋ฉ ์ค์ฒฉ) | ๋น ๋ฆ (Fetch Join + Batch Fetch) |
DTO ๊ตฌ์กฐ | ์ํฐํฐ ์ง์ ํฌํจ | ํ์ํ ํ๋๋ง ๋ด๋ถ DTO๋ก ๋งคํ |
ย ์ธ์ฌ์ดํธ
1.
Lazy Loading์ ํ์ฐ์ ์ผ๋ก N+1 ๋ฌธ์ ๋ฅผ ๋๋ฐํ๋ฏ๋ก, ์กฐํ ์ ์ฉ API์๋ ๋ฐ๋์ ์ ์ด๊ฐ ํ์ํ๋ค.
2.
์ ์ญ BatchSize ์ค์ ์ ๊ฐํธํ์ง๋ง, ๋๋ฉ์ธ๋ณ ์ต์ ํฌ๊ธฐ๋ฅผ ์กฐ์ ํด์ผ ํ๋ค.
3.
DTO๋ ์ํฐํฐ๋ฅผ ์ง์ ๋ด์ง ๋ง๊ณ , ๋ฐ์ดํฐ ์ ์ก ๋ชฉ์ ์ ๋ง๋ ํํ๋ก ๋ณํํด์ผ ์กฐํ ์ฑ๋ฅ๊ณผ ๋ถ๋ณ์ฑ์ ํ๋ณดํ ์ ์๋ค.
4.
์ต์ ์ ์ค๊ณ๋ โ์กฐํ์ฉ ์ฟผ๋ฆฌ(์ฝ๊ธฐ)โ์ โ๋ช
๋ น์ฉ ๋ก์ง(์ฐ๊ธฐ)โ์ ๋ถ๋ฆฌํ๋ ๊ฒ์ด๋ค. (CQRS ์ ๊ทผ)
