From 700d0d8a123c52e730333846b10288eb4a23af77 Mon Sep 17 00:00:00 2001 From: suhaoh Date: Sun, 27 Oct 2024 01:29:03 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[47]=20=EC=9D=BC=EB=B0=98=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=A3=BC=EB=AC=B8=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상품명으로 주문 내역 검색 --- .../application/order/OrderFacade.java | 10 +++ .../application/order/OrderService.java | 10 +++ .../order/dto/OrderDetailResponseDto.java | 7 ++ .../order/dto/OrderResponseDto.java | 32 ++++++++ .../common/util/PageConverter.java | 10 +++ .../order/CustomOrderRepository.java | 9 +++ .../order/CustomOrderRepositoryImpl.java | 79 +++++++++++++++++++ .../product/CustomProductRepositoryImpl.java | 2 - .../representation/order/OrderController.java | 16 +++- .../order/{request => dto}/OrderRequest.java | 2 +- .../order/dto/OrderResponse.java | 72 +++++++++++++++++ .../product/ProductController.java | 4 +- 12 files changed, 247 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/example/commerce_site/application/order/dto/OrderResponseDto.java create mode 100644 src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepository.java create mode 100644 src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepositoryImpl.java rename src/main/java/org/example/commerce_site/representation/order/{request => dto}/OrderRequest.java (95%) create mode 100644 src/main/java/org/example/commerce_site/representation/order/dto/OrderResponse.java diff --git a/src/main/java/org/example/commerce_site/application/order/OrderFacade.java b/src/main/java/org/example/commerce_site/application/order/OrderFacade.java index 6eb9a14..eb69fcb 100644 --- a/src/main/java/org/example/commerce_site/application/order/OrderFacade.java +++ b/src/main/java/org/example/commerce_site/application/order/OrderFacade.java @@ -5,6 +5,7 @@ import org.example.commerce_site.application.address.AddressService; import org.example.commerce_site.application.order.dto.OrderDetailResponseDto; import org.example.commerce_site.application.order.dto.OrderRequestDto; +import org.example.commerce_site.application.order.dto.OrderResponseDto; import org.example.commerce_site.application.product.ProductService; import org.example.commerce_site.application.shipment.ShipmentService; import org.example.commerce_site.application.user.UserService; @@ -14,11 +15,15 @@ import org.example.commerce_site.domain.Address; import org.example.commerce_site.domain.Order; import org.example.commerce_site.domain.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor public class OrderFacade { @@ -51,4 +56,9 @@ public void cancel(String userAuthId, Long orderId) { List orderDetails = orderDetailService.getOrderDetails(order.getId()); productService.restoreStockOnCancel(orderDetails); } + + public Page getOrderList(int page, int size, String keyword, String userAuthId) { + User user = userService.getUser(userAuthId); + return orderService.getOrderList(PageRequest.of(page - 1, size), keyword, user.getId()); + } } diff --git a/src/main/java/org/example/commerce_site/application/order/OrderService.java b/src/main/java/org/example/commerce_site/application/order/OrderService.java index bc8d721..b86c263 100644 --- a/src/main/java/org/example/commerce_site/application/order/OrderService.java +++ b/src/main/java/org/example/commerce_site/application/order/OrderService.java @@ -1,11 +1,15 @@ package org.example.commerce_site.application.order; import org.example.commerce_site.application.order.dto.OrderRequestDto; +import org.example.commerce_site.application.order.dto.OrderResponseDto; import org.example.commerce_site.attribute.OrderStatus; import org.example.commerce_site.common.exception.CustomException; import org.example.commerce_site.common.exception.ErrorCode; import org.example.commerce_site.domain.Order; +import org.example.commerce_site.infrastructure.order.CustomOrderRepository; import org.example.commerce_site.infrastructure.order.OrderRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,6 +19,7 @@ @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; + private final CustomOrderRepository customOrderRepository; @Transactional public Order createOrder(OrderRequestDto.Create dto, Long userId) { @@ -33,4 +38,9 @@ public void updateStatus(Order order, OrderStatus orderStatus) { order.updateOrderStatus(orderStatus); orderRepository.save(order); } + + @Transactional(readOnly = true) + public Page getOrderList(PageRequest pageRequest, String keyword, Long userId) { + return customOrderRepository.getOrders(pageRequest, keyword, userId); + } } diff --git a/src/main/java/org/example/commerce_site/application/order/dto/OrderDetailResponseDto.java b/src/main/java/org/example/commerce_site/application/order/dto/OrderDetailResponseDto.java index ea0125b..722b9e2 100644 --- a/src/main/java/org/example/commerce_site/application/order/dto/OrderDetailResponseDto.java +++ b/src/main/java/org/example/commerce_site/application/order/dto/OrderDetailResponseDto.java @@ -4,15 +4,18 @@ import java.time.LocalDateTime; import java.util.List; +import org.example.commerce_site.attribute.ShipmentStatus; import org.example.commerce_site.domain.Order; import org.example.commerce_site.domain.OrderDetail; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; public class OrderDetailResponseDto { @Getter @Builder + @AllArgsConstructor public static class Get { private LocalDateTime createdAt; private Long id; @@ -20,6 +23,10 @@ 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() diff --git a/src/main/java/org/example/commerce_site/application/order/dto/OrderResponseDto.java b/src/main/java/org/example/commerce_site/application/order/dto/OrderResponseDto.java new file mode 100644 index 0000000..bbe036f --- /dev/null +++ b/src/main/java/org/example/commerce_site/application/order/dto/OrderResponseDto.java @@ -0,0 +1,32 @@ +package org.example.commerce_site.application.order.dto; + +import java.math.BigDecimal; +import java.util.List; + +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 orderDetails; + + public Get(Long id, BigDecimal totalAmount, OrderStatus status) { + this.id = id; + this.totalAmount = totalAmount; + this.status = status; + } + + public void setOrderDetails(List orderDetails) { + this.orderDetails = orderDetails; + } + } +} diff --git a/src/main/java/org/example/commerce_site/common/util/PageConverter.java b/src/main/java/org/example/commerce_site/common/util/PageConverter.java index 43ef1b3..8a8de33 100644 --- a/src/main/java/org/example/commerce_site/common/util/PageConverter.java +++ b/src/main/java/org/example/commerce_site/common/util/PageConverter.java @@ -1,5 +1,6 @@ package org.example.commerce_site.common.util; +import java.util.Collections; import java.util.List; import org.springframework.data.domain.Page; @@ -8,7 +9,16 @@ public class PageConverter { public static Page getPage(List content, Pageable pageable) { + if (content == null || pageable == null) { + return new PageImpl<>(Collections.emptyList(), pageable != null ? pageable : Pageable.unpaged(), 0); + } + int start = (int)pageable.getOffset(); + + if (start >= content.size()) { + return new PageImpl<>(Collections.emptyList(), pageable, content.size()); + } + int end = Math.min((start + pageable.getPageSize()), content.size()); return new PageImpl<>(content.subList(start, end), pageable, content.size()); diff --git a/src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepository.java b/src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepository.java new file mode 100644 index 0000000..b5d4bb7 --- /dev/null +++ b/src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepository.java @@ -0,0 +1,9 @@ +package org.example.commerce_site.infrastructure.order; + +import org.example.commerce_site.application.order.dto.OrderResponseDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface CustomOrderRepository { + Page getOrders(Pageable pageable, String keyword, Long userId); +} diff --git a/src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepositoryImpl.java b/src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepositoryImpl.java new file mode 100644 index 0000000..860f989 --- /dev/null +++ b/src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepositoryImpl.java @@ -0,0 +1,79 @@ +package org.example.commerce_site.infrastructure.order; + +import java.util.List; +import java.util.stream.Collectors; + +import org.example.commerce_site.application.order.dto.OrderDetailResponseDto; +import org.example.commerce_site.application.order.dto.OrderResponseDto; +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.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 lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class CustomOrderRepositoryImpl implements CustomOrderRepository { + private final JPAQueryFactory queryFactory; + + @Override + public Page getOrders(Pageable pageable, String keyword, Long userId) { + BooleanBuilder builder = new BooleanBuilder(); + + if (StringUtils.hasText(keyword)) { + builder.and(QProduct.product.name.contains(keyword)); + } + + List 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 groupedOrderList = orderList.stream() + .collect(Collectors.groupingBy(OrderResponseDto.Get::getId)) + .values() + .stream() + .map(orderGroup -> { + OrderResponseDto.Get firstOrder = orderGroup.get(0); + List allDetails = orderGroup.stream() + .flatMap(order -> order.getOrderDetails().stream()) + .collect(Collectors.toList()); + firstOrder.setOrderDetails(allDetails); + return firstOrder; + }) + .collect(Collectors.toList()); + + return PageConverter.getPage(groupedOrderList, pageable); + } +} diff --git a/src/main/java/org/example/commerce_site/infrastructure/product/CustomProductRepositoryImpl.java b/src/main/java/org/example/commerce_site/infrastructure/product/CustomProductRepositoryImpl.java index 0b5655b..4d8585b 100644 --- a/src/main/java/org/example/commerce_site/infrastructure/product/CustomProductRepositoryImpl.java +++ b/src/main/java/org/example/commerce_site/infrastructure/product/CustomProductRepositoryImpl.java @@ -60,8 +60,6 @@ public Page getProducts(Pageable pageable, String keywor .leftJoin(QPartner.partner) .on(QProduct.product.partnerId.eq(QPartner.partner.id)) .where(builder) - - //sort, order by .fetch(); return PageConverter.getPage(products, pageable); diff --git a/src/main/java/org/example/commerce_site/representation/order/OrderController.java b/src/main/java/org/example/commerce_site/representation/order/OrderController.java index c739659..8949c13 100644 --- a/src/main/java/org/example/commerce_site/representation/order/OrderController.java +++ b/src/main/java/org/example/commerce_site/representation/order/OrderController.java @@ -2,14 +2,17 @@ import org.example.commerce_site.application.order.OrderFacade; import org.example.commerce_site.common.response.ApiSuccessResponse; -import org.example.commerce_site.representation.order.request.OrderRequest; +import org.example.commerce_site.representation.order.dto.OrderRequest; +import org.example.commerce_site.representation.order.dto.OrderResponse; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; @@ -38,4 +41,15 @@ public ApiSuccessResponse cancelOrder( orderFacade.cancel(userAuthId, orderId); return ApiSuccessResponse.success(); } + + @GetMapping + public ApiSuccessResponse.PageList getOrders( + @RequestParam(value = "page", defaultValue = "1") int page, + @RequestParam(value = "size", defaultValue = "10") int size, + @RequestParam(value = "keyword", required = false) String keyword, + @RequestAttribute("user_id") String userAuthId + ) { + return ApiSuccessResponse.success( + OrderResponse.Get.of(orderFacade.getOrderList(page, size, keyword, userAuthId))); + } } diff --git a/src/main/java/org/example/commerce_site/representation/order/request/OrderRequest.java b/src/main/java/org/example/commerce_site/representation/order/dto/OrderRequest.java similarity index 95% rename from src/main/java/org/example/commerce_site/representation/order/request/OrderRequest.java rename to src/main/java/org/example/commerce_site/representation/order/dto/OrderRequest.java index 6188003..1e3390c 100644 --- a/src/main/java/org/example/commerce_site/representation/order/request/OrderRequest.java +++ b/src/main/java/org/example/commerce_site/representation/order/dto/OrderRequest.java @@ -1,4 +1,4 @@ -package org.example.commerce_site.representation.order.request; +package org.example.commerce_site.representation.order.dto; import java.math.BigDecimal; import java.util.List; diff --git a/src/main/java/org/example/commerce_site/representation/order/dto/OrderResponse.java b/src/main/java/org/example/commerce_site/representation/order/dto/OrderResponse.java new file mode 100644 index 0000000..40f918f --- /dev/null +++ b/src/main/java/org/example/commerce_site/representation/order/dto/OrderResponse.java @@ -0,0 +1,72 @@ +package org.example.commerce_site.representation.order.dto; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +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.springframework.data.domain.Page; + +import lombok.Builder; +import lombok.Getter; + +public class OrderResponse { + @Getter + @Builder + public static class Get { + private Long id; + private BigDecimal totalAmount; + private OrderStatus status; + private List orderDetails; + + public static Get of(OrderResponseDto.Get dto) { + return Get.builder() + .id(dto.getId()) + .totalAmount(dto.getTotalAmount()) + .status(dto.getStatus()) + .orderDetails(DetailGet.of(dto.getOrderDetails())) + .build(); + } + + public static Page of(Page dtos) { + return dtos.map(Get::of); + } + } + + @Getter + @Builder + public static class DetailGet { + private LocalDateTime createdAt; + private Long id; + private Long productId; + private Long quantity; + private Long orderId; + private BigDecimal unitPrice; + private String productName; + private ShipmentStatus shipmentStatus; + private LocalDateTime shipmentCreatedAt; + private LocalDateTime shipmentUpdatedAt; + + public static DetailGet of(OrderDetailResponseDto.Get dto) { + return DetailGet.builder() + .createdAt(dto.getCreatedAt()) + .id(dto.getId()) + .productId(dto.getProductId()) + .quantity(dto.getQuantity()) + .orderId(dto.getOrderId()) + .unitPrice(dto.getUnitPrice()) + .productName(dto.getProductName()) + .shipmentStatus(dto.getShipmentStatus()) + .shipmentCreatedAt(dto.getShipmentCreatedAt()) + .shipmentUpdatedAt(dto.getShipmentUpdatedAt()) + .build(); + } + + public static List of(List dtos) { + return dtos.stream().map(DetailGet::of).toList(); + } + } +} diff --git a/src/main/java/org/example/commerce_site/representation/product/ProductController.java b/src/main/java/org/example/commerce_site/representation/product/ProductController.java index b3a0487..bbcf9e9 100644 --- a/src/main/java/org/example/commerce_site/representation/product/ProductController.java +++ b/src/main/java/org/example/commerce_site/representation/product/ProductController.java @@ -29,7 +29,7 @@ public class ProductController { @PostMapping() public ApiSuccessResponse createProduct( @Valid @RequestBody ProductRequest.Create request, - @RequestAttribute("userId") String userAuthId) { + @RequestAttribute("user_id") String userAuthId) { productFacade.createProduct(ProductRequest.Create.toDTO(request, userAuthId)); return ApiSuccessResponse.success(); } @@ -38,7 +38,7 @@ public ApiSuccessResponse createProduct( @PatchMapping("/{product_id}") public ApiSuccessResponse updateProduct( @PathVariable(name = "product_id") Long productId, - @RequestAttribute("userId") String userAuthId, + @RequestAttribute("user_id") String userAuthId, @Valid @RequestBody ProductRequest.Update request ) { productFacade.updateProduct(productId, ProductRequest.Update.toDTO(request, userAuthId)); From d62f2a31873b060d07cc866e677105d9da51dc1c Mon Sep 17 00:00:00 2001 From: suhaoh Date: Mon, 28 Oct 2024 17:47:16 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[47]=20=EC=BF=BC=EB=A6=AC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=84=B1=EB=8A=A5=20=ED=96=A5=EC=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - product name 을 FullText 인덱스로 지정 - queryDSL 에서 fulltext MATCH... AGAINST 지원이 안되므로 sql native query 를 사용하도록 수정 --- .../order/dto/OrderDetailResponseDto.java | 19 ++- .../order/dto/OrderResponseDto.java | 14 +- .../order/CustomOrderRepositoryImpl.java | 129 ++++++++++-------- .../order/dto/OrderResponse.java | 4 +- .../mysql/V1.7__add_product_name_index.sql | 1 + 5 files changed, 94 insertions(+), 73 deletions(-) create mode 100644 src/main/resources/db/migration/mysql/V1.7__add_product_name_index.sql diff --git a/src/main/java/org/example/commerce_site/application/order/dto/OrderDetailResponseDto.java b/src/main/java/org/example/commerce_site/application/order/dto/OrderDetailResponseDto.java index 722b9e2..d58bac4 100644 --- a/src/main/java/org/example/commerce_site/application/order/dto/OrderDetailResponseDto.java +++ b/src/main/java/org/example/commerce_site/application/order/dto/OrderDetailResponseDto.java @@ -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() @@ -54,4 +50,19 @@ public static List toDtoList(List 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; + } } diff --git a/src/main/java/org/example/commerce_site/application/order/dto/OrderResponseDto.java b/src/main/java/org/example/commerce_site/application/order/dto/OrderResponseDto.java index bbe036f..4c97bc0 100644 --- a/src/main/java/org/example/commerce_site/application/order/dto/OrderResponseDto.java +++ b/src/main/java/org/example/commerce_site/application/order/dto/OrderResponseDto.java @@ -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 orderDetails; - - public Get(Long id, BigDecimal totalAmount, OrderStatus status) { - this.id = id; - this.totalAmount = totalAmount; - this.status = status; - } - - public void setOrderDetails(List orderDetails) { - this.orderDetails = orderDetails; - } + private List orderDetails; } } diff --git a/src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepositoryImpl.java b/src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepositoryImpl.java index 860f989..142ee94 100644 --- a/src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepositoryImpl.java +++ b/src/main/java/org/example/commerce_site/infrastructure/order/CustomOrderRepositoryImpl.java @@ -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 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 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 groupedOrderList = orderList.stream() - .collect(Collectors.groupingBy(OrderResponseDto.Get::getId)) - .values() - .stream() - .map(orderGroup -> { - OrderResponseDto.Get firstOrder = orderGroup.get(0); - List 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 resultList = query.getResultList(); + + Map 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 orderList = new ArrayList<>(orderMap.values()); + + return PageConverter.getPage(orderList, pageable); + } + + private LocalDateTime convertTimestampToLocalDateTime(Timestamp timestamp) { + return timestamp != null ? timestamp.toLocalDateTime() : null; } } diff --git a/src/main/java/org/example/commerce_site/representation/order/dto/OrderResponse.java b/src/main/java/org/example/commerce_site/representation/order/dto/OrderResponse.java index 40f918f..2702a08 100644 --- a/src/main/java/org/example/commerce_site/representation/order/dto/OrderResponse.java +++ b/src/main/java/org/example/commerce_site/representation/order/dto/OrderResponse.java @@ -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()) @@ -65,7 +65,7 @@ public static DetailGet of(OrderDetailResponseDto.Get dto) { .build(); } - public static List of(List dtos) { + public static List of(List dtos) { return dtos.stream().map(DetailGet::of).toList(); } } diff --git a/src/main/resources/db/migration/mysql/V1.7__add_product_name_index.sql b/src/main/resources/db/migration/mysql/V1.7__add_product_name_index.sql new file mode 100644 index 0000000..dc466a7 --- /dev/null +++ b/src/main/resources/db/migration/mysql/V1.7__add_product_name_index.sql @@ -0,0 +1 @@ +ALTER TABLE products ADD FULLTEXT(name); \ No newline at end of file From 0e7668fed85b49f7510d5e4d9ff65cbf94558d32 Mon Sep 17 00:00:00 2001 From: suhaoh Date: Mon, 28 Oct 2024 18:33:21 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[41]=20rest=20template=20=EC=9D=84=20connec?= =?UTF-8?q?tion=20pool=20=EB=A1=9C=20=EA=B4=80=EB=A6=AC=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + .../commerce_site/config/AppConfig.java | 13 ---- .../config/RestTemplateConfig.java | 71 +++++++++++++++++++ src/main/resources/application.yml | 8 ++- 4 files changed, 81 insertions(+), 14 deletions(-) delete mode 100644 src/main/java/org/example/commerce_site/config/AppConfig.java create mode 100644 src/main/java/org/example/commerce_site/config/RestTemplateConfig.java diff --git a/build.gradle b/build.gradle index 6cb9412..5b06fb6 100644 --- a/build.gradle +++ b/build.gradle @@ -47,6 +47,9 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' // Project Common Dependency End ---------------------------------------- + // HttpComponent for restTemplate connection pool + implementation 'org.apache.httpcomponents.client5:httpclient5:5.3' + implementation 'org.apache.httpcomponents.core5:httpcore5:5.3' // QueryDsl implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' diff --git a/src/main/java/org/example/commerce_site/config/AppConfig.java b/src/main/java/org/example/commerce_site/config/AppConfig.java deleted file mode 100644 index 12fa81f..0000000 --- a/src/main/java/org/example/commerce_site/config/AppConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.example.commerce_site.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.client.RestTemplate; - -@Configuration -public class AppConfig { - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } -} diff --git a/src/main/java/org/example/commerce_site/config/RestTemplateConfig.java b/src/main/java/org/example/commerce_site/config/RestTemplateConfig.java new file mode 100644 index 0000000..a00c891 --- /dev/null +++ b/src/main/java/org/example/commerce_site/config/RestTemplateConfig.java @@ -0,0 +1,71 @@ +package org.example.commerce_site.config; + +import static org.springframework.http.HttpStatus.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.example.commerce_site.common.exception.CustomException; +import org.example.commerce_site.common.exception.ErrorCode; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Configuration +public class RestTemplateConfig { + @Bean + RestTemplate restTemplate() { + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(100); + connectionManager.setDefaultMaxPerRoute(30); + + RequestConfig requestConfig = RequestConfig.custom() + .setResponseTimeout(5000, TimeUnit.MILLISECONDS) + .build(); + + CloseableHttpClient httpClient = HttpClientBuilder.create() + .setDefaultRequestConfig(requestConfig) + .setConnectionManager(connectionManager) + .build(); + + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); + factory.setConnectTimeout(3000); + factory.setHttpClient(httpClient); + + RestTemplate restTemplate = new RestTemplate(factory); + + restTemplate.setErrorHandler(new ResponseErrorHandler() { + @Override + public boolean hasError(ClientHttpResponse response) throws IOException { + return response.getStatusCode().is4xxClientError() || response.getStatusCode().is5xxServerError(); + } + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + log.error("Error occurred : {} ", response.getStatusCode()); + switch (response.getStatusCode()) { + case UNAUTHORIZED: + throw new CustomException(ErrorCode.UNAUTHORIZED); + case FORBIDDEN: + throw new CustomException(ErrorCode.ACCESS_DENIED); + case NOT_FOUND: + throw new CustomException(ErrorCode.NOT_FOUND); + default: + throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR); + } + } + }); + + return restTemplate; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 72dfb0c..7e09bd7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -71,5 +71,11 @@ auth: logging: level: org: + apache: + http: DEBUG + tomcat.util.net: DEBUG springframework: - security: DEBUG \ No newline at end of file + security: DEBUG + sun: + security: + ssl: DEBUG \ No newline at end of file From 6ac59cb3ef763ff243c143628579df65a5bd4f92 Mon Sep 17 00:00:00 2001 From: suhaoh Date: Fri, 1 Nov 2024 14:34:45 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[41]=20rest=20template=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commerce_site/common/exception/ErrorCode.java | 6 +++++- .../commerce_site/config/RestTemplateConfig.java | 14 ++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/example/commerce_site/common/exception/ErrorCode.java b/src/main/java/org/example/commerce_site/common/exception/ErrorCode.java index 9a1122b..bba41b8 100644 --- a/src/main/java/org/example/commerce_site/common/exception/ErrorCode.java +++ b/src/main/java/org/example/commerce_site/common/exception/ErrorCode.java @@ -46,7 +46,11 @@ public enum ErrorCode { CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "카테고리 정보를 찾을 수 없습니다."), //shipment - SHIPMENT_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "배송 정보를 찾을 수 없습니다."); + SHIPMENT_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "배송 정보를 찾을 수 없습니다."), + + //rest template + REST_TEMPLATE_CONNECTION_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 500, "Rest template 에러"); + private final HttpStatus httpStatus; private final int code; diff --git a/src/main/java/org/example/commerce_site/config/RestTemplateConfig.java b/src/main/java/org/example/commerce_site/config/RestTemplateConfig.java index a00c891..31f5b31 100644 --- a/src/main/java/org/example/commerce_site/config/RestTemplateConfig.java +++ b/src/main/java/org/example/commerce_site/config/RestTemplateConfig.java @@ -1,7 +1,5 @@ package org.example.commerce_site.config; -import static org.springframework.http.HttpStatus.*; - import java.io.IOException; import java.util.concurrent.TimeUnit; @@ -53,16 +51,8 @@ public boolean hasError(ClientHttpResponse response) throws IOException { @Override public void handleError(ClientHttpResponse response) throws IOException { log.error("Error occurred : {} ", response.getStatusCode()); - switch (response.getStatusCode()) { - case UNAUTHORIZED: - throw new CustomException(ErrorCode.UNAUTHORIZED); - case FORBIDDEN: - throw new CustomException(ErrorCode.ACCESS_DENIED); - case NOT_FOUND: - throw new CustomException(ErrorCode.NOT_FOUND); - default: - throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR); - } + log.error("Error response message : {}", response.getBody()); + throw new CustomException(ErrorCode.REST_TEMPLATE_CONNECTION_ERROR); } });