๐Ÿ’ท๋ฉ”์ธ ํŽ˜์ด์ง€ ๋งŒ๋“ค๊ธฐ

gdhiยท2023๋…„ 12์›” 14์ผ
post-thumbnail

๐Ÿ’ท๋ฉ”์ธ ํŽ˜์ด์ง€

๋“ฑ๋ก๋œ ์ƒํ’ˆ์˜ ์‚ฌ์ง„๊ณผ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฉ”์ธ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž


๐Ÿ“ŒMainItemDto ํด๋ž˜์Šค ์ƒ์„ฑ

package com.shop.dto;

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

@Getter
@Setter
// ๋ฉ”์ธํ™”๋ฉด์— ์ถœ๋ ฅํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ DTO ๊ฐ์ฒด
public class MainItemDto {

    // ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์งˆ ๋‚ด์šฉ (๋“ฑ๋ก๋‚ ์งœ, ์ˆ˜์ •๋‚ ์งœ, ๋“ฑ๋ก์ž ๋“ฑ ์ œ์™ธ)
    private Long id;
    private String itemNm;
    private String itemDetail;
    private String imgUrl;
    private Integer price;

    // Entity ๊ฐ์ฒด๋ฅผ DTO ๊ฐ์ฒด๋กœ ๋ฐ”๋กœ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ง€์›ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜. ๋ณดํ†ต ์ƒ์„ฑ์ž์— ์‚ฌ์šฉ
    // @QueryProjection ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  QDto ํŒŒ์ผ ํ•„์š”
    @QueryProjection // Querydsl ๊ฒฐ๊ณผ ์กฐํšŒ ์‹œ MainItemDto ๊ฐ์ฒด๋กœ ๋ฐ”๋กœ ์˜ค๋„๋ก ํ™œ์šฉ
    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 ์ธํ„ฐํŽ˜์ด์Šค ์ˆ˜์ •

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<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable);
    
    // ๋ฉ”์ธ์—์„œ๋„ ๊ฒ€์ƒ‰์ด ๋˜๊ฒŒ๋” ItemSearchDto์„ ๋ถˆ๋Ÿฌ์˜จ๋‹ค
    Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable);
    
}



๐Ÿ“ŒItemRepositoryCustomImpl ํด๋ž˜์Šค ์ˆ˜์ •


...

    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;

        //QmainItemDto @QueryProjection์„ ์‚ฌ์šฉ ํ•˜๋ฉด DTO๋กœ ๋ฐ”๋กœ ์กฐํšŒ๊ฐ€ ๊ฐ€๋Šฅ
        QueryResults<MainItemDto> results = queryFactory
                // MainItemDto ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜
                // ๋ฉค๋ฒ„๋ณ€์ˆ˜ ์ดˆ๊ธฐํ™”๋Š” ์กฐํšŒ๋œ ๊ฒฐ๊ณผ๊ฐ’์—์„œ MainItemDto ๊ฐ์ฒด ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์ง€์ •
                // ์ฆ‰, db ์กฐํšŒ ๊ฒฐ๊ณผ๋Š” itemImg-item ์กฐ์ธ๋œ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ˜ํ™˜๋˜์ง€๋งŒ ๊ทธ ์ค‘ ์ผ๋ถ€๋งŒ ์‚ฌ์šฉ
                .select(new QMainItemDto(item.id,
                                         item.itemNm,
                                         item.itemDetail,
                                         itemImg.imgUrl,
                                         item.price)
                       )
                .from(itemImg)
                // ๋‚ด๋ถ€ ์กฐ์ธ. itemImg ํ…Œ์ด๋ธ”์˜ item ํ•„๋“œ๊ฐ€ ์ฐธ์กฐํ•˜๋Š” item ํ…Œ์ด๋ธ” ์กฐ์ธ 
                .join(itemImg.item, item)
                // regTime.eq("Y") ๋Œ€ํ‘œ์ด๋ฏธ์ง€๋งŒ ๊ฐ€์ ธ ์˜ด
                .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 ํด๋ž˜์Šค ์ˆ˜์ •

...

@Transactional(readOnly = true)
    public Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable){
        return itemRepository.getMainItemPage(itemSearchDto, pageable);
    }

}



