Jin's Dev Story

[스프링 부트 쇼핑몰 프로젝트 with JPA] 12. 구매 내역 조회 본문

Web & Android/스프링 부트 쇼핑몰 프로젝트 with JPA

[스프링 부트 쇼핑몰 프로젝트 with JPA] 12. 구매 내역 조회

woojin._. 2023. 10. 16. 19:21
이 내용은 스프링 부트 쇼핑몰 프로젝트 with JPA 책을 학습한 내용입니다.

1. OrderItemDto

  • 주문 상품 정보를 담을 OrderItemDto 객체 생성
  • 주문 상품 정보 (상품, 수량)와 상품의 이미지를 담고 있음
@Getter
@Setter
public class OrderItemDto {

    private String itemNm;  // 상품 이름
    private int count;      // 주문 수량
    private int orderPrice; // 주문 가격
    private String imgUrl;  // 상품 이미지

    public OrderItemDto(OrderItem orderItem, String imgUrl) {
        this.itemNm = orderItem.getItem().getItemNm();
        this.count = orderItem.getCount();
        this.orderPrice = orderItem.getOrderPrice();
        this.imgUrl = imgUrl;
    }
}

2. OrderHistDto

  • 주문 정보를 담을 OrderHistDto 객체 생성
  • 주문 정보내에 주문 상품 정보 List 존재
@Getter
@Setter
public class OrderHistDto {

    private Long orderId; // 주문자 아이디
    private String orderDate; // 주문 날짜
    private OrderStatus orderStatus; // 주문 상태
    private List<OrderItemDto> orderItemDtoList = new ArrayList<>();

    // 주문 정보를 담을 OrderHistDto
    public OrderHistDto(Order order) {
        this.orderId = order.getId();
        this.orderDate = order.getOrderDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        this.orderStatus = order.getOrderStatus();
    }

    // 주문 정보 내에 주문 상품 정보 List
    public void addOrderItemDto(OrderItemDto orderItemDto) {
        orderItemDtoList.add(orderItemDto);
    }
}

3. 구매 내역 조회 리포지토리

  • @Query 어노테이션을 이용하여 구매 내역을 조회
  • SQL 문을 여러줄로 작성 시 마지막에 띄어쓰기 " " 필수
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);

}

4. 상품 대표 이미지 조회 리포지토리

  • 구매 내역 페이지에서 주문 상품의 대표 이미지를 위한 조회
public interface ItemImgRepository extends JpaRepository<ItemImg, Long> {

    List<ItemImg> findByItemIdOrderByIdAsc(Long itemId);

    // 구매 내역 페이지에서 주문 상품의 대표 이미지를 위한 조회
    ItemImg findByItemAndRepimgYn(Item item, String repimgYn);

}

5. OrderService

  • 주문 목록을 조회하는 로직 (메소드) 추가
// 주문 목록 조회
    @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<>();

        for(Order order : orders) {
            OrderHistDto orderHistDto = new OrderHistDto(order);
            List<OrderItem> orderItems = order.getOrderItems();
            for(OrderItem orderItem : orderItems) {
                ItemImg itemImg = itemImgRepository.findByItemAndRepimgYn(orderItem.getItem(), "Y");
                OrderItemDto orderItemDto = new OrderItemDto(orderItem, itemImg.getImgUrl());
                orderHistDto.addOrderItemDto(orderItemDto);
            }
            orderHistDtos.add(orderHistDto);
        }
        return new PageImpl<OrderHistDto>(orderHistDtos, pageable, totalCount);
    }
  • 동작 구조 및 원리
    • order, orderItem Entity 객체를 각각 OrderHistDto, OrderItemDto 객체로 변환

6. OrderController

  • 로그인한 유저의 email 과 페이징 정보를 이용해서 구매 내역 Dto 객체를 담는 page 객체 생성 및 반환
// 로그인한 유저의 email과 페이징 정보를 이용해서 구매내역 Dto 객체를 담는 Page 객체 생성 및 반환
    @GetMapping({"/orders", "/orders/{page}"})
    public String orderHist(@PathVariable("page") Optional<Integer> page, Principal principal, Model model) {

        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";
    }

7. 구매 내역 페이지 View

<!DOCTYPE html>
<html xmlns:th="http://www.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>

<th:block layout:fragment="script">

  <script th:inline="javascript">
    function cancelOrder(orderId) {
      var token = $("meta[name='_csrf']").attr("content");
      var header = $("meta[name='_csrf_header']").attr("content");

      var url = "/order/" + orderId + "/cancel";
      var paramData = {
        orderId : orderId,
      };

      var param = JSON.stringify(paramData);

      $.ajax({
        url      : url,
        type     : "POST",
        contentType : "application/json",
        data     : param,
        beforeSend : function(xhr){
          /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
          xhr.setRequestHeader(header, token);
        },
        dataType : "json",
        cache   : false,
        success  : function(result, status){
          alert("주문이 취소 되었습니다.");
          location.href='/orders/' + [[${page}]];
        },
        error : function(jqXHR, status, error){
          if(jqXHR.status == '401'){
            alert('로그인 후 이용해주세요');
            location.href='/member/login';
          } else{
            alert(jqXHR.responseText);
          }
        }
      });
    }
  </script>

</th:block>

<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(kr.spring.order.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(kr.spring.order.constant.OrderStatus).ORDER}">
          <h6 style="margin-top : 6px">(취소 완료)</h6>
        </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>

8. 구매 내역 페이지 결과 화면

9. 성능 최적화 적용

  • application.properties 파일에 batch_fetch_size 설정 추가
# 기본 batch size 설정
spring.jpa.properties.hibernate.default_batch_fetch_size=1000