[Spring] Spring Boot + QueryDSL 활용하기
JPA를 활용해서 프로젝트를 진행하다보면 일반적인 검색을 넘어서
필터검색이나 키워드 검색을 하게될때 JPA만을 이용해서 구현하기에는
로직이 너무 복잡해지는 상황을 맞이하게 된다
그래서 어떻게하면 간단히 구현하고 성능개선도 할 수 있을까를 고민하다가
QueryDSL 이라는 것이 있다는것을 알게되었고
기본적인 설정부터 활용하기 까지 정리해보고자 한다
QueryDSL 레퍼런스 문서
http://querydsl.com/static/querydsl/3.4.3/reference/ko-KR/html_single/
Querydsl - 레퍼런스 문서
본 절에서는 SQL 모듈의 쿼라 타입 생성과 쿼리 기능을 설명한다. com.mysema.query.sql.Configuration 클래스를 이용해서 설정하며, Configuration 클래스는 생성자 인자로 Querydsl SQL Dialect를 취한다. 예를 들어
querydsl.com
1. QueryDSL 이란?
- 정적 타입을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해주는 프레임워크다
- JPQL을 Java 코드로 작성할 수 있도록 해준다
2. QueryDSL을 왜 사용하는가?
- 쿼리를 짜다보면 String 연결을 이용해서 짜다보니 컴파일 단계에서 오류가 있는지 알 수 없다
- 그래서 쿼리 생성을 자동화 하고 자바 코드로 작성할 수 있기에 querydsl을 사용한다
- 그 외에도 이득이 많다 맨 위에 레퍼런스 문서를 참고하기 바란다
3. QueryDSL 설정 및 사용하기
1) build.gradle
querydsl을 위해 추가해줘야하는 목록
- buildscript //동적쿼리
- plugins -> id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" // 동적쿼리
- dependencies ->
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
- QueryDSL start 에서 QueryDSL end 까지
buildscript { // 동적쿼리
ext {
queryDslVersion = "5.0.0"
}
}
plugins {
id 'org.springframework.boot' version '2.7.4'
id 'io.spring.dependency-management' version '1.0.14.RELEASE'
id 'java'
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" // 동적쿼리
}
group = 'com.sparta'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
//S3
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE'
// jwt
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
//swagger
implementation 'io.springfox:springfox-swagger2:2.9.2'
implementation 'io.springfox:springfox-swagger-ui:2.9.2'
//javassist
implementation group: 'org.javassist', name: 'javassist', version: '3.29.0-GA'
//queryDSL
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
}
tasks.named('test') {
useJUnitPlatform()
}
// QueryDSL start
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
// QueryDSL end
2) Q 클래스 생성
2-1) 오른쪽에 Gradle -> other -> compilejava 더블클릭
2-2) 왼쪽 build -> generated -> querydsl 안에 Q클래스 생성된거 확인
3) QueryDSLConfig 생성
querydsl 패키지 -> QueryDSLConfig 클래스 생성해준다
@Configuration
public class QuerydslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
지금부터 repository를 만들것인데 전체 흐름을 파악하고 아래 코드를 나열하겠다
여기서 postRepository가 JpaRepository와 postRepositoryCustom을 상속받고
postRepositoryCustom은 postRepositoryImpl을 구현하고있다고 보면 된다
그래서 실제로 postRepositoryImpl에서 QueryDsl을 구현한다고 보면된다
4) PostRepository
@Repository
public interface PostRepository extends JpaRepository<Post, Long>, PostRepositoryCustom {
List<Post> findAllByOrderByModifiedAtDesc();
}
5) RepositoryCustom 만들기
public interface PostRepositoryCustom {
List<Post> findByKeyword(String keyword);
}
6) PostRepositoryImpl
@Repository
@RequiredArgsConstructor
public class PostRepositoryImpl implements PostRepositoryCustom {
private final JPAQueryFactory jpaQueryFactory;
@Override
public List<Post> findByKeyword(String keyword) {
QPost post = QPost.post;
QUser user = QUser.user;
List<Post> posts = jpaQueryFactory.selectFrom(post)
.leftJoin(post.user, user).fetchJoin()
// where == 이 기준으로 조건을 주겠다
// contains == 앞뒤 구분없이, 1개의 문자만 똑같아도 포함된 것으로 간주하는 메서드
.where(post.contents.contains(keyword))
// orderBy == 이 기준으로 정렬하겠다
.orderBy(post.modifiedAt.desc())
// fetch == 기존에 조인된 것을 다시 해제하는 역할 + querydsl 값을 반환해주는 역할
.fetch();
return posts;
}
}
위와 같은 과정을 마치고 아래와 같이 서비스단에서 평소와 같이 진행해주면 된다
@Transactional
public ResponseDto<?> searchKeyword(String keyword) {
List<Post> posts = postRepository.findByKeyword(keyword);
return ResponseDto.success("posts");
}