보라코딩
스프링부트 페이징처리 + 별점순/인기순/거리순 + 검색기능 + join까지.. 본문
페이징처리가 되면 별점순 인기순 거리순이 안되고
반대가 되면 페이징이 안되는 고통에서 벗어났다!
드디어 해결했다...!
SQL문과 스프링부트 제대로 공부한 느낌이긴 함!
며칠 걸렸네.... 뿌듯해!!!!
다 되니까 검색기능 넣는건
별로 어렵지 않았다~
미리 Criteria 만들어둬서 ^^
list.html
별점순 / 인기순 / 거리순을 누른 상태에서
페이지 번호를 누르면 계속 파라미터를 전달할 수 있게 해주기 위해
Criteria에서 makeQueryString 이란 메서드를 만들어줬고
UriComponent를 사용했다!
(맨 아래 페이지 참고)
<a class="btn btn-warning purple3"
th:href="@{/store/list(storeStar='star')}">별점순</a>
<a class="btn btn-warning purple3"
th:href="@{/store/list(storeCount='popular')}">인기순</a>
<a class="btn btn-warning purple3"
th:href="@{/store/list(storeDistance='distance')}">거리순</a>
<div class="col-md-2 float-right">
<div class="form-group">
<form id="myForm" method="get">
<select class="form-control" name="cuisineSelect" onchange="submitForm()">
<option selected disabled>FOOD STYLE</option>
<option value="한식">한식</option>
<option value="중식">중식</option>
<option value="일식">일식</option>
<option value="양식">양식</option>
<option value="퓨전">퓨전</option>
<option value="푸드코트">푸드코트</option>
</select>
</form>
</div>
</div>
<!--페이징시작!!!!-->
<!-- 이전 버튼 -->
<div class="pagination-container mb-5">
<th:block th:if="${pageMaker.prev}">
<li class="pageMaker_btn prev pagination-item">
<a href="javascript:void(0)" th:onclick="movePage([[ ${#request.requestURI} ]], [[ ${pageMaker.cri.makeQueryString(pageMaker.pageStart - 1)} ]])">이전</a>
</li>
</th:block>
<!-- 페이지 번호 -->
<th:block th:with="pageMaker = ${pageMaker}">
<th:block th:each="num : *{#numbers.sequence(pageMaker.pageStart, pageMaker.pageEnd)}">
<li class="pageMaker_btn pagination-item" th:classappend="${pageMaker.cri.pageNum == num} ? 'active' : ''">
<a href="javascript:void(0)" th:text="${num}"
th:onclick="movePage([[ ${#request.requestURI} ]]
,[[ ${pageMaker.cri.makeQueryString(num)} ]])"></a>
</li>
</th:block>
</th:block>
<!-- 다음 버튼 -->
<th:block th:if="${pageMaker.next}">
<li class="pageMaker_btn next pagination-item">
<a href="javascript:void(0)" th:onclick="movePage(
[[ ${#request.requestURI} ]],
[[ ${pageMaker.cri.makeQueryString(pageMaker.pageEnd + 1)} ]])">다음</a>
</li>
</th:block>
<script>
function submitForm() {
var form = document.getElementById("myForm");
var selectedValue = form.querySelector('select[name="cuisineSelect"]').value;
form.action = "/store/list?cuisineSelect=" + encodeURIComponent(selectedValue);
form.submit();
}
/* ![CDATA[ */
function movePage(uri, queryString) {
console.log(uri);
console.log(queryString);
location.href = uri + queryString;
}
/* ]]*/
</script>
Controller
Map을 Object 가능하게 해서
String도 넣고 클래스도 넣을 수 있게 했다!
@GetMapping("/list")
public void get(Model model
, @RequestParam(value="cuisineSelect",required = false) String cuisineSelect
, @RequestParam(value="storeStar",required = false) String storeStar
, @RequestParam(value="storeCount",required = false) String storeCount
, @RequestParam(value="storeDistance",required = false) String storeDistance
, Criteria cri
){
if(cuisineSelect == null || cuisineSelect == ""){
cuisineSelect = "none";
}
if(storeStar == null || storeStar == ""){
storeStar = "none";
}
if(storeCount == null || storeCount == ""){
storeCount = "none";
}
if(storeDistance == null || storeDistance == ""){
storeDistance = "none";
}
Map<String,Object> orderMap = new HashMap<>();
orderMap.put("cuisineSelect", cuisineSelect);
orderMap.put("storeStar", storeStar);
orderMap.put("storeCount", storeCount);
orderMap.put("storeDistance", storeDistance);
orderMap.put("cri", cri);
log.info("orderMap : " + orderMap);
List<StoreVO> storeVO = service.store_getList(orderMap);
model.addAttribute("storeList", storeVO);
/*페이징*/
int total = service.store_totalCnt(orderMap);
PageDTO pageMaker = new PageDTO(cri, total);
model.addAttribute("pageMaker", pageMaker);
log.info("total : " + total);
log.info("new PageDTO(cri, total) : " + pageMaker);
}
Service
public List<StoreVO> store_getList(Map<String, Object> orderMap) {
//List<StoreVO> storeList = mapper.store_getList();
List<StoreVO> storeList = mapper.store_getList_withStar_withPaging(orderMap);
return storeList;
}
public int store_totalCnt(Map<String, Object> orderMap) {
return mapper.getTotalCount(orderMap);
}
Mapper
List<StoreVO> store_getList_withStar_withPaging(Map<String,Object> orderMap);
int getTotalCount(Map<String,Object> orderMap);
Mapper.xml
SQL문이 엄청 긴데...
정렬에 조인에 페이징 넣으니까
힘들었다 ㅠㅠㅠㅠ
근데 해냄..!
<!-- 메인 맛집 전체보기 화면에서 별점과 같이 받기 -->
<select id="store_getList_withStar_withPaging" resultMap="storeStarResultMap"
parameterType="Map">
SELECT *
FROM (
SELECT s.*,
ROWNUM AS RNUM
FROM (
SELECT store.store_idx, store.user_idx, store.store_name, store.category1, store.store_address,
store.store_lati, store.store_longi, store.phone_number, store.store_count, store.filename, store.distance,
ROUND(AVG(store_star), 1) AS STORE_STAR
FROM STORE store
LEFT JOIN STAR star ON store.store_idx = star.store_idx
WHERE store.STORE_STAT = 1
<if test="cuisineSelect != 'none'">
and category1 = #{cuisineSelect}
</if>
GROUP BY store.store_idx, store.user_idx, store.store_name, store.category1, store.store_address,
store.store_lati, store.store_longi, store.phone_number, store.store_count, store.filename, store.distance
<choose>
<when test="storeStar != 'none'">
order by STORE_STAR DESC NULLS LAST
</when>
<when test="storeCount != 'none'">
order by store_count DESC NULLS LAST
</when>
<when test="storeDistance != 'none'">
order by distance ASC NULLS LAST
</when>
<otherwise>
ORDER BY store_idx ASC
/*order by STORE_STAR DESC NULLS LAST*/
</otherwise>
</choose>
) s
)
<![CDATA[
WHERE RNUM > ((#{cri.pageNum} - 1) * #{cri.amount}) -- 페이지 번호에 따라 수정
AND RNUM <= #{cri.pageNum} * #{cri.amount} -- 페이지 번호에 따라 수정;
]]>
</select>
<!--페이징 처리시 전체 글수 확인-->
<select id="getTotalCount" parameterType="Map" resultType="integer">
SELECT count(*)
FROM store
WHERE STORE_STAT = 1
<if test="cuisineSelect != 'none'">
and category1 = #{cuisineSelect}
</if>
</select>
Criteria.java
package com.tastemate.domain.paging;
import lombok.Data;
import org.apache.ibatis.type.Alias;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
@Data
@Alias("Criteria")
public class Criteria {
/* 현재 페이지 번호 */
private int pageNum;
/* 페이지 표시 개수 */
private int amount;
/* 페이지 skip */
private int skip;
/* 검색 타입 */
private String type;
/* 검색 키워드 */
private String keyword;
private String cuisineSelect;
private String storeStar;
private String storeCount;
private String storeDistance;
/* Criteria 생성자 */
public Criteria(int pageNum, int amount) {
this.pageNum = pageNum;
this.amount = amount;
this.skip = (pageNum - 1) * amount;
}
/* Criteria 기본 생성자 */
public Criteria() {
this(1, 8);
}
/* 검색 타입 데이터 배열 변환 */
public String[] getTypeArr() {
return type == null ? new String[]{} : type.split("");
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
this.skip = (pageNum - 1) * this.amount;
}
public void setAmount(int amount) {
this.amount = amount;
this.skip = (this.pageNum - 1) * amount;
}
public String makeQueryString(int pageNum) {
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.queryParam("pageNum", pageNum)
.queryParam("amount", amount)
.queryParam("searchType", type)
.queryParam("keyword", keyword)
.queryParam("cuisineSelect", cuisineSelect)
.queryParam("storeStar", storeStar)
.queryParam("storeCount", storeCount)
.queryParam("storeDistance", storeDistance)
.build()
.encode();
return uriComponents.toUriString();
}
}
Paging.java
package com.tastemate.domain.paging;
import lombok.Data;
import lombok.Getter;
import lombok.ToString;
@Data
public class PageDTO {
/* 페이지 시작 번호 */
private int pageStart;
/* 페이지 끝 번호 */
private int pageEnd;
/* 이전, 다음 버튼 존재 유무 */
private boolean next, prev;
/* 행 전체 개수 */
private int total;
/* 현재페이지 번호(pageNum), 행 표시 수(amount), 검색 키워드(keyword), 검색 종류(type)*/
private Criteria cri;
/* 생성자(클래스 호출 시 각 변수 값 초기화 */
public PageDTO(Criteria cri, int total) {
/* cri, total 초기화 */
this.cri = cri;
this.total = total;
/* 페이지 끝 번호 */
this.pageEnd = (int) (Math.ceil(cri.getPageNum() / 5.0)) * 5;
/* 페이지 시작 번호 */
this.pageStart = this.pageEnd - 4;
/* 전체 마지막 페이지 번호 */
int realEnd = (int) (Math.ceil(total * 1.0 / cri.getAmount()));
/* 페이지 끝 번호 유효성 체크 */
if (realEnd < pageEnd) {
this.pageEnd = realEnd;
}
/* 이전 버튼 값 초기화 */
this.prev = this.pageStart > 1;
/* 다음 버튼 값 초기화 */
this.next = this.pageEnd < realEnd;
}
}
검색기능 추가된 부분
html
<div class="container text-center">
<form th:action="@{/store/list}" method="GET">
<div class="row justify-content-center mt-2 mb-5">
<div class="col-md-4">
<div class="input-group">
<input name="keyword" type="text" class="form-control rounded-pill" placeholder="맛집 검색!" aria-describedby="button-addon2">
<button type="submit" class="btn btn-warning mx-3">검색</button>
</div>
</div>
</div>
</form>
</div>
Controller
@GetMapping("/list")
public void get(Model model
, @RequestParam(value="cuisineSelect",required = false) String cuisineSelect
, @RequestParam(value="storeStar",required = false) String storeStar
, @RequestParam(value="storeCount",required = false) String storeCount
, @RequestParam(value="storeDistance",required = false) String storeDistance
, Criteria cri
){
if(cuisineSelect == null || cuisineSelect == ""){
cuisineSelect = "none";
}
if(storeStar == null || storeStar == ""){
storeStar = "none";
}
if(storeCount == null || storeCount == ""){
storeCount = "none";
}
if(storeDistance == null || storeDistance == ""){
storeDistance = "none";
}
if(cri.getKeyword() == null || cri.getKeyword() == ""){
cri.setKeyword("none");
}
Map<String,Object> orderMap = new HashMap<>();
orderMap.put("cuisineSelect", cuisineSelect);
orderMap.put("storeStar", storeStar);
orderMap.put("storeCount", storeCount);
orderMap.put("storeDistance", storeDistance);
orderMap.put("cri", cri);
log.info("orderMap : " + orderMap);
List<StoreVO> storeVO = service.store_getList(orderMap);
model.addAttribute("storeList", storeVO);
/*페이징*/
int total = service.store_totalCnt(orderMap);
PageDTO pageMaker = new PageDTO(cri, total);
model.addAttribute("pageMaker", pageMaker);
log.info("total : " + total);
log.info("new PageDTO(cri, total) : " + pageMaker);
}
Mapper
<!-- 메인 맛집 전체보기 화면에서 별점과 같이 받기 -->
<select id="store_getList_withStar_withPaging" resultMap="storeStarResultMap"
parameterType="Map">
SELECT *
FROM (
SELECT s.*,
ROWNUM AS RNUM
FROM (
SELECT store.store_idx, store.user_idx, store.store_name, store.category1, store.store_address,
store.store_lati, store.store_longi, store.phone_number, store.store_count, store.filename, store.distance,
ROUND(AVG(store_star), 1) AS STORE_STAR
FROM STORE store
LEFT JOIN STAR star ON store.store_idx = star.store_idx
WHERE store.STORE_STAT = 1
<if test="cri.getKeyword != 'none'">
AND store.store_name LIKE '%' || #{cri.keyword} || '%'
</if>
<if test="cuisineSelect != 'none'">
and category1 = #{cuisineSelect}
</if>
GROUP BY store.store_idx, store.user_idx, store.store_name, store.category1, store.store_address,
store.store_lati, store.store_longi, store.phone_number, store.store_count, store.filename, store.distance
<choose>
<when test="storeStar != 'none'">
order by STORE_STAR DESC NULLS LAST
</when>
<when test="storeCount != 'none'">
order by store_count DESC NULLS LAST
</when>
<when test="storeDistance != 'none'">
order by distance ASC NULLS LAST
</when>
<otherwise>
/*ORDER BY store_idx ASC*/
order by STORE_STAR DESC NULLS LAST
</otherwise>
</choose>
) s
)
<![CDATA[
WHERE RNUM > ((#{cri.pageNum} - 1) * #{cri.amount}) -- 페이지 번호에 따라 수정
AND RNUM <= #{cri.pageNum} * #{cri.amount} -- 페이지 번호에 따라 수정;
]]>
</select>
<!--페이징 처리시 전체 글수 확인-->
<select id="getTotalCount" parameterType="Map" resultType="integer">
SELECT count(*)
FROM store
WHERE STORE_STAT = 1
<if test="cuisineSelect != 'none'">
and category1 = #{cuisineSelect}
</if>
<if test="cri.getKeyword != 'none'">
AND store.store_name LIKE '%' || #{cri.keyword} || '%'
</if>
</select>
'코딩 > Spring' 카테고리의 다른 글
스프링부트 타임리프 :: 등록 또는 수정 후에 메인 페이지에서 모달창 띄우기 (관리자의 승인 후 게시됩니다) (1) | 2023.06.11 |
---|---|
타임리프 select/option 에서 기존값 가져오게 하기(update 시) (0) | 2023.06.11 |
깔끔한 댓글창 (부트스트랩) (1) | 2023.06.09 |
스프링부트 iamport 이니시스 환불하기!! (0) | 2023.06.08 |
Day113_230608 스프링부트 iamport 이니시스 통합인증(토큰받기) (0) | 2023.06.08 |