[Java+SpringBoot+JPA] 기본 CRUD 구현하기 (5) 검색 & 페이지 기능
2023. 12. 24. 20:20ㆍPratice/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>
반응형
'Pratice > CRUD' 카테고리의 다른 글
[Java+SpringBoot+JPA] To do List 게시판 만들기 (시작일/마감일 설정) (1) | 2024.01.09 |
---|---|
[Java+SpringBoot+JPA] 기본 CRUD 구현하기 (6) 조회수 증가 기능 (0) | 2024.01.02 |
[Java+SpringBoot+JPA] 기본 CRUD 구현하기 (4) enum으로 카테고리 추가 (0) | 2023.12.24 |
[Java+SpringBoot+JPA] 기본 CRUD 구현하기 (3-1) required로 유효성 검사 (0) | 2023.12.24 |
[Java+SpringBoot+JPA] 기본 CRUD 구현하기 (3)검증 오류 추가 (0) | 2023.12.24 |