[Debugging Log] MyBatis 동적쿼리를 이용해 쇼핑몰 상품 리스트 페이지 (카테고리-정렬-페이징) 구현

이의준·2024년 8월 17일

Debugging Log

목록 보기
2/6
post-thumbnail

배경

스프링 부트와 mybatis를 이용하는 쇼핑몰 토이 팀 프로젝트 진행 중 나는 상품 영역을 담당하게 되었다.
그중 상품 리스트 페이지에서의 기능들 중 상품 카테고리를 선택하고 정렬하고 페이지를 넘어가는 것에 대한 구현이 필요했다.
사진으로 보면 다음과 같다.

왼쪽 사이드바에서 카테고리를 선택하고 정렬 기준을 고르고 정렬 버튼을 누른 후 페이징까지 호환이 되도록 동작해야 하는 것이었다.

프로젝트라는 것도 처음이고 어떻게 손을 대야 하는지 감도 오지 않아서 일단 실습 때 배운 대로 쿼리부터 작성했다.

내가 작성한 쿼리는 다음과 같다.

  • 카테고리에 따라 상품 리스트 반환
  • 모든 정렬 기준에 대한 각각의 리스트 반환 (총 6가지)
  • 페이지 기준에 따른 상품 리스트 반환

문제 상황

쿼리를 전부 작성하고 서비스 단계에서 세 조건을 모두 충족하는 페이지를 띄우고자 하는데 문제가 생겼다.
총 8가지에 해당하는 각각의 쿼리들은 모두 상품 dto 리스트를 반환하고 있고 모든 쿼리는 select * from product로 시작한다.
입력 없이 리스트를 반환하기만 하는 쿼리들을 통해 카테고리, 정렬, 페이징 기준을 모두 충족하는 상품 리스트를 반환할 방법이 없었다.

결국 쿼리를 수정해가며 서비스 단계에서 switch-case 문을 통해 접근을 해보고자 했다.


남아있는 그 때의 흔적.....

이렇게까지 하긴 했는데 이 메서드는 정렬이 된 페이지의 리스트를 반환할 뿐 카테고리 필터링은 할 수 없었다. (정렬 기준을 선택하거나 페이지를 넘기면 카테고리 정보를 잃어버린다.)


문제 해결

이 상황을 어떻게 해결해야 할까 한참 고민하던 중에 동기분이 동적쿼리에 대해 설명해주셨다. 나는 동적 쿼리라는 단어조차 처음 들어봤는데 이게 my-batis를 사용하는 유일한 이유라며 링크를 공유해주셨다.

https://mybatis.org/mybatis-3/ko/dynamic-sql.html

공유받은 공식 사이트를 보면서 공부를 조금 해보니까 어떻게 문제를 해결해야 할지 감이 오기 시작했다.

먼저 이 카테고리, 정렬, 페이징 이 세 요소의 관계를 정리해야 했다.
내가 생각한 바는 다음과 같다.

  1. 카테고리 선택 : 정렬기준 초기화, 1페이지
  2. 정렬 버튼 : 카테고리 유지, 정렬 셀렉트 옵션 유지, 1페이지
  3. 페이지 버튼 : 카테고리 유지, 정렬 셀렉트 옵션 유지, 페이지 넘어가기

이후 다음과 같은 절차를 통해 문제를 해결해나갔다.

  1. 동적 쿼리를 이용해 쿼리 하나로 카테고리, 정렬, 페이징 조건을 모두 만족하는 상품 리스트를 추출한다.
    <select id="getFilteredAndSortedPage" parameterType="map" resultType="com.no1.book.domain.product.ProductDto">
        SELECT *
        FROM product
        WHERE 1=1
        <if test="cateKey != null and cateKey != ''">
            AND cate_code LIKE CONCAT(#{cateKey}, '%')
        </if>
        ORDER BY
        <choose>
            <when test="sortKey == 'price'">
                prod_base_price
            </when>
            <when test="sortKey == 'sales'">
                total_sales
            </when>
            <otherwise>
                reg_date
            </otherwise>
        </choose>
        <choose>
            <when test="sortOrder == 'desc'">
                DESC
            </when>
            <otherwise>
                ASC
            </otherwise>
        </choose>
        ,prod_name asc
        LIMIT #{offset}, #{pageSize}
    </select>

  1. 정렬 버튼을 누를 시 폼 태그의 히든 인풋을 이용하여 카테고리 정보를 저장한다.
    <form th:action="@{/product/list}" method="get" class="sort-form">
        <input type="hidden" name="cateKey" th:value="${cateKey}"/>
        <div class="sort-options">
            <select name="sortKey">
                <option value="date" th:selected="${sortKey == 'date'}">등록 날짜</option>
                <option value="sales" th:selected="${sortKey == 'sales'}">판매량</option>
                <option value="price" th:selected="${sortKey == 'price'}">가격</option>
            </select>
            <select name="sortOrder">
                <option value="desc" th:selected="${sortOrder == 'desc'}"></option>
                <option value="asc" th:selected="${sortOrder == 'asc'}"></option>
            </select>
            <button type="submit" class="btn btn-primary">정렬</button>
        </div>
    </form>

  1. 페이지 링크에 카테고리 키와 정렬 기준을 붙인다.
        <a th:href="@{/product/list(cateKey=${cateKey}, sortKey=${sortKey}, sortOrder=${sortOrder}, page=${i}, pageSize=${ph.pageSize})}"
           th:text="${i}" th:classappend="${ph.page == i} ? 'active' : ''"></a>

이러한 과정으로 카테고리-정렬-페이징 조건을 만족하는 상품 리스트를 반환할 수 있게 되었다.


후기

삽질하는 시간이 너무 많았어서 시간이 너무 아깝다고 동기 형한테 징징거렸는데, 오히려 많은 시간 끝에 문제를 해결했으니 이 방법은 앞으로 절대 잊어버리지 않을거라는 말을 해주셨다.
앞으로도 문제를 직면하고 시간을 쏟을때마다 원리를 알고 기록해서 오래오래 써먹도록 해야겠다.



0개의 댓글