- 조회한 주문 데이터를 화면에 보낼 때 사용할 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 문제를 많이 만나게 하는데 성능상 이슈가 생길 수 있기 때문에 조심해서 사용

'project > 쇼핑몰 프로젝트' 카테고리의 다른 글
| [쇼핑몰] 주문 취소하기 (0) | 2023.09.12 |
|---|---|
| [쇼핑몰] 주문 기능 구현하기 - 2 (0) | 2023.09.08 |
| [쇼핑몰] 주문 기능 구현하기 - 1 (0) | 2023.09.07 |
| [쇼핑몰] 상품 상세 페이지 (0) | 2023.09.06 |
| [쇼핑몰] 메인화면 (0) | 2023.09.05 |