[Java+SpringBoot+JPA] 기본 CRUD 구현하기 (5) 검색 & 페이지 기능

2023. 12. 24. 20:20Pratice/CRUD

반응형

 

 

보통의 게시판은 글번호 역순으로 나열이 되기 때문에

내림차순 정렬과 함께 페이지 번호, 검색기능을 추가 구현해 보겠다.

 

 

 


 

 

🟢 Repository

  검색 조건을 추가해 준다.

@Repository
public interface ProductRepository extends JpaRepository <ProductEntity, Integer> {

  //상품명
  @Query("SELECT p FROM ProductEntity p WHERE p.productName LIKE %:keyword%")
  Page<ProductEntity> findByProductName(@Param("keyword") String keyword, Pageable pageable);

}
더보기

findAllBy 메서드

주어진 조건에 해당하는 모든 엔터티를 검색

반환 형식은 List이며, 조건에 해당하는 모든 결과를 리스트로 반환

 

 findBy 메서드

주어진 조건에 해당하는 첫 번째 엔터티를 검색

반환 형식은 단일 엔터티이며, 조건에 해당하는 첫 번째 결과만 반환

 

 

🟢 Service

  //Read 전체 조회
  public Page<ProductDTO> findAll(String type, String keyword, String categoryTypeRole, Pageable page) throws Exception {

    int currentPage = page.getPageNumber() - 1;
    int productLimit = 10;
    
    Pageable pageable = PageRequest.of(currentPage, productLimit, Sort.by(Sort.Direction.DESC, "productId"));

    //검색조건
    Page<ProductEntity> productEntityPage;

    if ("n".equals(type) && keyword != null && !keyword.isEmpty()) {
      productEntityPage = productRepository.findByProductName(keyword, pageable);
    } else if ("ca".equals(type) && categoryTypeRole != null && !categoryTypeRole.isEmpty()) {
      productEntityPage = productRepository.findByCategoryTypeRole(CategoryTypeRole.valueOf(categoryTypeRole), pageable);
    } else {
      productEntityPage = productRepository.findAll(pageable);
    }


    //stream : 각각의 ProductEntity를 ProductDTO로 변환하여 전달
    //map() 함수는 ModelMapper.map()을 사용하여 변환을 수행
    //변환된 요소들을 List<ProductDTO>로 수집
    List<ProductDTO> productDTOList = productEntityPage.stream()
        .map(productEntity -> modelMapper.map(productEntity, ProductDTO.class))
        .collect(Collectors.toList());

    //PageImpl을 사용하여 DTO 목록과 페이지 정보를 반환
    //새로운 PageImpl 객체에 변환된 ProductDTO들, 원래의 Pageable 정보, 그리고 원래 페이지의 총 요소 수를 담아서 전달
    return new PageImpl<>(productDTOList, pageable, productEntityPage.getTotalElements());

  }

 

 

 

