프로젝트 Motivation - 게시물 검색( query dsl )

youngkyu MIn·2023년 11월 19일
0

우리의 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 이다.


오늘도 나는 참 친절한 사람이다! ㅎㅎ

profile
한 줄 소개

0개의 댓글