본문 바로가기
BackEnd/Project

[Board] Ch02. API 구현

by 개발 Blog 2024. 8. 8.

공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.

 

API 구현

이번에는 API 구현 부분을 마무리해 볼 것이다. 이번 섹션에서는 검색 기능을 구현하면서 Query DSL을 연동시키는 방법과 Spring Boot, Spring Data JPA와 Query DSL의 연계 기능이 어떻게 서비스로 제공되는지 알아볼 것이다. 

 

DataRestTest 테스트 방식 변경

지난 시간에 만든 DataRestTest는 평범한 방식으로는 컨트롤러 테스트를 하듯이 할 수 없었다. 웹 MVC 테스트로 슬라이스 테스트를 적용하려면 추가적인 설정이 필요했는데, 이는 꽤 복잡했다. 그래서 Data Rest와 관련된 오토 컨피규레이션을 불러올 수 있게끔 통합 테스트 형태로 진행했다.

@SpringBoot 애노테이션을 사용하여 필요한 빈들을 모두 읽은 환경에서 테스트를 진행하도록 설정했다. 그러나 이 방식은 몇 가지 문제점이 있었다:

1. 테스트의 무거움: 통합 테스트 형태로 진행하다 보니 테스트가 무겁다.
2. DB 모킹 불가: DB 쪽에 모킹이 되지 않아 테스트가 느리고, 실제 DB에 의존한다.

또한, Data Rest의 기능은 비즈니스 로직으로 구현한 것이 아니고, Spring Data REST에서 제공하는 기능이다. 기본적으로 잘 작동해야 하는 기능이므로, 이를 테스트 대상으로 두는 것은 의미가 없다고 판단했다. 따라서 이 테스트는 Disabled 처리하기로 했다.

@Disabled("Spring Data REST 통합테스트는 불필요하므로 제외시킴")
@DisplayName("Data REST -API 테스트")
@Transactional
@AutoConfigureMockMvc
@SpringBootTest
public class DataRestTest {
...
}

 

Query DSL 설정

build.gradle 파일에 필요한 의존성을 추가하여 설정을 완료할 수 있다.

// queryDSL 설정
implementation "com.querydsl:querydsl-jpa:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
implementation "com.querydsl:querydsl-core"
implementation "com.querydsl:querydsl-collections"
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" // querydsl JPAAnnotationProcessor 사용 지정
annotationProcessor "jakarta.annotation:jakarta.annotation-api" // java.lang.NoClassDefFoundError (javax.annotation.Generated) 대응 코드
annotationProcessor "jakarta.persistence:jakarta.persistence-api" // java.lang.NoClassDefFoundError (javax.annotation.Entity) 대응 코드

// 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)
}

// Heroku 설정
jar {
    manifest {
        attributes('Main-Class': 'com.fastcampus.projectboard.FastCampusProjectBoardApplication')
    }
}

QueryDSL 의존성 추가

  • QueryDSL JPA, Core, Collections 의존성을 추가한다.
  • QueryDSL Annotation Processor를 설정하여 QClass 파일 생성을 지정한다.
  • jakarta.annotation 및 jakarta.persistence 의존성을 추가하여 관련 에러를 방지한다.

QueryDSL 설정

  • QClass 파일 생성 위치를 src/main/generated로 지정한다.
  • Java 컴파일 시 생성된 QClass 파일들을 해당 디렉토리에 저장한다.
  • Java 소스 세트에 src/main/generated 디렉토리를 포함시킨다.
  • gradle clean 명령 시 QClass 파일들이 삭제되도록 설정한다.

Heroku 설정

  • Heroku 배포 시 JAR 파일의 메인 클래스를 com.fastcampus.projectboard.FastCampusProjectBoardApplication으로 지정하여 애플리케이션의 진입점을 설정한다.

Query DSL 연동 확인

QueryDSL 설정 후 QArticle, QArticleComment, QAuditingFields와 같은 Q 클래스들이 generated 폴더에 생성되었다. 이는 QueryDSL이 성공적으로 설정되고 Q 클래스를 생성할 준비가 완료되었음을 나타낸다.

 

API 검색 기능 구현

다음 단계로 API 검색 기능을 구현한다.

이를 위해 ArticleRepository와 ArticleCommentRepository에 QueryDSL을 연동하여 검색 기능을 추가한다.

@RepositoryRestResource
public interface ArticleRepository extends
        JpaRepository<Article, Long>,
        QuerydslPredicateExecutor<Article>
{
}



@RepositoryRestResource
public interface ArticleCommentRepository extends
        JpaRepository<ArticleComment, Long>,
        QuerydslPredicateExecutor<ArticleComment>
{
}

이제 실행하여 title 필드로 필터를 걸어서 검색이 되는지 확인한다. 예를 들어, 특정 제목으로 검색하여 결과를 확인한다.

 

검색 결과는 해당 제목을 가진 기사를 반환한다.

 

부분 검색 기능 구현

현재는 제목을 완전히 일치시켜야 검색이 가능하다.

부분 검색 기능을 추가하여, 제목의 일부만 입력해도 검색이 되도록 설정한다.

 

