Yebali

QueryDSL in Kotlin 본문

Spring

QueryDSL in Kotlin

예발이 2021. 10. 31. 23:44

QueryDSL이란?

기존 JPA Criterial는 코드로 JPQL을 작성하므로 문법 오류를 컴파일 단계에서 잡을 수 있고 IDE 자동완성 기느의 도움을 받을 수 있는 등 여러 가지 장점이 있다. 하지만 너무 복잡하고 어렵다.

쿼리를 문자가 아닌 코드로 작성해도 쉽고 간결하며 그 모양도 쿼리와 비슷하게 개발 할 수 있는 프로젝트가 바로 QueryDSL이다.

QueryDSL 설정

build.gradle.kts에 아래 설정을 추가해준다. Version은 개발자가 원하는 버전을 선택한다.

plugins {
	kotlin("kapt") version "1.4.32"
}

dependencies {
	val querydslVersion = "4.4.0"

	//== QueryDSL ==//
	implementation("com.querydsl:querydsl-jpa:$querydslVersion") // QueryDSL JPA 라이브러리
	kapt("com.querydsl:querydsl-apt:$querydslVersion:jpa") // Q클래스 생성에 필요한 라이브러리
}

QueryDSL은 @Entity 애노테이션을 선언한 클래스를 탐색하고, JPA Annotation Processor를 이용하여 Q 클래스를 생성한다. 이때 Annotation Processor를 사용하기 위해서 ‘kapt’라는 플러그인을 사용한다.

Kapt란?

코틀린 프로젝트를 컴파일 할 때는 javac가 아닌 kotlinc로 컴파일을 하기 때문에 Java로 작성한 애노테이션 프로세서가 동작하지 않는다. 그렇기 때문에 코틀린에서는 이러한 애노테이션 처리기를 위해 KAPT(Kotlin Annotation Processing Tool)를 제공한다.

 

이후 Gradle을 Reload하고 build 하면 ‘build/generated/source/japt/main’ 하위에 @Entity 애노테이션이 붙어있던 클래스로부터 Q 클래스들이 생성된 걸 볼 수 있다.

Option) JpaQueryFactory Bean으로 등록하기

QueryDSL는 이용해 쿼리를 만들고 실행시키는 기능은 JpaQueryFactory를 통해 이루어진다.

이때 JpaQueryFactory를 Bean으로 등록하여 필요한 곳에서 주입 받아 쓰면 편리하다.

// QuerydlsConfig.kt

@Configuration
class QuerydslConfig(
    @PersistenceContext
    private val entityManager: EntityManager
) {

    @Bean
    fun jpaQueryFactory() = JPAQueryFactory(entityManager)
}

QueryDSL 사용

QueryDSL을 사용하기 위해서는 쿼리 타입(Q)을 생성해야 하는데 생성자는 별칭을 파라미터로 받는다.

지정한 별칭은 JPQL에서 사용된다.

QTeam qteam = new QTeam("t") // 생성되는 JPQ의 별칭이 't'

하지만 쿼리 타입(Q)은 사용하기 편히하도록 내부에 기본 인스턴스를 보관하고 있다.

@Generated("com.querydsl.codegen.EntitySerializer")
public class QTeam extends EntityPathBase<Team> {

    private static final long serialVersionUID = 997838414L;

    public static final QTeam team = new QTeam("team"); // 내부에 이미 인스턴스가 있음.
    
    ...
    ...
}

 

그렇기 때문에 내부에 있는 인스턴스를 그대로 쓰면 된다.

QTeam qteam = QTeam.team // 이렇게 쓰거나

OR

import com.example.testKoSpring.entity.QTeam.team
QTeam qteam = team

밑에 나올 예제에서는 QTeam.team를 import해서 사용할 것이다.

 

조회

  • 여러건 조회 : fetch()
    fun findAll(): MutableList<Team>? {
        return jpaQueryFactory
            .selectFrom(team)
            .fetch()
    }​
  • 단건 조회 : fetchOne()
    fun fundOne(teamId: Long): Team? {
        return jpaQueryFactory
            .selectFrom(team)
            .where(team.id.eq(teamId)) 
            .orderBy(team.teamName.desc())
            .fetchOne()
    }​
     

페이징

fun fundPaging(): MutableList<Team>? {
    return jpaQueryFactory
        .selectFrom(team)
        .offset(0) // page number
        .limit(3) // paging size
        .orderBy(team.teamName.desc())
        .fetch()
}

Group By

fun group(): MutableList<Team>? {
    return jpaQueryFactory
        .selectFrom(team)
        .groupBy(team.teamName)
        .having(team.id.gt(100))
        .fetch()
}

groupBy후 그룹화된 결과를 제한하려면 having을 사용하면 된다.

조인

  • 기본 조인
    fun join(): MutableList<Team>? {
        return jpaQueryFactory
            .selectFrom(team)
            .leftJoin(team.members)
            .fetch()
    }
    
    //== on 사용 ==//
    fun joinOn(): MutableList<Team>? {
        return jpaQueryFactory
            .selectFrom(team)
            .leftJoin(team.members)
            .on(team.id.gt(2))
            .fetch()
    }​
  • 기본 조인시 쿼리
    select
        team0_.id as id1_1_,
        team0_.team_name as team_nam2_1_ 
    from
        team team0_ 
    left outer join
        member members1_ 
            on team0_.id=members1_.team
            
    //== on 사용 ==//
    select
        team0_.id as id1_1_,
        team0_.team_name as team_nam2_1_ 
    from
        team team0_ 
    left outer join
        member members1_ 
            on team0_.id=members1_.team 
            and (
                team0_.id>?
            )​

 

  • 페치 조인
    fun join(): MutableList<Team>? {
        return jpaQueryFactory
            .selectFrom(team)
            .leftJoin(team.members).fetchJoin() //해당 조인이 페치 조인임을 명시
            .fetch()
    }​
  • 페치 조인시 쿼리
    select
        team0_.id as id1_1_0_,
        members1_.id as id1_0_1_,
        team0_.team_name as team_nam2_1_0_,
        members1_.name as name2_0_1_,
        members1_.team as team3_0_1_,
        members1_.team as team3_0_0__,
        members1_.id as id1_0_0__ 
    from
        team team0_ 
    left outer join
        member members1_ 
            on team0_.id=members1_.team​

서브 쿼리

JPAExpressions를 이용해서 서브 쿼리를 적용할 수 있다.

서브 쿼리는 select, where 절 안에만 사용할 수 있다.

fun subQuery(): Team? {
    return jpaQueryFactory
        .selectFrom(team)
        .where(team.id.eq(
            JPAExpressions
                .select(team.id.max())
                .from(team)
        ))
        .fetchOne()
}

서브 쿼리 사용 시 쿼리

select
    team0_.id as id1_1_,
    team0_.team_name as team_nam2_1_ 
from
    team team0_ 
where
    team0_.id=(
        select
            max(team1_.id) 
        from
            team team1_
    )

'Spring' 카테고리의 다른 글

Spring의 Filter와 Interceptor  (0) 2022.01.23
Join의 종류와 QueryDsl  (0) 2021.11.01
Spring JPA의 Cache  (0) 2021.10.31
Spring JPA에 기본 생성자가 필요한 이유  (0) 2021.10.31
Spring JPA의 @OneToOne 관계와 지연로딩  (0) 2021.10.11