[게시판 프로젝트] API 구현(2) - Querydsl을 통해서 세부 검색 기능 구현

2024. 8. 16. 00:29CS/Spring

Querydsl을 통해서, Spring DATA REST로 구현할 수 없었던
좀 더 복잡하고 상세한 검색 기능을 구현합니다.




먼저, Querydsl을 사용하기 위한 설정을 해줍니다.

 

Querydsl 관련 플러그인
 

Gradle - Plugin: com.ewerk.gradle.plugins.querydsl

Version 1.0.10 (latest) 1.0.10 Created 02 July 2018. Plugin for generating QueryDSL model files. plugins { id("com.ewerk.gradle.plugins.querydsl") version "1.0.10" } buildscript { repositories { maven { url = uri("https://plugins.gradle.org/m2/") } } depen

plugins.gradle.org

  • Qclasss 자동생성이라 gitignore 설정해 주어야 한다.

Querydsl 의존성 추가 - springboot3.x.x
// Querydsl 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
implementation 'com.querydsl:querydsl-core'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
//뷰 dependencies 추가
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
//사용자 커스텀 properties 도 문세세팅 가능 + IDE 지원
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
tasks.named('test') {
    useJUnitPlatform()
}


// Querydsl 설정부
def generated = 'src/main/generated'

// querydsl QClass 파일 생성 위치를 지정
tasks.withType(JavaCompile) {
    options.getGeneratedSourceOutputDirectory().set(file(generated))
}

// java source set 에 querydsl QClass 위치 추가
sourceSets {
    main.java.srcDirs += [ generated ]
}

// gradle clean 시에 QClass 디렉토리 삭제
clean {
    delete file(generated)
}

 

 

설정이 끝났으면,
레포지토리에 기본 검색에 대한 설정을 세팅한 다음
@Override를 통해서 검색에 대한 세부 내용을 구현합니다.

 

repository 설정
@RepositoryRestResource
public interface ArticleRepository extends
        JpaRepository<Article, Long>,
        QuerydslPredicateExecutor<Article>,
        QuerydslBinderCustomizer<QArticle>
{
    @Override //검색에 대한 세부 내용 구현
    default void customize(QuerydslBindings bindings, QArticle root){
        bindings.excludeUnlistedProperties(true);
        bindings.including(root.title,root.content,root.hashtag,root.createdAt,root.createdBy);
        bindings.bind(root.title).first(StringExpression::containsIgnoreCase);//쿼리문 : like '%${v}'
        //bindings.bind(root.title).first(StringExpression::likeIgnoreCase);//쿼리문 : like '${v}'
        bindings.bind(root.content).first(StringExpression::containsIgnoreCase);
        bindings.bind(root.hashtag).first(StringExpression::containsIgnoreCase);
        bindings.bind(root.createdAt).first(DateTimeExpression::eq); // 동일 검사
        bindings.bind(root.createdBy).first(StringExpression::containsIgnoreCase);
    }
}
@RepositoryRestResource
public interface ArticleCommentRepository extends
        JpaRepository<ArticleComment, Long>,
        QuerydslPredicateExecutor<ArticleComment>,
        QuerydslBinderCustomizer<QArticleComment> {
    @Override //검색에 대한 세부 내용 구현
    default void customize(QuerydslBindings bindings, QArticleComment root) {
        bindings.excludeUnlistedProperties(true);
        bindings.including(root.content, root.createdAt, root.createdBy);
        bindings.bind(root.content).first(StringExpression::containsIgnoreCase);
        bindings.bind(root.createdAt).first(DateTimeExpression::eq); // 동일 검사
        bindings.bind(root.createdBy).first(StringExpression::containsIgnoreCase);
    }

}

 


뷰에서 Querydsl 연관 부분과 매커니즘
<form id="search-form">
    <div class="row">
        <div class="col-12">
            <div class="row no-gutters">
                <div class="col-lg-3 col-md-3 col-sm-12 p-0">
                    <label for="search-type" hidden>검색 유형</label>
                    <select class="form-control" id="search-type" name="searchType">
                        <option>제목</option>
                        <option>본문</option>
                        <option>id</option>
                        <option>닉네임</option>
                        <option>해시태그</option>
                    </select>
                </div>
                <div class="col-lg-8 col-md-6 col-sm-12 p-0">
                    <label for="search-value" hidden>검색어</label>
                    <input type="text" placeholder="검색어..." class="form-control" id="search-value" name="searchValue">
                </div>
                <div class="col-lg-1 col-md-3 col-sm-12 p-0">
                    <button type="submit" class="btn btn-base">
                        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search">
                            <circle cx="11" cy="11" r="8"></circle>
                            <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
                        </svg>
                    </button>
                </div>
            </div>
        </div>
    </div>
</form>

 

1. 검색 폼
<select id="search-type" name="searchType">:
  • 사용자가 검색하려는 유형을 선택할 수 있습니다(예: 제목, 본문, ID, 닉네임, 해시태그).
  • 이 값은 searchType이라는 이름으로 서버로 전송됩니다.
<input type="text" id="search-value" name="searchValue">
  • 사용자가 입력한 검색어입니다.
  • 이 값은 searchValue라는 이름으로 서버로 전송됩니다.
<button type="submit">
  • 검색 폼을 제출합니다. 사용자가 검색 유형과 검색어를 입력하고 이 버튼을 클릭하면, 폼에 입력된 데이터가 서버로 전송됩니다.

 

2. QueryDSL과의 연관성
  • 사용자가 폼에 입력한 searchType과 searchValue가 서버로 전송되면, 백엔드에서 이 데이터를 받아 QueryDSL을 사용하여 데이터베이스에서 필요한 게시글을 검색합니다.
  • 이 검색 작업은 ArticleRepository와 ArticleCommentRepository의 customize 메소드에서 정의된 QueryDSL 바인딩 설정과 연관이 있습니다. 예를 들어, 사용자가 "제목"으로 검색을 선택하고 검색어를 입력하면, QueryDSL이 이 조건을 사용하여 제목에 검색어가 포함된 게시글을 찾습니다.

 

3. 서버 측에서의 처리
  • 서버 측 코드, Repository의 QueryDSL 설정이 실제로 검색 작업을 처리합니다.
  • 이 설정은 사용자가 선택한 검색 유형과 검색어에 따라 동적으로 쿼리를 생성하여 데이터베이스에서 결과를 가져오는 역할을 합니다.