ArticleRepository

ArticleRepository는 Article 엔티티에 대한 데이터 접근을 담당하는 리포지토리 인터페이스이다. 이 인터페이스는 Spring Data JPA와 QueryDSL을 활용하여 다양한 검색 및 CRUD 작업을 수행할 수 있도록 구성하였다.

package org.example.projectboard.repository;

import com.querydsl.core.types.dsl.DateTimeExpression;
import com.querydsl.core.types.dsl.StringExpression;
import org.example.projectboard.domain.Article;
import org.example.projectboard.domain.QArticle;
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.createdAt).first(DateTimeExpression::eq);
        bindings.bind(root.createdBy).first(StringExpression::containsIgnoreCase);
    }
}
  • JpaRepository<Article, Long>
    • 기본적인 CRUD 작업을 지원하는 JPA 리포지토리이다.
    • Article 엔티티와 그 식별자 타입인 Long을 매개변수로 받는다.
  • QuerydslPredicateExecutor<Article>
    • QueryDSL을 사용하여 동적 쿼리를 지원한다.
    • 이를 통해 복잡한 조건의 검색 기능을 구현할 수 있다.
  • QuerydslBinderCustomizer<QArticle>
    •  QueryDSL 바인딩을 사용자 정의할 수 있는 인터페이스이다.
    • 이를 통해 검색 조건을 커스터마이징 할 수 있다.
  • customize 메서드
    • excludeUnlistedProperties
      • QueryDSL을 사용하여 쿼리 바인딩을 설정할 때, 나열되지 않은 속성을 제외할지 여부를 설정하는 메서드이다. 
    • QueryDSL 바인딩을 설정한다.
    • 여기서는 title, content, hashtag, createdAt, createdBy 필드를 검색 가능하도록 설정하고, 해당 필드에 대해 대소문자를 구분하지 않는 부분 검색을 지원하도록 설정하였다.

ArticleCommentRepository

ArticleCommentRepository는 ArticleComment 엔티티에 대한 데이터 접근을 담당하는 리포지토리 인터페이스이다. 이 인터페이스는 Spring Data JPA와 QueryDSL을 활용하여 다양한 검색 및 CRUD 작업을 수행할 수 있도록 구성하였다.

package org.example.projectboard.repository;

import com.querydsl.core.types.dsl.DateTimeExpression;
import com.querydsl.core.types.dsl.StringExpression;
import org.example.projectboard.domain.ArticleComment;
import org.example.projectboard.domain.QArticleComment;
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.createdAt).first(DateTimeExpression::eq);
        bindings.bind(root.createdBy).first(StringExpression::containsIgnoreCase);
    }
}
  • JpaRepository<ArticleComment, Long>
    • 기본적인 CRUD 작업을 지원하는 JPA 리포지토리이다.
    • ArticleComment 엔티티와 그 식별자 타입인 Long을 매개변수로 받는다.
  • QuerydslPredicateExecutor<ArticleComment>
    • QueryDSL을 사용하여 동적 쿼리를 지원한다.
    • 이를 통해 복잡한 조건의 검색 기능을 구현할 수 있다.
  • QuerydslBinderCustomizer<QArticleComment>
    • QueryDSL 바인딩을 사용자 정의할 수 있는 인터페이스이다.
    • 이를 통해 검색 조건을 커스터마이징 할 수 있다.
  • customize 메서드
    • excludeUnlistedProperties
      • QueryDSL을 사용하여 쿼리 바인딩을 설정할 때, 나열되지 않은 속성을 제외할지 여부를 설정하는 메서드이다.
    • QueryDSL 바인딩을 설정한다.
    • 여기서는 content, createdAt, createdBy 필드를 검색 가능하도록 설정하고, 해당 필드에 대해 대소문자를 구분하지 않는 부분 검색을 지원하도록 설정하였다.

이제 대소문자 구분 없이 부분검색이 되는지 확인한다.

 

부분 검색이 잘 되는 것을 확인할 수 있다.

 

Q클래스 파일 관리

Q 클래스 파일은 자동 생성 파일이므로 프로젝트에 포함시킬 필요가 없다. 따라서 .gitignore에 추가하여 관리한다.

 

.gitignore

## Querydsl
/src/main/generated

 

깃허브에 Push 및 마무리

이제 모든 설정과 구현을 완료했으므로 깃허브에 변경 사항을 push 하고 프로젝트를 마무리한다.

 

마무리

이번 프로젝트에서는 Spring Data REST와 QueryDSL을 사용하여 간단하고 효율적인 API 검색 기능을 구현하였다.

QueryDSL 설정을 통해 자동으로 생성되는 Q 클래스를 활용하여, 부분 검색 기능까지 추가할 수 있었다.

이를 통해 사용자는 대소문자 구분 없이 제목의 일부만으로도 원하는 결과를 검색할 수 있게 되었다.

프로젝트를 깔끔하게 마무리하기 위해 자동 생성 파일을 .gitignore에 추가하였고, 모든 변경 사항을 깃허브에 push 하였다.

이로써 API의 검색 기능이 더욱 유연하고 강력하게 개선되었다.