게시판 서비스 api 테스트를 만들었지만 api가 전부 다 구현 되어있지는 않은 상황이다. 예를 들면 api/articles에서 필터를 사용하여 조회를 하는 것이 목표인데, 지금 테스트를 통해 실행해보면 해당 기능은 아직 만들어 지지 않았다는 것을 알 수 있다.
예를 들어서 hal explorer로 조회한 게시글 중에서 제목중에 Donec 이라는 단어가 들어있는 게시물이 존재한다.
그렇다면 만약에
http://localhost:8080/api/explorer/index.html#uri=http://localhost:8080/api/articles?title=Donec
이런식으로 뒤에 title의 값이 Donec인 게시글을 조회할 때 결과값이 적어도 이 게시글이 나타나야하지만 막상 검색해보면
보이는 것처럼 조건을 만족하지 않는데도 게시글이 조회된 모습이다. 따라서 이 세부기능을 구현해야한다. 그와 동시에 query dsl에 연동시키는 작업도 진행한다.
이전에 깃헙 프로젝트 이슈에서 체크한 목록을 다시 해제하고 내용을 수정했다.
※ data REST 테스트는 이 프로젝트의 비즈니스 로직으로 구현한 내용이 아니라 data rest 기능이고, 통합테스트라거 무거운데다가 db에 영향을 끼치기 때문에 제외처리해서 커밋처리했다.
build.gradle에 queyDSL실행을 위해 필요한 디펜던시를 추가한다.
//queryDSL 실행
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.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
querydsl-jpa만 하면 Qclass가 생성되지 않으므로 쿼리타입을 생성할 때 필요한 라이브러리 추가는 annotationProcessor를 이용해서 추가한다.
여기서 끝이 아니라 추가 옵션을 작성한다.
// 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)
}
generated라는 파일경로 변수를 만들었다. querydsl은 자동으로 클래스를 생성하는 기능이 있는데 그 클래스를 QClass 라고 한다. 이 클래스는 build 디렉토리 안으로 들어가기 때문에 빌드 진행을 할 때 우리 눈에 보이지 않지만, 위 코드처럼 파일경로를 설정해서 직접 눈으로 볼수 있게 설정한다.
왜? 어떤 ide를 이용해서 빌드할때 생길수 있는 잠재적인 문제를 피하기 위함이라고 한다. 빌드를 할때 gradle 빌드 도구가 스캔하는 영역과, ide가 빌드를 시도할 때 인텔리제이가 스캔하는 영역의 차이가 존재한다. 이런 차이 때문에 gradle이 스캔한 영역과 인텔리제이가 스캔하고자 하는 빌드 클래스 파일이 있는 영역들을 한번 더 스캔함으로써 중복 스캔이 일어나게 된다.
파일의 경로를 변경한 다음 java컴파일을 할때 해당 경로로 소스 아웃풋 디렉토리를 변경한다.
새로운 파일경로가 생성되었기 때문에 소스셋에도 등록을 해줘야하고. 마지막으로 gradle을 clean해야 할 때 해당경로도 같이 제거를 한다는 옵션이다.
reload 한뒤 gradle에서 build를 실행하면,
이렇게 디렉토리가 추가된 것을 볼수 있다.
ArticleRepository로 들어가서 QuerydslPredicateExecutor
와 QuerydslBinderCustomizer
를 추가한다.
QuerydslPredicateExecutor
는 기본적으로 엔티티 안에 있는 모든 필드에 대한 기본 검색기능을 추가해준다. 사실 이거 하나만 추가해줘도 끝난다.
ArticleCommentRepository에도 같은 요소를 작성한후에 프로젝트를 실행해서 테스트 해보자.
게시글 목록을 가져오고 목록중 하나의 게시글의 제목을 검색한다.
예를 들어 내 경우에는 처음 게시글의 제목인 Donec odio justo, sollicitudin ut, suscipit a, feugiat et, eros.
을 검색해봤다.따라서 헤더에
http://localhost:8080/api/articles?title=Donec odio justo, sollicitudin ut, suscipit a, feugiat et, eros.
이렇게 작성하고 실행하면?
조건을 만족하는 2개의 게시글이 나타났다!
검색의 기능이 작동한다.
하지만!
게시글의 제목의 일부만 입력해도 결과값이 나타나지 않는다. 제목의 일부분만 입력해도 검색결과에 나타나게 하고싶다면, 내 입맛에 맞는 검색기능을 추가하기 위해서 이제! QuerydslBinderCustomizer
가 등판을 하는 것이다.
@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.hashtag,root.content, root.createdAt, root.createdBy); // 엔티티에 있는 필드중, 해당 항목들도 검색조건으로 검색할수 있게 해준다.
bindings.bind(root.title).first(StringExpression::containsIgnoreCase); // like'%${v}%'
bindings.bind(root.content).first(StringExpression::containsIgnoreCase);
bindings.bind(root.createdAt).first(DateTimeExpression::eq);
bindings.bind(root.createdBy).first(StringExpression::containsIgnoreCase);
};
article 뿐만 아니라 articleComment 리포지토리도 설정을 작성한다.
@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); // like'%${v}%'
bindings.bind(root.createdAt).first(DateTimeExpression::eq);
bindings.bind(root.createdBy).first(StringExpression::containsIgnoreCase);
};
}
이제 다시 실행해서 타이틀 부분검색을 해보면
7개의 게시판이 결과로 나타났다!
createdBy 검색도,
content 검색도,
모두 제대로 작동하지만 createdAt의 경우, 현재 시,분,초까지 정확하게 기록해야 검색되는 상황이지만 나중에 추후에 수정하고, 부분검색의 기능이 구현 되었기 때문에 여기서 종료.
변경/추가내용을 저장, 커밋한다.
잠깐!
내용을 저장하고 깃크라켄에 가보면 못보던 생성파일이 스테이징 목록에 추가되어있다.
QArticleComment 와 QAuditingFields는 자동생성 파일이고 프로젝트에 들어갈 필요가 없기 때문에 gitignore에 추가한다. gradle.build에서 따로 추가한 디렉토리 전체를 추가해주면 된다.(자동생성되는 Q클래스의 경로를 여기로 지정했으니까)
다음엔 게시판 서비스 뷰를 만드는 작업에 들어간다.