
๋ฑ๋ก๋ ์ํ์ ์ฌ์ง๊ณผ ์ ๋ณด๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฉ์ธ ํ์ด์ง๋ฅผ ๋ง๋ค์ด๋ณด์
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;
}
}


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);
}
...
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);
}
}
...
@Transactional(readOnly = true)
public Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable){
return itemRepository.getMainItemPage(itemSearchDto, pageable);
}
}
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";
}
}
<!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 ๊ฒ์
๐ ํ์ด์ง๋ ๊ฒ์ ๊ฐ์ ์ธ๋ถ ์ฌํญ์ ๋ฐ๋ก ๊ณต๋ถํ๋๊ฒ ๋์ ๋ฏ. ์ผ๋จ ๋๋ฌด๋ฅผ ๋ณด์ง๋ง๊ณ ์ฒ์ ๋จผ์ ๋ณด์