검색과 페이징 동시 적용을 위한 api를 작성해야한다.
검색은 queryDsl을 이용해서 동적으로 구현 할 것이다.
QueryDsl을 위해서는 우선 의존성을 gradle에서 설치하여야한다.
// Query Dsl
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
그 후 querydsl을 통한 페이징을 처리하기 위한 컨트롤러와 서비스를 만든다.
레파지스터리에 QuerydslPredicateExecutor을 상속하여 백엔드 api를 마무리한다.
@Repository
public interface UserRepository extends JpaRepository<User, Long>, QuerydslPredicateExecutor<User> {
Optional<User> findByUsername(String username);
Page<User> findAll(Pageable pageable);
}
검색을 위한 html요소를 작성한다.
<div class="search-form mb-4" style="max-width: 600px; margin: 0 auto;">
<form th:action="@{/admin/users/v2}" method="get" class="row">
<div class="col-4 mb-2">
<input type="text" class="form-control" id="userNameSearch" name="userName"
placeholder="사용자 이름 검색">
</div>
<div class="col-4 mb-2">
<select class="form-control" id="userRoleSearch" name="userRole">
<option value="">사용자 역할 선택</option>
<option value="ADMIN">ADMIN</option>
<option value="USER">USER</option>
</select>
</div>
<div class="col-4 mb-2">
<button type="submit" class="btn btn-primary w-100">검색</button>
</div>
</form>
</div>
그리고 페이지 네비게이션을 클릭했을때 가져가는 파라미터에 검색 정보 역시 추가한다.
<div>
<ul class="pagination">
<!-- 현재 페이지 번호가 10 이상일 경우, 이전 페이지 그룹의 첫 번째 페이지로 이동 -->
<li class="page-item" th:if="${(currentPage / 10 ) * 10 > 0}">
<a class="page-link"
th:href="@{/admin/users/v2(page=${(currentPage / 10 - 1) * 10} , userName=${param.userName}, userRole=${param.userRole})}"
aria-label="Go to the first page of the next group">
<span aria-hidden="true">«</span>
</a>
</li>
<!-- 현재 페이지 그룹의 페이지 번호들을 표시. 현재 페이지 번호 기준으로 10개 페이지를 표시 -->
<li th:each="pageNumber : ${#numbers.sequence((currentPage / 10) * 10, (currentPage / 10) * 10 + 9 )}"
th:if="${pageNumber < totalPages}"
class="page-item"
th:class="${pageNumber == currentPage ? 'active' : ''}">
<a class="page-link"
th:href="@{/admin/users/v2(page=${pageNumber} , userName=${param.userName}, userRole=${param.userRole})}"
th:text="${pageNumber + 1}"></a>
</li>
<!-- 현재 페이지 그룹의 마지막 페이지가 전체 페이지 수보다 작을 경우, 다음 페이지 그룹의 첫 번째 페이지로 이동 -->
<li class="page-item" th:if="${(currentPage / 10 + 1) * 10 < totalPages}">
<a class="page-link"
th:href="@{/admin/users/v2(page=${(currentPage / 10 + 1) * 10} , userName=${param.userName}, userRole=${param.userRole})}"
aria-label="Go to the first page of the next group">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</div>
검색이 잘 되는 것을 확인할 수 있다.
이 페이지는 페이징이 2중으로 되어있다.
이벤트, 해당 이벤트의 도서들 이렇게 말이다.
따라서 하나씩 순차적으로 기능을 넣는것이 합리적이다.
우선 도서 검색 기능 부터 시작하겠다.
bookService.findBookByNameAndRoleAndDonationIdWithPagination(
bookName,author,publish,status,
bookDonationEventPageResponseDtoV3.getBookDonationEventResponseDtoV3().get(i).getDonationId(),
bookPageRequest
@Service
@RequiredArgsConstructor
public class BookService {
private final BookRepository bookRepository;
public Page<Book> findBookByNameAndRoleAndDonationIdWithPagination(String bookName, String author, String publish, String status,
Long donationId, Pageable pageable) {
QBook qBook = QBook.book;
BooleanBuilder builder = new BooleanBuilder();
builder.and(qBook.bookDonationEvent.donationId.eq(donationId));
if(!bookName.isEmpty())
builder.and(qBook.bookName.contains(bookName));
if(!author.isEmpty())
builder.and(qBook.bookAuthor.contains(author));
if(!publish.isEmpty())
builder.and(qBook.bookPublish.contains(publish));
if(!status.isEmpty())
builder.and(qBook.bookStatus.eq(BookStatusEnum.valueOf(status)));
return bookRepository.findAll(builder,pageable);
}
}
public interface BookRepository extends JpaRepository <Book,Long>, QuerydslPredicateExecutor<Book> {
우선 책과 이벤트 검색을 위한 검색 요소들을 만든다.
<div class="search-form mb-4" style="max-width: 1200px; margin: 0 auto;">
<form th:action="@{/admin/donation/v4}" method="get" class="row">
<div class="col-2 mb-2">
<input type="text" class="form-control" id="eventIdSearch" name="eventId"
placeholder="Event Id">
</div>
<div class="col-2 mb-2">
<input type="text" class="form-control" id="bookNameSearch" name="bookName"
placeholder="book Name">
</div>
<div class="col-2 mb-2">
<input type="text" class="form-control" id="authorSearch" name="author"
placeholder="Author">
</div>
<div class="col-2 mb-2">
<input type="text" class="form-control" id="publishSearch" name="publish"
placeholder="Publisher">
</div>
<div class="col-2 mb-2">
<select class="form-control" id="status" name="status">
<option value="">Status</option>
<option value="SOLD_OUT">SOLD_OUT</option>
<option value="DONATION">DONATION</option>
</select>
</div>
<div class="col-2 mb-2">
<button type="submit" class="btn btn-primary w-100">검색</button>
</div>
</form>
</div>
검색하거나 페이징을 하였을때 검색 정보들을 다 가져가도록 한다.
<div>
<ul class="pagination">
<!-- 현재 페이지 번호가 10 이상일 경우, 이전 페이지 그룹의 첫 번째 페이지로 이동 -->
<li class="page-item"
th:if="${(currentBookPage[eventStatus.index] / 10 ) * 10 > 0}">
<a class="page-link"
th:onclick="|modifyArrayAndRedirect(${eventStatus.index}, ${(currentBookPage[eventStatus.index] / 10 - 1) * 10},
${currentEventPage}, '${eventId}', '${bookName}', '${author}', '${publish}', '${status}')|"
aria-label="Go to the first page of the next group">
<span aria-hidden="true">«</span>
</a>
</li>
<li th:each="pageNumber : ${#numbers.sequence((currentBookPage[eventStatus.index] / 10) * 10, (currentBookPage[eventStatus.index] / 10) * 10 + 9)}"
th:if="${pageNumber < totalPages}"
class="page-item"
th:class="${pageNumber == currentBookPage[eventStatus.index] ? 'active' : ''}">
<a class="page-link"
th:onclick="|modifyArrayAndRedirect(${eventStatus.index}, ${pageNumber}, ${currentEventPage}, '${eventId}', '${bookName}', '${author}', '${publish}', '${status}')|"
th:text="${pageNumber + 1}"></a>
</li>
<!-- 현재 페이지 그룹의 마지막 페이지가 전체 페이지 수보다 작을 경우, 다음 페이지 그룹의 첫 번째 페이지로 이동 -->
<li class="page-item"
th:if="${(currentBookPage[eventStatus.index] / 10 + 1) * 10 < totalPages}">
<a class="page-link"
th:onclick="|modifyArrayAndRedirect(${eventStatus.index}, ${(currentBookPage[eventStatus.index] / 10 + 1) * 10}, ${currentEventPage}, '${eventId}', '${bookName}', '${author}', '${publish}', '${status}')|"
aria-label="Go to the first page of the next group">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</div>
function modifyArrayAndRedirect(eventIndex, newValue, currentEventPage, eventId, bookName, author, publish, status) {
currentBookPage[eventIndex] = newValue;
var updatedBookPageString = currentBookPage.join(',');
var newUrl = '/admin/donation/v4?bookPage=' + updatedBookPageString + '&eventPage=' + currentEventPage + '&eventId=' + eventId + '&bookName=' + bookName + '&author=' + author + '&publish=' + publish + '&status=' + status;
window.location.href = newUrl;
}
트러블 슈팅
검색 버튼을 눌렀을때 검색 필드의 값이 초기화 되어 이후 다른 페이지를 누르면 null값으로 전송되는 문제 발생