개발/Project(Spring-쇼핑몰)

Project (6-2) 주문 이력 조회하기

잇(IT) 2023. 8. 15. 16:19
728x90

- 주문을 한 다음에는 주문 이력을 조회할 수 있는 페이지를 만들어야 한다.

 

- 조회한 주문 데이터를 화면에 보낼 때 사용할 DTO 클래스를 생성한다.

- OrderItemDto.java

@Getter @Setter
public class OrderItemDto {

    private String itemNm; //상품명

    private int count; //주문 수량

    private int orderPrice; //주문 금액

    private String imgUrl; //상품 이미지 경로

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

- 생성자의 파라미터로 item이름을 가져와야 하기 때문에 OrderItem을 받아오고 주문 이력에 보여줄 이미지의 파라미터를 바당온다.


- 주문 정보를 담을 OrderHistDto클래스를 생성한다.

- OrderHistDto.java

@Getter @Setter
public class OrderHistDto {

    public OrderHistDto(Order order) {
        //1. OrderHistDto 클래스의 생성자로, order 객체를 파라미터로 받아서 멤버 변수 값을 세팅한다.
        // 주문 날짜의 경우 화면에 "yyyy-MM-dd HH:mm" 형태로 전달하기 위해서 포맷을 수정한다.
        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<>();

    public void addOrderItemDto(OrderItemDto orderItemDto) {
        //2. orderItemDto 객체를 주문 상품 리스트에 추가하는 메소드다.
        orderItemDtoList.add(orderItemDto);
    }
}

- 생성자의 파라미터로 Order 객체를 받아와서 Dto 필드에 복사해준다.

- 주문 이력 페이지에는 지금까지 주문한 orderItem들이 보여져야 하기 때문에 OrderItem 엔티티의 필드 값을 복사한 orderItemDto의 객체들을 담을 List를 생성한다.


- OrderRepository 인터페이스에 @Query어노테이션을 이용하여 주문 이력을 조회하는 쿼리를 작성해준다.

- 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> findOders(@Param("email") String email, Pageable pageable);
//    1. 현재 로그인한 사용자의 주문 데이터를 페이징 조건에 맞춰서 조회한다.

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

- :email과 같이 파라미터 바인딩 형식으로 원하는 값을 대입하여 쿼리를 호출 할 수 있다.

- 파라미터 email의 값이 :email에 바인딩 되어 쿼리를 호출 할 때 해당 조건을 반영하여 쿼리를 호출한다.


- ItemImgRepository 인터페이스에는 상폼의 대표 이미지를 찾는 쿼리 메소드를 추가한다. 주문 이력 페이지에서 대표 이미지를 보여주기 위해서이다.

- ItemImgRepository.java

public interface ItemImgRepository extends JpaRepository<ItemImg, Long> {

    List<ItemImg> findByItemIdOrderByIdAsc(Long itemId);

    ItemImg findByItemIdAndRepImgYn(Long itemId, String repImgYn);
}

- JPA를 통해 메서드 명을 통해 쿼리를 호출 할 수 있다. 


- 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.findOders(email, pageable);
//        1. 유저의 아이디와 페이징 조건을 이용하여 주문 목록을 조회한다.
        Long totalCount = orderRepository.countOrder(email);
//        2. 유저의 주문 총 개수를 구한다.

        List<OrderHistDto> orderHistDtos = new ArrayList<>();

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

