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

[쇼핑몰] 메인화면

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

- @QueryProjection을 이용해 상품 조회 시 DTO 객체로결과값을 받는 방법을 알아보겠다.

- @QueryProjection을 이용하면 Item 객체로 값을 받은 후 DTO 클래스로 변환하는 과정 없이 바로 DTO객체를 뽑아 낼 수 있다.

MainItemDto.java

package com.shop.dto;

import com.querydsl.core.annotations.QueryProjection;
import lombok.Getter;
import lombok.Setter;

@Getter@Setter
public class MainItemDto {
    private Long id;

    private  String itemNm;

    private String itemDetail;

    private String imgUrl;

    private Integer price;

    //생성자에 @QueryProjection 어노테이션을 선언하여
    //Querydsl로 결과 조회 시 MainItemDto 객체로 바로 받아 오도록 활용
    @QueryProjection
    public MainItemDto(Long id, String itemNm, String itemDetail, String imgUrl, Integer price) {
        this.id = id;
        this.itemNm = itemNm;
        this.itemDetail = itemDetail;
        this.imgUrl = imgUrl;
        this.price = price;
    }
}

- ItemRepositoryCustom 클래스에 메인 페이지에 보여줄 상품 리스트를 가져오는 메소드를 생성

ItemRepositoryCustom.java

package com.shop.repository;

import com.shop.dto.ItemSearchDto;
import com.shop.dto.MainItemDto;
import com.shop.entity.Item;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;


public interface ItemRepositoryCustom {
    ...코드 생략...
    Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable);
}

ItemRepositoryCustomImpl.java

package com.shop.repository;

import com.querydsl.core.QueryResults;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.shop.constant.ItemSellStatus;
import com.shop.dto.ItemSearchDto;
import com.shop.dto.MainItemDto;
import com.shop.dto.QMainItemDto;
import com.shop.entity.Item;
import com.shop.entity.QItem;
import com.shop.entity.QItemImg;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.thymeleaf.util.StringUtils;

import javax.persistence.EntityManager;
import java.time.LocalDateTime;
import java.util.List;

public class ItemRepositoryCustomImpl implements ItemRepositoryCustom{

   ...코드 생략...

   //검색어가 null이 아니면 상품명에 해당 검색어가 포함되는 상품을 조회하는 조건을 반환
   private BooleanExpression itemNmLike(String searchQuery){
        return StringUtils.isEmpty(searchQuery) ? null : QItem.item.itemNm.like("%"+searchQuery+"%");
    }

    @Override
    public Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {
        QItem item = QItem.item;
        QItemImg itemImg = QItemImg.itemImg;

        QueryResults<MainItemDto> results = queryFactory
                                            .select(
                                                //QMainItemDto의 생성자에 반환할 값들을 넣어줌
                                                //@QueryProjection을 사용하면 DTO로 바로 조회가 가능
                                                //엔티티 조회 후 DTO로 변환하는 과정을 줄임
                                                new QMainItemDto(
                                                        item.id,
                                                        item.itemNm,
                                                        item.itemDetail,
                                                        itemImg.imgUrl,
                                                        item.price
                                                )
                                            ).from(itemImg)

                                             //itemimg와 item을 내부 조인
                                             .join(itemImg.item, item)

                                             //상품 이미지의 경우 대표 상품 이미지만 불러온다
                                             .where(itemImg.repimgYn.eq("Y"))
                                             .where(itemNmLike(itemSearchDto.getSearchQuery()))
                                             .orderBy(item.id.desc())
                                             .offset(pageable.getOffset())
                                             .limit(pageable.getPageSize())
                                             .fetchResults();
        List<MainItemDto> content = results.getResults();
        long total = results.getTotal();
        return new PageImpl<>(content,pageable,total);
    }
}

- 메인페이지 보여줄 상품 데이터를 조회하는 메소드를 ItemService 클래스에 추가

itemService.java

package com.shop.service;

