본문 바로가기
project/쇼핑몰 프로젝트

[쇼핑몰] 주문 이력 조회하기

by 혀끄니 2023. 9. 11.
728x90

- 조회한 주문 데이터를 화면에 보낼 때 사용할 DTO 클래스를 만듬

- 주문 상품 정보를 담을 OrderItemDto클래스를 생성

OrderItemDto.java

@Getter@Setter
public class OrderItemDto {
    //OrderItemDto 클래스의 생성자로 orderItem객체와 이미지 경로를 파라미터로 받아서
    //멤버 변수 값을 세팅
    public OrderItemDto(OrderItem orderItem, String imgUrl) {
        this.itemNm = orderItem.getItem().getItemNm();
        this.count = orderItem.getCount();
        this.orderPrice = orderItem.getOrderPrice();
        this.imgUrl = imgUrl;
    }

    private String itemNm; //상품명

    private int count; //주문수량

    private int orderPrice; //주문금액

    private String imgUrl; //상품 이미지 검색

}

OrderHistDto.java

@Getter@Setter
public class OrderHistDto {
    //OrderHistDto클래스의 생성자
    //order 객체를 파라미터로 받아서 멤버 변수 값을 세팅
    //주문 날짜의 경우 "yyyy-MM-dd HH:mm" 형태로 전달하기 위해서 포맷을 수정
    public OrderHistDto(Order order) {
        this.orderId = order.getId();
        this.orderDate = order.getOrderDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        this.orderStatus = order.getOrderStatus();
    }

    private Long orderId;
    private String orderDate;
    private OrderStatus orderStatus;

    private List<OrderItemDto> orderItemDtoList = new ArrayList<>();
    
    //orderItemDto 객체를 주문 심플 리스트에 추가하는 메소드
    public void addOrderItemDto(OrderItemDto orderItemDto){
        orderItemDtoList.add(orderItemDto);
    }
}

- 조회 조건이 복잡하지 않으면 QueryDsl을 사용하지 않고 @Query어노테이션을 이용해서 구현하는 것도 괜찮다고 생각

- @Query 어노테이션에 작성한 쿼리 String을 한 줄로 입력할 수도 있지만 가독성을 고려하여 여러 줄로 작성

- 마지막칸에 띄어쓰기를 하였는데 공백을 넣지 않으면 에러가 나기 때문에 유의하기

OrderRepository.java

public interface OrderRepository extends JpaRepository<Order, Long> {

    @Query("select o from Order o where o.member.email = :email order by o.orderDate desc")
    //현재 로그인한 사용자의 주문 데이터를 페이징 조건에 맞춰서 조회
    List<Order> findOrders(@Param("email") String email, Pageable pageable);

    @Query("select count(o) from Order o where o.member.email = :email")
    //현재 로그인한 회원의 주문 개수가 몇 개인지 조회
    Long countOrder(@Param("email") String email);
}

- ItemImgRepository 인터페이스에는 상품의 대표 이미지를 찾는 쿼리 메소드 추가

- 구매 이력 페이지에서 주문 상품의 대표 이미지를 보여주기 위해서 추가

ItemImgRepository.java

public interface ItemImgRepository extends JpaRepository<ItemImg, Long> {
    List<ItemImg> findByItemIdOrderByIdAsc(Long itemId);

    ItemImg findByItemIdAndRepimgYn(Long itemId, String repimgYn);
}

OrderService.java

@Service
@Transactional
@RequiredArgsConstructor
public class OrderService {

    private final ItemRepository itemRepository;
    private final MemberRepository memberRepository;
    private final OrderRepository orderRepository;
    private final ItemImgRepository itemImgRepository;

   ...코드 생략...

  @Transactional(readOnly = true)
    public Page<OrderHistDto> getOrderList(String email, Pageable pageable){
        //유저의 아이디와 페이징 조건을 이용하여 주문 목록을 조회
        List<Order> orders = orderRepository.findOrders(email,pageable);
        //유저의 주문 총 개수를 구함
        Long totalCount = orderRepository.countOrder(email);

        List<OrderHistDto> orderHistDtos = new ArrayList<>();
        
        //주문 리스트를 순회하면서 구매 이력 페이지에 전달할 DTO를 생성
        for (Order order : orders){
            OrderHistDto orderHistDto = new OrderHistDto(order);
            List<OrderItem> orderItems = order.getOrderItems();
            for(OrderItem orderItem : orderItems){
                //주문한 상품의 대표 이미지를 조회
                ItemImg itemImg = itemImgRepository.findByItemIdAndRepimgYn(orderItem.getItem().getId(),"Y");
                OrderItemDto orderItemDto = new OrderItemDto(orderItem, itemImg.getImgUrl());
                orderHistDto.addOrderItemDto(orderItemDto);
            }
            orderHistDtos.add(orderHistDto);
        }
        //페이지 구현 객체를 생성하여 반환
        return new PageImpl<OrderHistDto>(orderHistDtos,pageable,totalCount);
    }
}

