querydsl을 사용해 검색 기능 구현

강찬우·2024년 2월 2일

Query DSL Query Domain Specific Language의 약자로 도메인 + 특화 + 언어라는 개념으로, 오픈소스 프로젝트이며 JPQL을 JAVA코드로 작성할 수 있게 해준다. 쉽게 말하면 기존의 쿼리처럼 sql문법에 맞추어 작성하지 않고 jAVA코드로만 작성할수 있도록 도와주는 라이브러리 이다.

Qudry dsl 을 이용해서 구현한 검색 기능인데 기억하기보다는 기록해 두고 싶었다

package com.ll.edubridge.domain.post.post.repository;

import com.ll.edubridge.domain.course.course.entity.QCourse;
import com.ll.edubridge.domain.member.member.entity.Member;
import com.ll.edubridge.domain.post.post.entity.Post;
import com.ll.edubridge.domain.post.post.entity.QPost;
import com.ll.edubridge.standard.base.KwTypeV1;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.support.PageableExecutionUtils;

import java.util.List;

import static com.ll.edubridge.domain.course.course.entity.QCourse.course;
import static com.ll.edubridge.domain.post.post.entity.QPost.*;

@RequiredArgsConstructor
public class CustomPostRepositoryImpl implements CustomPostRepository{
    private final JPAQueryFactory queryFactory;

    @Override
    public Page<Post> findByKw(KwTypeV1 kwType, String kw, Member author, Boolean published, Pageable pageable) {
        BooleanBuilder builder = new BooleanBuilder();

        if (author != null) {
            builder.and(post.writer.eq(author));
        }

        if (published != null) {
            builder.and(post.published.eq(published));
        }

        if (kw != null && !kw.isBlank()) {
            applyKeywordFilter(kwType, kw, builder);
        }

        JPAQuery<Post> postsQuery = createPostsQuery(builder);
        applySorting(pageable, postsQuery);

        postsQuery.offset(pageable.getOffset()).limit(pageable.getPageSize());

        JPAQuery<Long> totalQuery = createTotalQuery(builder);

        return PageableExecutionUtils.getPage(postsQuery.fetch(), pageable, totalQuery::fetchOne);
    }



    private void applyKeywordFilter(KwTypeV1 kwType, String kw, BooleanBuilder builder) {
        switch (kwType) {
            case kwType.TITLE -> builder.and(post.title.containsIgnoreCase(kw));
            case kwType.BODY -> builder.and(post.content.containsIgnoreCase(kw));
            case kwType.NAME -> builder.and(post.writer.nickname.containsIgnoreCase(kw));
            default -> builder.and(
                    post.title.containsIgnoreCase(kw)
                            .or(post.content.containsIgnoreCase(kw))
                            .or(post.writer.nickname.containsIgnoreCase(kw))
            );
        }
    }

    private JPAQuery<Post> createPostsQuery(BooleanBuilder builder) {
        return queryFactory
                .select(post)
                .from(post)
                .where(builder);
    }

    private void applySorting(Pageable pageable, JPAQuery<Post> postsQuery) {
        for (Sort.Order o : pageable.getSort()) {
            PathBuilder pathBuilder = new PathBuilder(post.getType(), post.getMetadata());
            postsQuery.orderBy(new OrderSpecifier(o.isAscending() ? Order.ASC : Order.DESC, pathBuilder.get(o.getProperty())));
        }
    }

    private JPAQuery<Long> createTotalQuery(BooleanBuilder builder) {
        return queryFactory
                .select(post.count())
                .from(post)
                .where(builder);
    }

}

이 코드는 Java로 작성된 CustomPostRepositoryImpl라는 클래스이다. 이 클래스는 CustomPostRepository 인터페이스를 구현하고 있고, JPAQueryFactory를 사용하여 데이터베이스로부터 Post 객체를 검색하는 메서드들을 제공한다.

  1. findByKw 메서드:

    • 이 메서드는 주어진 조건에 따라 Post 객체의 페이지를 반환한다.
    • 파라미터로는 KwTypeV1 kwType, String kw, Member author, Boolean published, Pageable pageable이 있다.
    • BooleanBuilder를 사용하여 조건부 쿼리를 구성합니다.
    • 필터링 조건은 글쓴이(author), 게시 여부(published), 키워드(kw)를 기반으로 한다.
    • 키워드 필터링은 applyKeywordFilter 메서드를 통해 처리되며, KwTypeV1에 따라 제목, 본문, 작성자 이름 중에서 검색할 수 있다.
    • 페이징 및 정렬은 Pageable 객체를 사용하여 적용된다.
  2. applyKeywordFilter 메서드:

    • 이 메서드는 검색 키워드와 KwTypeV1에 따라 쿼리에 조건을 추가한다.
    • KwTypeV1에 따라 제목, 본문, 작성자의 닉네임 필드에서 키워드를 검색할 수 있다.
  3. createPostsQuery 및 createTotalQuery 메서드:

    • createPostsQuery는 주어진 조건(BooleanBuilder)을 사용하여 Post 객체를 검색하는 JPAQuery를 생성한다.
    • createTotalQuery는 총 결과 수를 계산하는 쿼리를 생성한다.
  4. applySorting 메서드:

    • 이 메서드는 Pageable 객체에서 정렬 조건을 추출하여 쿼리에 적용한다.
  5. JPAQueryFactory:

    • queryFactory 필드는 JPA 쿼리를 동적으로 생성하는 데 사용된다.

이 코드를 작성하면서 BooleanBuilder에 대해 더 공부해 봤다.

BooleanBuilder

BooleanBuilder는 Querydsl 라이브러리의 일부로, 동적으로 JPQL (Java Persistence Query Language) 또는 SQL 쿼리의 WHERE 조건을 구성하는 데 사용된다. 이 클래스는 복잡한 조건을 논리적으로 조합할 때 유용하다. 여기에는 기본적인 and, or, not 같은 논리 연산자를 사용할 수 있다.

주요 특징 및 사용 방법

  1. 조건의 동적 구성:

    • BooleanBuilder는 쿼리의 조건을 조건부로 구성하는 데 사용된다. 예를 들어, 사용자 입력에 따라 다른 검색 조건을 적용해야 할 때 유용하다.
  2. 체이닝 방식:

    • BooleanBuilder는 메서드 체이닝을 지원한다. 즉, .and(), .or() 같은 메서드를 연속해서 호출하여 여러 조건을 쉽게 결합할 수 있다.
  3. Predicate 인터페이스:

    • BooleanBuilderPredicate 인터페이스를 구현한다. 따라서 Querydsl의 다른 API와 쉽게 결합하여 사용할 수 있다.
  4. 예제 사용:

    BooleanBuilder builder = new BooleanBuilder();
    if (someCondition) {
        builder.and(someEntity.someField.eq(someValue));
    }
    if (anotherCondition) {
        builder.or(anotherEntity.anotherField.eq(anotherValue));
    }

적용 예

예를 들어, 사용자가 여러 필터 옵션을 선택할 수 있는 검색 시스템에서 BooleanBuilder를 사용하여 쿼리를 구성할 수 있다. 사용자가 선택한 각 옵션에 따라 .and() 또는 .or()를 사용하여 조건을 추가하고, 최종적으로 구성된 BooleanBuilder 객체를 쿼리의 WHERE 절에 적용한다.

주의 사항

  • BooleanBuilder는 초기 상태가 null과 같다. 즉, 아무런 조건도 추가되지 않으면, 이는 "모든 조건에 대해 참"으로 인식한다.
  • 쿼리의 성능과 가독성을 유지하기 위해 필요한 조건만 추가하는 것이 중요하다.

0개의 댓글