        List<Order> orders = orderRepository.findOders(email, pageable);
//        1. 유저의 아이디와 페이징 조건을 이용하여 주문 목록을 조회한다.
        Long totalCount = orderRepository.countOrder(email);
//        2. 유저의 주문 총 개수를 구한다.

- orderRepository에서 작성한 findOrders 메서드를 통해 특정 회원이 주문한 주문들을 가져온다.

- 마찬가지로 countOrder 메서드를 통해 총 주문 갯수를 가져온다.

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

- findOrders 쿼리를 통해 찾아온 order들을 for문을 통해 하나씩 조회하면서, OrderHistDto 객체에 데이터들을 복사한다.

- order엔티티에서 orderItems 리스트를 가져온 다음 마찬가지로 for문을 돌리면서 해당 orderItem의 itemId와 해당 이미지가 대표 이미지인지 ("Y")를 찾아내는 쿼리를 실행하여 대표 특정 Item의 대표 이미지를 가져온다.

- orderItemDto생성자의 파라미터로 orderItem과 itemImg의 주소를 파라미터로 넘긴다음 해당 Dto 객체를 orderHistDto 객체의 orderItemDto를 담는 List에 담는다. 그리고 위의 메서드에서 생성한 orderHistDtos 리스트에 orderHistDto 객체들을 담는다.

- Page 인터페이스의 구현체인 PageImpl을 통해 orderHistDto 데이터들을 페이징 정보에 따라 전달한다.


- OrderController.java

@GetMapping(value = {"/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);
//        1. 한 번에 가지고 올 주문의 개수는 4개로 설정하겠다.

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

//        principal은 현재 로그인한 사용자의 정보를 가지고 있다. (여기선 Name이 email에 해당한다.)

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

        return "order/orderHist";
    }

- pageable 파라미터로 특정 페이지를 조회했을 경우, 아니면 처음 주문 이력 페이지를 조회했을 경우를 나누고, 데이터는 4개까지 보이도록 설정한다.

Page<OrderHistDto> orderHistDtoList = orderService.getOrderList(principal.getName(), pageable);

- principal.getName()을 통해 email로 회원을 검증하고 getOrderList 메서드를 호출하여 해당 회원의 orderHist를 가져온다.

- view에 해당 데이터들을 보여주기 위해 view에 orderHistDtoList및 페이징 정보들을 전달한다.


- 주문을 위한 화면(View)를 작성한다.

- orderHist.html

<!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>

<!-- 사용자 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>
<div th:each="order : ${orders.getContent()}">

- getContent(): 현재 페이지의 데이터를 반환합니다. 이 데이터는 일반적으로 리스트의 형태로 제공된다.

 

th:value="${order.orderId}" onclick="cancelOrder(this.value)">주문취소</button>

- HTML에서 'this' 키워드는 현재 이벤트를 발생시키는 요소(위에서는 버튼)를 가르키다. 따라서 'this.value'는 이벤트를 발생시키는 버튼의 'value' 속성 값을 의미한다.

- 즉, 위의 코드에서는 orderId의 값을 가르킨다.

 

<div th:each="orderItem : ${order.orderItemDtoList}" class="d-flex mb-3">
...
<img th:src="${orderItem.imgUrl}" class = "rounded repImg" th:alt="${orderItem.itemNm}">
...

- order는 현재 orderHistDto객체를 가르키고 있고, orderHistDto는 orderItemDtoList를 가지고 있다.

- orderItemDtoList를 돌면서 해당 orderItem의 imgUrl을 가져오게 되면 /images/**에 해당하는 경로를 url로 요청하게 되고 해당 경로는 WebMvcConfig에 의해 로컬에 실제 해당 이미지가 저장된 경로로 변경되고 해당 이미지를 가져오게 된다.

 

<div th:with="start=${(orders.number/maxPage)*maxPage + 1}, end=(${(orders.totalPages == 0)
     ? 1 : (start + (maxPage - 1) < orders.totalPages ? start + (maxPage - 1) : orders.totalPages)})" >
<!--    with는 변수를 초기화하거나 재설정하기 위해서 사용한다.-->
        <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 th:with="start=${(orders.number/maxPage)*maxPage + 1}, end=(${(orders.totalPages == 0)
     ? 1 : (start + (maxPage - 1) < orders.totalPages ? start + (maxPage - 1) : orders.totalPages)})" >

- start의 경우 orders.number를 통해 현재 페이지를 가져온다. 한 페이지에 5장씩 보여주기로 했기 때문에 (현재 페이지) / 5 + 1을 하게 1, 6, 11, 16과 같이 각 페이지의 시작 페이지를 가르키게 되고 해당 페이지를 start로 지정한다.

- end의 경우

1. 총 페이지 수가 0 일 경우 start와 end가 동일하기 때문에 둘 다 1로 설정된다.

2. 마지막 페이지가 8이라고 가정하고, 위의 코드를 보게 되면 해당 페이지의 start는 6이 될 것이다. (start 공식에 의해) 삼항 연산자의 앞부분은 (6 + 5 - 1 = 10)(start + (maxPage) - 1)이 부분의 경우 해당 페이지의 마지막 페이지를 나타내는 공식인데 해당 페이지가 총 페이지 (= 마지막 페이지) 보다 작으면 (1) 해당 페이지의 마지막 페이지를 보여주고 (2) 총 페이지가 작을 경우 해당 페이지의 마지막 페이지는 totalPages가 될 것이기 때문에 totalPages를 반환한다.

 

<li class="page-item" th:each="page: ${#numbers.sequence(start, end)}"

- #numbers는 Thymeleaf의 유틸리티 객체이며, start부터 end까지의 숫자 시퀀스를 생성한다. (ex. start가 1이고, end가 5라면 [1,2,3,4,5]와 같은 시퀀스가 생성된다.

728x90