- 구매이력을 조회할 수 있도록 OrderController 클래스에 지금까지 구현한 로직을 호출하는 메소드를 만듬

OrderController.java

    @GetMapping(value = {"/orders", "/orders/{page}"})
    public String orderHist(@PathVariable("page") Optional<Integer>page, Principal principal, Model model){
        //한 번에 가지고 올 주문의 개수는 4개로 설정
        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 4);

        //현재 로그인한 회원은 이메일과 페이징 객체를 파라미터로 전달하여
        //화면에 전달한 주문 목록 데이터를 리턴 값으로 받는다.
        Page<OrderHistDto> orderHistDtoList = orderService.getOrderList(principal.getName(),pageable);

        model.addAttribute("orders",orderHistDtoList);
        model.addAttribute("page",pageable.getPageNumber());
        model.addAttribute("maxPage",5);

        return "order/orderHist";
    }

OrderHist.html

<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout1}">

<head>
    <meta name="_csrf" th:content="${_csrf.token}"/>
    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
</head>
<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
    <style>
        .content-mg{
            margin-left:30%;
            margin-right:30%;
            margin-top:2%;
            margin-bottom:100px;
        }
        .repImgDiv{
            margin-right:15px;
            margin-left:15px;
            height:auto;
        }
        .repImg{
            height:100px;
            width:100px;
        }
        .card{
            width:750px;
            height:100%;
            padding:30px;
            margin-bottom:20px;
        }
        .fs18{
            font-size:18px
        }
        .fs24{
            font-size:24px
        }
    </style>
</th:block>
<div layout:fragment="content" class="content-mg">

    <h2 class="mb-4">
        구매 이력
    </h2>

    <div th:each="order : ${orders.getContent()}">

        <div class="d-flex mb-3 align-self-center">
            <h4 th:text="${order.orderDate} + ' 주문'"></h4>
            <div class="ml-3">
                <th:block th:if="${order.orderStatus == T(com.shop.constant.OrderStatus).ORDER}">
                    <button type="button" class="btn btn-outline-secondary" th:value="${order.orderId}" onclick="cancelOrder(this.value)">주문취소</button>
                </th:block>
                <th:block th:unless="${order.orderStatus == T(com.shop.constant.OrderStatus).ORDER}">
                    <h4>(취소 완료)</h4>
                </th:block>
            </div>
        </div>
        <div class="card d-flex">
            <div th:each="orderItem : ${order.orderItemDtoList}" class="d-flex mb-3">
                <div class="repImgDiv">
                    <img th:src="${orderItem.imgUrl}" class = "rounded repImg" th:alt="${orderItem.itemNm}">
                </div>
                <div class="align-self-center w-75">
                    <span th:text="${orderItem.itemNm}" class="fs24 font-weight-bold"></span>
                    <div class="fs18 font-weight-light">
                        <span th:text="${orderItem.orderPrice} +'원'"></span>
                        <span th:text="${orderItem.count} +'개'"></span>
                    </div>
                </div>
            </div>
        </div>

    </div>

    <div th:with="start=${(orders.number/maxPage)*maxPage + 1}, end=(${(orders.totalPages == 0) ? 1 : (start + (maxPage - 1) < orders.totalPages ? start + (maxPage - 1) : orders.totalPages)})" >
        <ul class="pagination justify-content-center">

            <li class="page-item" th:classappend="${orders.number eq 0}?'disabled':''">
                <a th:href="@{'/orders/' + ${orders.number-1}}" aria-label='Previous' class="page-link">
                    <span aria-hidden='true'>Previous</span>
                </a>
            </li>

            <li class="page-item" th:each="page: ${#numbers.sequence(start, end)}" th:classappend="${orders.number eq page-1}?'active':''">
                <a th:href="@{'/orders/' + ${page-1}}" th:inline="text" class="page-link">[[${page}]]</a>
            </li>

            <li class="page-item" th:classappend="${orders.number+1 ge orders.totalPages}?'disabled':''">
                <a th:href="@{'/orders/' + ${orders.number+1}}" aria-label='Next' class="page-link">
                    <span aria-hidden='true'>Next</span>
                </a>
            </li>

        </ul>
    </div>

</div>

</html>

- OrderService클래스에 구현한 getOrderList()메소드에서 for문을 순회하면서 order.getOrderItems()를 호출할 때마다 조회 쿼리문이 추가로 실행되는 것을 볼 수 있다.

- orders 리스트의 사이즈 만큼 쿼리문이 실행된다.

- 만약 orders의 사이즈가 100이었다면 100번의 쿼리문이 더 실행

application.properties

#기본 batch size 설정
spring.jpa.properties.hibernate.default_batch_fetch_size=1000

- 해당 옵션을 추가한 후 다시 구매이력을 조회할 때, 반복문에서 order.getOrderItems() 처음 실행 할때 조건절에 in쿼리문이 실행

- JPA를 사용하다 보면 N+1 문제를 많이 만나게 하는데 성능상 이슈가 생길 수 있기 때문에 조심해서 사용

 

728x90