Skip to content

Commit

Permalink
[47] 쿼리 조회 성능 향상
Browse files Browse the repository at this point in the history
- product name 을 FullText 인덱스로 지정
- queryDSL 에서 fulltext MATCH... AGAINST 지원이 안되므로 sql native query 를 사용하도록 수정
  • Loading branch information
ohsuha committed Oct 28, 2024
1 parent 700d0d8 commit d62f2a3
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ public static class Get {
private Long quantity;
private Long orderId;
private BigDecimal unitPrice;
private String productName;
private ShipmentStatus shipmentStatus;
private LocalDateTime shipmentCreatedAt;
private LocalDateTime shipmentUpdatedAt;

public static Get toDto(OrderDetail orderDetail) {
return Get.builder()
Expand Down Expand Up @@ -54,4 +50,19 @@ public static List<OrderDetailResponseDto.Get> toDtoList(List<OrderDetail> order
return orderDetails.stream().map(Get::toDto).toList();
}
}

@Getter
@AllArgsConstructor
public static class GetList {
private Long id;
private LocalDateTime createdAt;
private Long productId;
private Long quantity;
private Long orderId;
private BigDecimal unitPrice;
private String productName;
private ShipmentStatus shipmentStatus;
private LocalDateTime shipmentCreatedAt;
private LocalDateTime shipmentUpdatedAt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,15 @@
import org.example.commerce_site.attribute.OrderStatus;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

public class OrderResponseDto {
@Builder
@Getter
@AllArgsConstructor
public static class Get {
private Long id;
private BigDecimal totalAmount;
private OrderStatus status;
private List<OrderDetailResponseDto.Get> orderDetails;

public Get(Long id, BigDecimal totalAmount, OrderStatus status) {
this.id = id;
this.totalAmount = totalAmount;
this.status = status;
}

public void setOrderDetails(List<OrderDetailResponseDto.Get> orderDetails) {
this.orderDetails = orderDetails;
}
private List<OrderDetailResponseDto.GetList> orderDetails;
}
}
Original file line number Diff line number Diff line change
@@ -1,79 +1,100 @@
package org.example.commerce_site.infrastructure.order;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Map;

import org.example.commerce_site.application.order.dto.OrderDetailResponseDto;
import org.example.commerce_site.application.order.dto.OrderResponseDto;
import org.example.commerce_site.attribute.OrderStatus;
import org.example.commerce_site.attribute.ShipmentStatus;
import org.example.commerce_site.common.util.PageConverter;
import org.example.commerce_site.domain.QOrder;
import org.example.commerce_site.domain.QOrderDetail;
import org.example.commerce_site.domain.QProduct;
import org.example.commerce_site.domain.QShipment;
import org.flywaydb.core.internal.util.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Repository
@RequiredArgsConstructor
public class CustomOrderRepositoryImpl implements CustomOrderRepository {
private final JPAQueryFactory queryFactory;
@PersistenceContext
private final EntityManager entityManager;

@Override
public Page<OrderResponseDto.Get> getOrders(Pageable pageable, String keyword, Long userId) {
BooleanBuilder builder = new BooleanBuilder();
StringBuilder sql = new StringBuilder("SELECT o.id, o.total_amount, o.status, " +
"od.id AS order_detail_id, od.created_at, od.product_id, od.quantity, " +
"od.order_id, od.unit_price, p.name AS product_name, s.status AS shipment_status, " +
"s.created_at AS shipment_created_at, s.updated_at AS shipment_updated_at " +
"FROM orders o " +
"INNER JOIN order_details od ON o.id = od.order_id " +
"LEFT JOIN products p ON od.product_id = p.id " +
"LEFT JOIN shipments s ON od.id = s.order_detail_id " +
"WHERE o.user_id = :userId ");

if (StringUtils.hasText(keyword)) {
sql.append("AND p.name IS NOT NULL AND (MATCH(p.name) AGAINST (:keyword IN BOOLEAN MODE) " +
"OR p.name LIKE CONCAT(:keyword, '%')) ");
}

sql.append("ORDER BY o.created_at LIMIT :pageSize OFFSET :offset");

Query query = entityManager.createNativeQuery(sql.toString());
query.setParameter("userId", userId);
query.setParameter("pageSize", pageable.getPageSize());
query.setParameter("offset", pageable.getOffset());

if (StringUtils.hasText(keyword)) {
builder.and(QProduct.product.name.contains(keyword));
query.setParameter("keyword", keyword);
}

List<OrderResponseDto.Get> orderList = queryFactory.select(Projections.constructor(OrderResponseDto.Get.class,
QOrder.order.id,
QOrder.order.totalAmount,
QOrder.order.status,
Projections.list(Projections.constructor(OrderDetailResponseDto.Get.class,
QOrderDetail.orderDetail.createdAt,
QOrderDetail.orderDetail.id,
QOrderDetail.orderDetail.productId,
QOrderDetail.orderDetail.quantity,
QOrderDetail.orderDetail.order.id,
QOrderDetail.orderDetail.unitPrice,
QProduct.product.name,
QShipment.shipment.status,
QShipment.shipment.createdAt,
QShipment.shipment.updatedAt
))
))
.from(QOrder.order)
.leftJoin(QOrderDetail.orderDetail).on(QOrder.order.id.eq(QOrderDetail.orderDetail.order.id))
.leftJoin(QProduct.product).on(QOrderDetail.orderDetail.productId.eq(QProduct.product.id))
.leftJoin(QShipment.shipment).on(QOrderDetail.orderDetail.id.eq(QShipment.shipment.orderDetail.id))
.where(QOrder.order.userId.eq(userId).and(builder))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

List<OrderResponseDto.Get> groupedOrderList = orderList.stream()
.collect(Collectors.groupingBy(OrderResponseDto.Get::getId))
.values()
.stream()
.map(orderGroup -> {
OrderResponseDto.Get firstOrder = orderGroup.get(0);
List<OrderDetailResponseDto.Get> allDetails = orderGroup.stream()
.flatMap(order -> order.getOrderDetails().stream())
.collect(Collectors.toList());
firstOrder.setOrderDetails(allDetails);
return firstOrder;
})
.collect(Collectors.toList());

return PageConverter.getPage(groupedOrderList, pageable);
List<Object[]> resultList = query.getResultList();

Map<Long, OrderResponseDto.Get> orderMap = new HashMap<>();
for (Object[] row : resultList) {
Long orderId = (Long) row[0]; // 주문 ID
BigDecimal totalAmount = BigDecimal.valueOf((Long) row[1]); // 총 금액
OrderStatus orderStatus = OrderStatus.valueOf((String) row[2]); // 주문 상태

OrderDetailResponseDto.GetList orderDetail = new OrderDetailResponseDto.GetList(
(Long) row[3], // orderDetailId
convertTimestampToLocalDateTime((Timestamp) row[4]), // createdAt
(Long) row[5], // productId
(Long) row[6], // quantity
(Long) row[7], // orderId
BigDecimal.valueOf((Long) row[8]), // unitPrice
(String) row[9], // productName
ShipmentStatus.valueOf((String) row[10]), // shipmentStatus
convertTimestampToLocalDateTime((Timestamp) row[11]), // shipmentCreatedAt
convertTimestampToLocalDateTime((Timestamp) row[12]) // shipmentUpdatedAt
);

OrderResponseDto.Get orderResponse = orderMap.get(orderId);
if (orderResponse == null) {
orderResponse = new OrderResponseDto.Get(orderId, totalAmount, orderStatus, new ArrayList<>());
orderMap.put(orderId, orderResponse);
}

orderResponse.getOrderDetails().add(orderDetail);
}

List<OrderResponseDto.Get> orderList = new ArrayList<>(orderMap.values());

return PageConverter.getPage(orderList, pageable);
}

private LocalDateTime convertTimestampToLocalDateTime(Timestamp timestamp) {
return timestamp != null ? timestamp.toLocalDateTime() : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static class DetailGet {
private LocalDateTime shipmentCreatedAt;
private LocalDateTime shipmentUpdatedAt;

public static DetailGet of(OrderDetailResponseDto.Get dto) {
public static DetailGet of(OrderDetailResponseDto.GetList dto) {
return DetailGet.builder()
.createdAt(dto.getCreatedAt())
.id(dto.getId())
Expand All @@ -65,7 +65,7 @@ public static DetailGet of(OrderDetailResponseDto.Get dto) {
.build();
}

public static List<DetailGet> of(List<OrderDetailResponseDto.Get> dtos) {
public static List<DetailGet> of(List<OrderDetailResponseDto.GetList> dtos) {
return dtos.stream().map(DetailGet::of).toList();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE products ADD FULLTEXT(name);

0 comments on commit d62f2a3

Please sign in to comment.