보라코딩

스프링부트 페이징처리 + 별점순/인기순/거리순 + 검색기능 + join까지.. 본문

코딩/Spring

스프링부트 페이징처리 + 별점순/인기순/거리순 + 검색기능 + join까지..

new 보라 2023. 6. 10. 21:46

페이징처리가 되면 별점순 인기순 거리순이 안되고

반대가 되면 페이징이 안되는 고통에서 벗어났다!

 

 

드디어 해결했다...!

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>