Spring

[Spring] Spring Boot + QueryDSL 활용하기

펭귄코기 2022. 11. 14. 18:29

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");
}