import com.shop.dto.ItemFormDto;
import com.shop.dto.ItemImgDto;
import com.shop.dto.ItemSearchDto;
import com.shop.dto.MainItemDto;
import com.shop.entity.Item;
import com.shop.entity.ItemImg;
import com.shop.repository.ItemImgRepository;
import com.shop.repository.ItemRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.persistence.EntityExistsException;
import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;

@Service
@Transactional
@RequiredArgsConstructor
public class ItemService {
    ...코드 생략...
    @Transactional(readOnly = true)
    public Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable){
        return itemRepository.getMainItemPage(itemSearchDto, pageable);
    }
}

- 메인페이지에 상품 데이터를 보여주기 위해서 기존에 작성했던 MainController 클래스를 수정

MainController.java

package com.shop.controller;

import com.shop.dto.ItemSearchDto;
import com.shop.dto.MainItemDto;
import com.shop.service.ItemService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.Optional;

@Controller
@RequiredArgsConstructor
public class MainController {

    private final ItemService itemService;

    @GetMapping(value = "/")
    public String main(ItemSearchDto itemSearchDto, Optional<Integer> page, Model model){
        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 6);
        Page<MainItemDto> items = itemService.getMainItemPage(itemSearchDto, pageable);
        model.addAttribute("items", items);
        model.addAttribute("itemSearchDto", itemSearchDto);
        model.addAttribute("maxPage", 5);
        return "main";
    }
}

- 마지막으로 메인 페이지에서 상품 데이터를 보여주도록 main.html 파일을 수정

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

<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
    <style>
        .carousel-inner > .item {
            height: 350px;
        }
        .margin{
            margin-bottom:30px;
        }
        .banner{
            height: 300px;
            position: absolute; top:0; left: 0;
            width: 100%;
            height: 100%;
        }
        .card-text{
            text-overflow: ellipsis;
            white-space: nowrap;
            overflow: hidden;
        }
        a:hover{
            text-decoration:none;
        }
        .center{
            text-align:center;
        }
    </style>
</th:block>
<div layout:fragment="content">

    <div id="carouselControls" class="carousel slide margin" data-ride="carousel">
        <div class="carousel-inner">
            <div class="carousel-item active item">
                <img class="d-block w-100 banner" src="https://user-images.githubusercontent.com/13268420/112147492-1ab76200-8c20-11eb-8649-3d2f282c3c02.png" alt="First slide">
            </div>
        </div>
    </div>

    <input type="hidden" name="searchQuery" th:value="${itemSearchDto.searchQuery}">
    <div th:if="${not #strings.isEmpty(itemSearchDto.searchQuery)}" class="center">
        <p class="h3 font-weight-bold" th:text="${itemSearchDto.searchQuery} + '검색 결과'"></p>
    </div>

    <div class="row">
        <th:block th:each="item, status: ${items.getContent()}">
            <div class="col-md-4 margin">
                <div class="card">
                    <a th:href="'/item/' +${item.id}" class="text-dark">
                        <img th:src="${item.imgUrl}" class="card-img-top" th:alt="${item.itemNm}" height="400">
                        <div class="card-body">
                            <h4 class="card-title">[[${item.itemNm}]]</h4>
                            <p class="card-text">[[${item.itemDetail}]]</p>
                            <h3 class="card-title text-danger">[[${item.price}]]원</h3>
                        </div>
                    </a>
                </div>
            </div>
        </th:block>
    </div>

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

            <li class="page-item" th:classappend="${items.number eq 0}?'disabled':''">
                <a th:href="@{'/' + '?searchQuery=' + ${itemSearchDto.searchQuery} + '&page=' + ${items.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="${items.number eq page-1}?'active':''">
                <a th:href="@{'/' +'?searchQuery=' + ${itemSearchDto.searchQuery} + '&page=' + ${page-1}}" th:inline="text" class="page-link">[[${page}]]</a>
            </li>

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

        </ul>
    </div>

</div>
728x90