우리의 Motivation 도 갈수록 게시물이 늘어나겠지?
사용자가 쉽게 원하는 게시물을 찾을 수 있도록 검색기능을 넣어주자.
query dsl 을 사용하여 제목, 내용, 작성자 또는 이 전부를 대상으로 하는 검색 필터를 만들어보자
<dialog id="searchFormModal" class="modal">
<div class="modal-box">
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
</form>
<form class="bg-base rounded flex flex-col gap-6" onsubmit="submitSearchForm(this); return false;">
<div class="form-control">
<label class="label">
<span class="label-text">검색필터</span>
</label>
<select name="kwType" class="select select-bordered" th:value="${param.kwType}">
<option value="all">전체</option>
<option value="subject">제목</option>
<option value="body">내용</option>
<option value="nickname">별명</option>
</select>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">검색</span>
</label>
<input placeholder="검색어"
class="input input-bordered ws-full"
name="kw"
type="search"
th:value="${param.kw}"
>
</div>
<div>
<button class="btn btn-block btn-primary gap-1">
<i class="fa-solid fa-magnifying-glass"></i>
<span>검색</span>
</button>
</div>
</form>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
귀여운 검색창을 하나 만들어 보았다.
모바일 웹 플렛폼도 지원하고자 하는데...
검색을 위한 부분이 작은 화면에서 보니 영 거슬렸다. 그래서 검색을 누르면 모달이 뜨게 한번 만들어 보았다.
근데 또 만들고보니 검색하는 과정이 좀 귀찮아 진 것 같기도 하고... 고민을 좀 더 해봐야겠다.
@GetMapping("/{boardCode}/list")
public String showList(
Model model,
@PathVariable String boardCode,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "") String kw,
@RequestParam(defaultValue = "all") String kwType
) {
Board board = boardService.findByCode(boardCode).get();
model.addAttribute("board", board);
List<Sort.Order> sorts = new ArrayList<>();
sorts.add(Sort.Order.desc("id"));
Pageable pageable = PageRequest.of(page - 1, 10, Sort.by(sorts));
Page<Article> articlePage = articleService.findByKw(board, kwType, kw, pageable);
model.addAttribute("articlePage", articlePage);
return "usr/article/list";
}
요기는 Controller 다. 사용자가 고른 필터 대상과 검색어를 받고, Pageable 객체 까지 만들어 서비스로 출발한다. default 를 통해 초기로드시에는 모든 리스트를 불러오게 했다.
@Override
public Page<Article> findByKw(Board board, String kwType, String kw, Pageable pageable) {
BooleanBuilder builder = new BooleanBuilder();
builder.and(article.board.eq(board));
switch (kwType) {
case "subject" -> builder.and(article.subject.containsIgnoreCase(kw));
case "body" -> builder.and(article.body.containsIgnoreCase(kw));
case "nickname" -> builder.and(article.author.nickname.containsIgnoreCase(kw));
default -> builder.and(
article.subject.containsIgnoreCase(kw)
.or(article.body.containsIgnoreCase(kw))
.or(article.author.nickname.containsIgnoreCase(kw))
);
}
JPAQuery<Article> articlesQuery = jpaQueryFactory
.selectDistinct(article)
.from(article)
.where(builder);
for (Sort.Order o : pageable.getSort()) {
PathBuilder pathBuilder = new PathBuilder(article.getType(), article.getMetadata());
articlesQuery.orderBy(new OrderSpecifier(o.isAscending() ? Order.ASC : Order.DESC, pathBuilder.get(o.getProperty())));
}
articlesQuery.offset(pageable.getOffset()).limit(pageable.getPageSize());
JPAQuery<Long> totalQuery = jpaQueryFactory
.select(article.count())
.from(article)
.where(builder);
return PageableExecutionUtils.getPage(articlesQuery.fetch(), pageable, totalQuery::fetchOne);
}
이곳은 Repository 다.
지난 프로젝트에서는 Boolean Expression 을 활용해 동적쿼리를 작성했다. 이번 프로젝트에서 검색은 비교적 가볍다. Boolean Expression 으로 하기엔 가독성만 떨어지고 복잡해질 것 같아 그냥 switch 문을 통해 간단하게 만들어 보았다.
PageableExecutionUtils.getPage 를 통해 Page 객체로 맹글어 return 해준다.
짜잔! 검색은 잘 되고있다.
<button class="btn btn-sm btn-primary" onclick="searchFormModal.showModal()">
<i class="fa-solid fa-magnifying-glass"></i>
검색어
<span th:if="${UtThy.hasText(param.kw)}">`<span class="normal-case"
th:text="${param.kw}"></span>`</span>
</button>
<a th:if="${UtThy.hasText(param.kw)}" th:href="${@rq.currentUrlPath}"
class="btn btn-sm btn-outline">
<i class="fa-solid fa-xmark"></i>
검색조건 지우기
</a>
사용자의 편의를 위해 요론 버튼들도 만들어줬다! rq.currentUrlPath 는 url 의 파라미터를 날려버린 url 이다.
오늘도 나는 참 친절한 사람이다! ㅎㅎ