상똥이의 Back-End 공부방

[Board Project] 12. API 구현 (Query DSL, Spring data, JPA) 본문

프로젝트/게시판 만들기

[Board Project] 12. API 구현 (Query DSL, Spring data, JPA)

상똥백 2023. 10. 21. 01:08

목차

1. query DSL을 사용해 필터 검색 기능 구현

 

[0. 현재 상황]

- 단순 데이터 서빙만 가능한 상황

- 필터 검색 불가능

- 즉, 구체적 검색 불가능하므로 이번에 구현할 것

 

[1. Query DSL 연결]

1. dependencies 내부 의존성 주입

(1) 필수 요소

implementation "com.querydsl:querydsl-jpa" //필수
implementation "com.querydsl:querydsl-core" //필수

annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}"
//버전을 자동으로 인식해줌

(2) Query DSL에 대응하기 위한 요소

implementation "com.querydsl:querydsl-collections"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

 

2.dependencies 외부 주입

(1) 주입 이유

- Query DSL은 자동으로 클래스를 생성하는 기능(Q-class)을 가지는데, 빌드 디렉토리 안에 들어가는 것을 밖으로꺼내기 위함

- 밖으로 꺼내는 이유 : 빌드할 때 발생할 잠재적인 문제 해결

- 잠재적인 문제 : gradle 빌드하며 스캔했던 영역과 intelliJ가 스캔하는 영역이 겹치면서 중복 발생

- 중복 = 충돌이므로 이 충돌문제를 해결하기 위해 Q-class의 위치를 강제로 옮긴 것

- 즉 없어도 되지만 intelliJ환경의 안정성을 위함

 

 

def generated = 'src/main/generated'

tasks.withType(JavaCompile) {
	options.getGeneratedSourceOutputDirectory().set(file(generated))
}

sourceSets {
	main.java.srcDirs += [generated]
}

clean {
	delete file(generated)
}

 

[2. API 검색 기능 넣기]

1. extends QuerydslPredicateExequtor<테이블_클래스명>, QuerydslBinderCustomizer<Q테이블_클래스명>

(1) QuerydslPredicateExequtor<테이블_클래스명>

- 기본적으로 모든 필드에 대한 기본 검색 기능을 추가해줌

- exact match 검색만 가능 : 대소문자 구분은 안하지만, 부분 검색은 불가능함 (Ex. 키워드 검색이 불가능)

(2) QuerydslBinderCustomizer<Q테이블_클래스명>

- 오버라이드를 통해 구현된 내용을 토대로 검색에 대한 세부적인 규칙이 구성됨

- 오버라이드를 직접 구현하지 않고 JPA가 가능하게 해주기 때문에 default 메소드 사용

 

2. @Override -> customize

(1) bindings.excludeUnlistedProperties()

- PredicateExecutor에 의해 모든 필드들에 대한 검색이 열려있는데, 필터링을 걸어주는 역할

- 기본값이 false

(2) bindings.including() : 원하는 검색 조건을 설정할 수 있게 해줌, 괄호 안에 root.인덱스를 한 개 이상씩 넣어서 설정

(3) bindings.bind() : root.t인덱스를 하나씩 넣어 exact match검색 조건을 바꿀 수 있음

(4) first() : bindings.bind()에 붙여서 사용, 검색 타입을 설정할 수 있음

(5) StringExpression:: : 문자열 검색

- LikeIgnoreCase : 문자열이 다른 문자열 내에 순서에 상관 없이 포함되어있으면 검색됨

- containsIgnoreCase : 문자열이 다른 문자열 내에 순서대로 포함되어있으면 검색됨, 와일드카드(%) 사용

(6) DateTimeExpression:: : 날짜와 시간 검색

- eq : 정확히 같은 내용을 검색 (*잠시만 이대로 해놓고 후에 수정)

 

 

[3. API 검색]

1. Article에서 내용(content) 검색

- 검색할 키워드 : Vivamus metus

검색 방법 키워드 그대로 대소문자 바꿔서 순서 바꿔서
검색 문자 http://localhost:8080/api/articles?content=Vivamus%20metus http://localhost:8080/api/articles?content=vivAmus%20mETus http://localhost:8080/api/articles?content=metus%20Vivamus
결과 검색결과 같음 검색결과 같음 검색결과 다름

 

2. ArticleComments에서 내용(content) 검색

- 검색할 키워드 : pellentesque ultrices

검색 방법 키워드 그대로 대소문자 바꿔서 순서 바꿔서
검색 문자 http://localhost:8080/api/articleComments?content=pellentesque%20ultrices http://localhost:8080/api/articleComments?content=PeLLentesque%20ultriceS http://localhost:8080/api/articleComments?content=ultrices%20pellentesque
결과 검색결과 같음 검색결과 같음 검색결과 다름

 

 

더보기
package com.fastcampus.projectboard.repository;

import com.fastcampus.projectboard.domain.Article;
import com.fastcampus.projectboard.domain.QArticle;
import com.querydsl.core.types.dsl.DateTimeExpression;
import com.querydsl.core.types.dsl.StringExpression;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
import org.springframework.data.querydsl.binding.QuerydslBindings;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@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);
        bindings.bind(root.content).first(StringExpression::containsIgnoreCase);
        bindings.bind(root.hashtag).first(StringExpression::containsIgnoreCase);
        bindings.bind(root.createdBy).first(StringExpression::containsIgnoreCase);
        bindings.bind(root.createdAt).first(DateTimeExpression::eq);
    }
}
package com.fastcampus.projectboard.repository;

import com.fastcampus.projectboard.domain.ArticleComment;
import com.fastcampus.projectboard.domain.QArticleComment;
import com.querydsl.core.types.dsl.DateTimeExpression;
import com.querydsl.core.types.dsl.StringExpression;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
import org.springframework.data.querydsl.binding.QuerydslBindings;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@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.createdBy).first(StringExpression::containsIgnoreCase);
        bindings.bind(root.createdAt).first(DateTimeExpression::eq);
    }
}
plugins {
	id 'org.springframework.boot' version '2.7.0'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.fastcampus'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-data-rest'
	implementation 'org.springframework.data:spring-data-rest-hal-explorer'
	runtimeOnly 'com.h2database:h2'
	runtimeOnly 'mysql:mysql-connector-java'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	implementation "com.querydsl:querydsl-jpa" //필수
	implementation "com.querydsl:querydsl-core" //필수
	implementation "com.querydsl:querydsl-collections"
	annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
	annotationProcessor "jakarta.annotation:jakarta.annotation-api"
	annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

tasks.named('test') {
	useJUnitPlatform()
}

def generated = 'src/main/generated'

tasks.withType(JavaCompile) {
	options.getGeneratedSourceOutputDirectory().set(file(generated))
}

sourceSets {
	main.java.srcDirs += [generated]
}

clean {
	delete file(generated)
}