๐Ÿ“ŒMainController ํด๋ž˜์Šค ์ˆ˜์ •

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;

    //ํ™”๋ฉด header Navbar ์•ˆ์— ์žˆ๋Š” "search" ๋ถ€๋ถ„์—์„œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด ์ž…๋ ฅ
    // ๐Ÿ‘‰ QueryString ๋„˜์–ด์˜ค๋Š” searchQuery ๋ณ€์ˆ˜๋ฅผ ItemSearchDto ๊ฐ์ฒด์˜ ๋ฉค๋ฒ„๋ณ€์ˆ˜์— ์ดˆ๊ธฐํ™”
    //QueryString ์œผ๋กœ ๋„˜์–ด์˜ค๋Š” "page" ๋ณ€์ˆ˜๋ฅผ page ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋„˜๊น€
    @GetMapping(value = "/")
    public String main (ItemSearchDto itemSearchDto, Optional<Integer> page, Model model){

        // PageRequest.of() ๋ฅผ ํ†ตํ•ด์„œ ํŽ˜์ด์ง€์˜ ์œ ๋ฌด๋ฅผ ํ™•์ธํ•œ ํ›„์— Pageable ๊ฐ์ฒด ์ƒ์„ฑ
        // ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์กฐํšŒํ•  ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ, ๋‘ ๋ฒˆ์งธ๋Š” ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ฌ ๋ฐ์ดํ„ฐ ์ˆ˜
        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 5);

        if (itemSearchDto.getSearchQuery() == null){
            itemSearchDto.setSearchQuery("");
        }

        // itemSearchDto: ์กฐํšŒ ์กฐ๊ฑด, pageable: ํŽ˜์ด์ง• ์ •๋ณด
        Page<MainItemDto> items = itemService.getMainItemPage(itemSearchDto, pageable);
        System.out.println(items.getNumber() + "!!!!!!!!!!!!!!!!!");
        System.out.println(items.getTotalPages() + "#################");

        model.addAttribute("items", items);
        model.addAttribute("itemSearchDto", itemSearchDto);
        model.addAttribute("maxPage", 5);

        return "main";

    }
}



๐Ÿ“Œmain.html ์ˆ˜์ •

<!DOCTYPE html>
<html xmlns:th="http://www.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;
        }
        .carousel-item{
            padding-bottom:60px;
        }
    </style>
</th:block>

<th:block layout:fragment="script">
    <script type="text/javascript">
        $(document).ready(function () {
            $('.carousel').carousel({
                interval: 4000,
                pause: "hover",
                wrap: true,
                keyboard : true
            });
        });
    </script>
</th:block>

<div layout:fragment="content" style="min-width: 700px; max-width: 900px; margin: 50px auto 100px auto;">

    <div id="carouselController" class="carousel slide" data-ride="carousel">
        <div class="carousel-inner">
            <div class="carousel-item active">
                <img class="d-block w-100"
                     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">
                        <!-- ์ƒํ’ˆ ์ด๋ฏธ์ง€๋ฅผ ์š”์ฒญํ•˜๋Š” URL ์€ ์ƒํ’ˆ ์ด๋ฏธ์ง€ ์ •๋ณด์— ๋‹ด๊ธด imgUrl ๋กœ ์ง€์ • ๐Ÿ‘‰ ๋Œ€ํ‘œ์ด๋ฏธ์ง€ -->
                        <img th:src="${item.imgUrl}" class="card-img-top" th:alt="${item.itemNm}" height="300">
                        <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>
</html>



๐Ÿ“Œ๊ฒฐ๊ณผ

๐Ÿ‘‰ ๋ฉ”์ธ ํŒจ์ด์ง€

๐Ÿ‘‰ 4ํŽ˜์ด์ง€

๐Ÿ‘‰ 3 ๊ฒ€์ƒ‰



โ“์–ด๋–ค ๊ตฌ์กฐ๋กœ ์‹คํ–‰?


๐Ÿ‘‰ ํŽ˜์ด์ง€๋‚˜ ๊ฒ€์ƒ‰ ๊ฐ™์€ ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ๋”ฐ๋กœ ๊ณต๋ถ€ํ•˜๋Š”๊ฒŒ ๋‚˜์„ ๋“ฏ. ์ผ๋‹จ ๋‚˜๋ฌด๋ฅผ ๋ณด์ง€๋ง๊ณ  ์ˆฒ์„ ๋จผ์ € ๋ณด์ž

0๊ฐœ์˜ ๋Œ“๊ธ€