🟢 Controller

  상품 목록 + 검색 + 페이지네이션

  @GetMapping("/productlist")
  public String productlist (@RequestParam(value = "type", defaultValue = "") String type,
                             @RequestParam(value = "keyword", defaultValue = "") String keyword,
                             @RequestParam(value = "categoryTypeRole", defaultValue = "") String categoryTypeRole,
                             @PageableDefault(page=1) Pageable pageable,
                             Model model) throws Exception {

    //상품 조회 및 페이지네이션
    Page<ProductDTO> productDTOS = productService.findAll(type, keyword, categoryTypeRole, pageable);

    int blockLimit = 5;
    int startPage = (((int)(Math.ceil((double) pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1;
    int endPage = Math.min(startPage + blockLimit - 1, productDTOS.getTotalPages());

    int prevPage = productDTOS.getNumber();
    int currentPage = productDTOS.getNumber() + 1;
    int nextPage = productDTOS.getNumber() + 2;
    int lastPage = productDTOS.getTotalPages();

    model.addAttribute("productDTOS", productDTOS);

    model.addAttribute("startPage", startPage);
    model.addAttribute("endPage", endPage);
    model.addAttribute("prevPage", prevPage);
    model.addAttribute("currentPage", currentPage);
    model.addAttribute("nextPage", nextPage);
    model.addAttribute("lastPage", lastPage);

    model.addAttribute("type", type);
    model.addAttribute("keyword", keyword);

    return "/product/list";

  }
더보기

@RequestParam:

  • value

매개변수의 이름

클라이언트가 전송한 요청 매개변수의 이름과 메서드 매개변수의 이름이 다를 때 사용

  • defaultValue

요청 매개변수가 제공되지 않은 경우 사용할 기본값을 설정

 

@PageableDefault

기본 페이지 값이 1인 Pageable 객체를 생성

 

 

 

🟢 list.html

  - 검색기능

<form th:action="@{/productlist}" method="get">
        <input type="hidden" name="page" value="1">
        <div class="input-group mb-3 mt-5">

          <!-- 대분류 -->
          <select class="form-select" name="type" id="searchType">
            <option value="" th:selected="${type == ''}">== 선택 ==</option>
            <option value="n" th:selected="${type == 'n'}">상품명</option>
            <option value="ca" th:selected="${type == 'ca'}">카테고리</option>
          </select>

          <!-- 중분류 (검색창) -->
          <input type="text" class="form-control" name="keyword" id="keyword" th:value="${keyword}">

          <!-- 중분류 (카테고리 유형) -->
          <select class="form-select" name="categoryTypeRole" id="categoryTypeRole">
            <option value="" th:selected="${categoryTypeRole == ''}"> == 선택 == </option>
            <option value="ALL" th:selected="${categoryTypeRole == 'ALL'}"> 전체 </option>
            <option value="FOOD" th:selected="${categoryTypeRole == 'FOOD'}"> 음식 </option>
            <option value="CLOTHES" th:selected="${categoryTypeRole == 'CLOTHES'}"> 의류 </option>
            <option value="BEAUTY" th:selected="${categoryTypeRole == 'BEAUTY'}"> 화장품 </option>
            <option value="ETC" th:selected="${categoryTypeRole == 'ETC'}"> 기타 </option>
          </select>

          <button type="submit" class="btn btn-primary" name="searchButton">검색</button>

          <button type="reset" class="btn btn-primary" name="resetButton" th:onclick="|location.href='@{/productlist}'|">다시</button>
          
        </div>
</form>

 

  - 검색기능 (Script)

<!-- JavaScript 코드 -->
      <script>

          // resetSearchForm : 검색 유형 드롭다운이 변경될 때 호출되며, 선택한 검색 유형에 따라 스타일을 업데이트
          document.getElementById('searchType').addEventListener('change', function () {
              resetSearchForm();
          });

          function resetSearchForm() {
              // 검색 폼 값 초기화
              document.getElementById('keyword').value = '';
              document.getElementById('categoryTypeRole').value = '';

              // localStorage에서 이전 선택 값을 제거
              localStorage.removeItem('selectedCategoryTypeRole');

              // applyStylesAfterSearch : 선택한 검색 유형 및 카테고리 유형에 따라 스타일을 적용
              applyStylesAfterSearch();
          }

          function applyStylesAfterSearch() {
              var selectedSearchType = document.getElementById('searchType').value;

              //toggleElementDisplay : 조건에 따라 요소의 표시 여부를 전환
              //'ca'가 선택되면 카테고리 유형을 선택하는 드롭다운 출력
              toggleElementDisplay('categoryTypeRole', selectedSearchType == 'ca');

              //'n'이 선택되면 상품명을 입력하는 텍스트 필드 출력
              toggleElementDisplay('keyword', selectedSearchType == 'n');

              //localStorage : 검색 값 유지
              if (selectedSearchType == 'ca') {
                  //초기화
                  var categoryTypeRoleElement = document.getElementById('categoryTypeRole');

                  if (categoryTypeRoleElement.value == '') {
                      // localStorage에서 이전 선택 값을 가져옴
                      categoryTypeRoleElement.value = localStorage.getItem('selectedCategoryTypeRole') || '';
                  }
              }
          }


          //검색창 또는 선택창 출력 (elementId에 해당하는 요소의 표시 여부를 condition에 따라 조절)
          function toggleElementDisplay(elementId, condition) {
              var element = document.getElementById(elementId);
              element.style.display = condition ? 'block' : 'none';
          }

          // 페이지 로드 시 초기 스타일 적용
          applyStylesAfterSearch();

          // 페이지 로드 시 localStorage에서 이전 선택 값을 가져와 적용
          window.onload = function () {
              applyStylesAfterSearch();
          }

          // 페이지 언로드 시 localStorage에 현재 선택 값을 저장
          window.onbeforeunload = function () {
              localStorage.setItem('selectedCategoryTypeRole', document.getElementById('categoryTypeRole').value);
          }

      </script>

 

 

 

 - 페이지네이션

        <div class="d-flex justify-content-center">
          <ul class="pagination">
            <li class="page-item" th:unless="${startPage == 1}">
              <a class="page-link" th:href="@{/productlist(type=${type}, keyword=${keyword}, categoryTypeRole=${categoryTypeRole}, page=1)}">처음</a>
            </li>
            <li class="page-item" th:unless="${currentPage == 1}">
              <a class="page-link" th:href="@{/productlist(type=${type}, keyword=${keyword}, categoryTypeRole=${categoryTypeRole}, page=${prevPage})}"> < </a>
            </li>
            <span th:each="page:${#numbers.sequence(startPage, endPage)}">
             <li class="page-item" th:unless="${page == currentPage}">
                 <a class="page-link" th:href="@{/productlist(type=${type}, keyword=${keyword}, categoryTypeRole=${categoryTypeRole}, page=${page})}">[[${page}]]</a>
             </li>
             <li class="page-item active" th:if="${page == currentPage}">
                 <a class="page-link" href="#">[[${page}]]</a>
             </li>
            </span>
            <li class="page-item" th:unless="${currentPage == lastPage}">
              <a class="page-link" th:href="@{/productlist(type=${type}, keyword=${keyword}, categoryTypeRole=${categoryTypeRole}, page=${nextPage})}"> > </a>
            </li>
            <li class="page-item" th:unless="${lastPage == endPage}">
              <a class="page-link" th:href="@{/productlist(type=${type}, keyword=${keyword}, categoryTypeRole=${categoryTypeRole}, page=${lastPage})}"> 끝 </a>
            </li>
          </ul>
        </div>

 

 

반응형