[Spring]QueryDSL 동적쿼리

Beanzinu·2022년 6월 6일

스프링부트

목록 보기
4/7

QueryDSL이 필요한 이유

현재 모든 Database에 SQL 쿼리를 JPA를 활용하여 쉽게 결과를 받아왔다.

JPA의 경우

  • JpaRepository 를 활용하여 기본적인 쿼리문을 인터페이스 함수들을 쉽게 선언하여 결과를 받아올 수 있다.
  • @Query 어노테이션을 활용하여 커스텀 쿼리들을 작성하여 원하는 함수도 선언 가능
  • SQL, JPQL은 문자열이다.
  • Type-check가 불가능하다.
  • 잘 해봐야 애플리케이션 로딩 시점에 알 수 있다.
  • 컴파일 시점에 알 수 있는 방법이 없다.
  • 자바와 문자열의 한계이다.
  • 해당 로직 실행 전까지 작동여부 확인을 할 수 없다.
  • 해당 쿼리 실행 시점에 오류를 발견한다.
    ( 잘못된 인터페이스 함수들을 선언하여도 오류를 발견하지 못한다. )

검색 키워드들을 기반으로 검색결과를 반환하는 시스템을 구축하며 겪은 문제

  • 검색 키워드들은 몇 개인지 알 수 없다.
    -> 동적으로 검색 키워드들의 개수가 변한다.
    -> JPA를 사용할 경우 Parameter의 개수를 특정지을 수 없다.

QueryDSL의 장점

  • 문자가 아닌 코드로 작성가능
  • 컴파일 시점에 문법 오류를 발견가능
  • 코드 자동완성(IDE 도움)단순하고 쉽다.
  • 코드 모양이 JPQL과 거의 비슷하다
  • 동적 쿼리

Configurations

  1. build.gradle 수정하기
// QueryDSL Version 5부터 추가가 필요하다고 한다.
// 최상단에 명시
buildscript {
	ext {
		queryDslVersion = "5.0.0"
	}
}

plugins {
	...
	id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" // 작성
	...
}

...

// QueryDSL 설정 start
def querydslDir = "$buildDir/generated/querydsl"

dependencies {
	...
	// [QueryDSL]
	implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
	implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
}

querydsl {
	jpa = true
	querydslSourcesDir = querydslDir
}
sourceSets {
	main.java.srcDir querydslDir
}
compileQuerydsl{
	options.annotationProcessorPath = configurations.querydsl
}
configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
	querydsl.extendsFrom compileClasspath
}
  1. QClass 생성하기
  • Why QClass ?

    • Querydsl을 이용하려면 기존에 작성한 Entity 클래스를 그대로 이용하는 것이 아닌 Q도메인 클래스를 이용해야만 한다.

    • Querydsl의 핵심 원칙은 타입 안정성(Type Safety)이다. 도메인 타입의 프로퍼티를 반영해 생성한 쿼리 타입을 이용해 쿼리를 작성하게 된다. 또한, 완전하게 타입에 안전한 방법으로 함수 or 메서드 호출이 이뤄진다.

  • Gradle -> Tasks -> other -> compileQuerydsl 실행

  • 내가 작성한 Entity들에 대하여 /build/generated에 QClass 생성

사용해보기 (예제)

  1. QueryDSLSupport 클래스 상속
  • Spring Data Jpa에 포함된 클래스로 QueryDsl 라이브러리를 통해 직접 구현할 경우 필요

  • 생성자가 존재하므로 super()을 이용해 호출하고 super(Domain.class)에 null이 올 수 없다.

  • Domain : Post.class

@Repository
public class PostRepositorySupport extends QuerydslRepositorySupport {

	private final JPAQueryFactory jpaQueryFactory;
    private final EntityManager entityManager;

    public PostRepositorySupport(JPAQueryFactory jpaQueryFactory, EntityManager entityManager) {
        super(Post.class);
        this.jpaQueryFactory = jpaQueryFactory;
        this.entityManager = entityManager;
    }

}
  1. 사용자함수 작성
  • 검색 결과가 클 경우를 대비하여 페이지네이션을 적용
    (PageIml 객체를 반환함)
  • 동적으로 쿼리를 작성할 경우 BooleanBuilder를 사용해야한다.
  • searchBuilder.~ 코드를 조건에 맞게 동적으로 할당시켜
    이후 DB에 쿼리를 전달할 때 한번에 전달
  • 아래와 같은 SQL 문법들을 단순 코드로 작성 가능하고 오류 또한 발견 가능하고 리턴값을 받아 바로 반환하여 사용 가능하다!
from : 쿼리 소스를 추가한다.
innerJoin, join, leftJoin, fullJoin, on : 조인 부분을 추가한다. 조인 메서드에서 첫 번째 인자는 조인 소스이고, 두 번째 인자는 대상(별칭)이다.
where : 쿼리 필터를 추가한다. 가변 인자나 AND/OR 메서드를 이용해 필터를 추가한다.
groupBy : 가변인자 형식의 인자를 기준으로 그룹을 추가한다.
having : Predicate 표현식을 이용해 "group by" 그룹핑의 필터를 추가한다.
orderBy : 정렬 표현식을 이용해서 정렬 순서를 지정한다. 숫자나 문자열에 대해서는 asc()나 desc()를 사용하고, OrderSpecifier에 접근하기 위해 다른 비교 표현식을 사용한다.
limit, offset, restrict : 결과의 페이징을 설정한다. limit은 최대 결과 개수, offset은 결과의 시작 행, restrict는 limit과 offset을 함께 정의한다.

queryDSL ref에 대한 더 자세한 내용 : http://querydsl.com/static/querydsl/4.0.1/reference/ko-KR/html_single/

public PageImpl<Post> findCustomSearchResultsWithPagination(String sentence, Pageable pageable) {
        ArrayList<String> phrase_list = new ArrayList<>();
        ArrayList<String> tag_list = new ArrayList<>();
        ArrayList<String> exact_phrase_list = new ArrayList<>();

		BooleanBuilder searchBuilder = new BooleanBuilder();

        QPost post = QPost.post;

		... // 검색 문장 처리 로직 ( 길어서 생략 )
        
        
        // 태그
        for( String tag : tag_list ){
            searchBuilder.or(QPost.post.tags.contains(tag)); // OR Post.tags LIKE %:tag%
        }
        for( String exact_phrase : exact_phrase_list ){
            searchBuilder.or(QPost.post.title.contains(exact_phrase));
            searchBuilder.or(QPost.post.content.contains(exact_phrase));
        }
        for( String phrase : phrase_list ){
            searchBuilder.or(QPost.post.title.contains(phrase));
            searchBuilder.or(QPost.post.content.contains(phrase));
        }

        JPAQuery<Post> postJPAQuery = jpaQueryFactory.selectFrom(QPost.post)
                .where(searchBuilder)
                .orderBy(QPost.post.createdTime.desc());

        long totalCount = postJPAQuery.stream().count();
        List<Post> results = getQuerydsl().applyPagination(pageable, postJPAQuery).fetch();

        return new PageImpl<>(results, pageable, totalCount);
}

참고:
https://velog.io/@solchan/Spring-QueryDSL-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0
https://ict-nroo.tistory.com/117
https://dev-gorany.tistory.com/32
https://jessyt.tistory.com/55

profile
당신을 한 줄로 소개해보세요.

0개의 댓글