JiwonDev

QueryDSL + JPA

by JiwonDev

์ฐธ๊ณ ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•˜๋ฉด ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/test
    username: sa
    password:
    driver-class-name: org.h2.Driver

  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        format_sql: true # ์ฟผ๋ฆฌ๋ฅผ ํฌ๋งทํŒ…ํ•˜์—ฌ ์ถœ๋ ฅํ•œ๋‹ค.
        use_sql_comments: true # ํ•ด๋‹น ์ฟผ๋ฆฌ์˜ JPQL์„ ์ฃผ์„์œผ๋กœ ์ถœ๋ ฅํ•œ๋‹ค.

logging.level:
  org.hibernate.SQL: debug # ์ฟผ๋ฆฌ๋ฅผ ๋กœ๊ทธ์— ๋‚จ๊ธด๋‹ค.
  org.hibernate.event.internal.AbstractFlushingEventListener: DEBUG # Flush ํ™•์ธ ๋กœ๊ทธ

 

๐Ÿ’ญ QueryDSL ์†Œ๊ฐœ(์˜คํ”ˆ์†Œ์Šค)

2015๋…„์— ์ถœ์‹œํ•œ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์ด๋‹ค. ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด์ค€๋‹ค.

์„ค์ •์€ ์กฐ๊ธˆ ๊ท€์ฐฎ์ง€๋งŒ, ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์ž‘์„ฑ๋˜์—ˆ๊ธฐ์— ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ณ , ์ปดํŒŒ์ผ ์‹œ์ ์—์„œ ๋งŽ์€ ์˜ค๋ฅ˜๋ฅผ ์žก์„ ์ˆ˜ ์žˆ๋‹ค.

https://github.com/querydsl/querydsl/tree/master/querydsl-examples

 

์žฌ๋ฐŒ๋Š” ์ ์€ Spring ๊ณต์‹๋ฌธ์„œ์—์„œ๋„ Predicate(JPA Criteria)๋Š” ์–ธ๊ธ‰๋„ ์—†๋Š”๋ฐ, JPA + QueryDSL ์ง€์›์€ ๊ฐ•์กฐํ•˜๊ณ  ์žˆ๋‹ค.

๊ทธ๋งŒํผ ์ธ๊ธฐ์žˆ๊ณ , ์‹ค๋ฌด์—์„œ ๊ฑฐ์˜ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ์ด๋‹ค.

 

 

๐Ÿ’ญ ์–ด๋–ป๊ฒŒ ์ถ”๊ฐ€ํ•˜๋‚˜์š”

์Šคํ”„๋ง๋ถ€ํŠธ 2.6.4 ์ดํ›„, queryDsl์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ๊ต‰์žฅํžˆ ์‰ฌ์›Œ์กŒ๋‹ค. ์ฐธ๊ณ ๋กœ QClass๋Š” ./build/generated/{์—”ํ‹ฐํ‹ฐ ๊ฒฝ๋กœ} ์— ์ƒ์„ฑ๋œ๋‹ค.
github.com/querydsl ์— ๋‚˜์™€์žˆ๋Š” ๊ทธ๋ž˜๋“ค ์„ค์ •๋ฐฉ๋ฒ• (2022.07)

 implementation `com.querydsl:querydsl-jpa`
 annotationProcessor "com.querydsl:querydsl-apt

- Gradle Kotlin DSL ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด https://jessyt.tistory.com/106 ์ฐธ๊ณ 

// kotlin
 implementation("com.querydsl:querydsl-jpa")
 kapt(group = "com.querydsl", name = "querydsl-apt", classifier = "jpa")

๋”๋ณด๊ธฐ
buildscript {
  ext {
    queryDslVersion = "5.0.0"
  }
}

plugins {
  id 'org.springframework.boot' version '2.6.2'
  id 'io.spring.dependency-management' version '1.0.11.RELEASE'
  //querydsl ์ถ”๊ฐ€
  id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
  id 'java'
}

group = 'com.example'
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-web'
  //querydsl ์ถ”๊ฐ€
  implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
  annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"
  compileOnly 'org.projectlombok:lombok'
  runtimeOnly 'com.h2database:h2'
  annotationProcessor 'org.projectlombok:lombok'
}

test {
  useJUnitPlatform()
}

//querydsl ์ถ”๊ฐ€ ์‹œ์ž‘
def querydslDir = "$buildDir/generated/querydsl"

querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
configurations {
querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
//querydsl ์ถ”๊ฐ€ ๋

 

- ์ฐธ๊ณ ๋กœ ๊ธฐ์กด์—๋Š” Gradle ํ”Œ๋Ÿฌ๊ทธ์ธ (com.ewerk.gradle.plugins.querydsl) ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๊ฐ€ํ–ˆ์—ˆ๋‹ค.

์Šคํ”„๋ง๋ถ€ํŠธ 2.6.4 ์ด์ „ ์„ค์ •๋ฐฉ๋ฒ•

๋”๋ณด๊ธฐ

์•„๋ž˜ 2๊ฐ€์ง€ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ค˜์•ผํ•œ๋‹ค.

  • implementation 'com.querydsl:querydsl-jpa'
    โžก queydsl์˜ JPA ์—ฐ๋™ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • annotationProcessor "com.querydsl:querydsl-apt
    โžก QueryDsl์˜ ์ปดํŒŒ์ผ ํƒ€์ž„ QClass ์ƒ์„ฑ๋„๊ตฌ (Gradle-Task querydslClasses)
buildscript {
    ext {
        queryDslVersion = "5.0.0"
    }
}

plugins {
    id 'org.springframework.boot' version '2.6.3'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" // ํ”Œ๋Ÿฌ๊ทธ์ธ ์ถ”๊ฐ€
    id 'java'
}

dependencies{
	...
    implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
    annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"
}

ํ˜น์‹œ๋‚˜ NoClassDefFoundError๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ์•„๋ž˜ ๋‘์ค„์„ ์ถ”๊ฐ€ํ•ด์ฃผ์ž.

javax.annotation.Entity๊ฐ€ jakarta.annotation.Entity๋กœ ๋ฐ”๋€Œ๋ฉด์„œ JPA ์–ด๋…ธํ…Œ์ด์…˜์„ ์ฐพ์ง€๋ชปํ•ด์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ด๋‹ค.

// javax -> jakarta ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•œ NoClassDefFoundError ์˜ค๋ฅ˜ ๋ฐฉ์ง€
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"

 

 

๐Ÿ“‘ QureyDsl ํ”Œ๋Ÿฌ๊ทธ์ธ ์ถ”๊ฐ€์„ค์ •

์•ž์—์„œ ์‚ฌ์šฉํ•œ ์Šคํ”„๋ง์˜ ๋„๊ตฌ๋“ค์€, Spring-boot-starter๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ณ„๋„์˜ ์„ค์ •์—†์ด ์‰ฝ๊ฒŒ ์ถ”๊ฐ€๊ฐ€ ๊ฐ€๋Šฅํ–ˆ์—ˆ๋‹ค.ํ•˜์ง€๋งŒ QueryDsl์€ ์˜คํ”ˆ์†Œ์Šค๋ผ์„œ ์„ค์ •์„ ์ž๋™ํ™”ํ•ด์ฃผ๋Š” ์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ ์—†๋‹ค. Gradle์„ ์ง์ ‘ ์„ค์ •ํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค.
๋ฌผ๋ก  ๋ฒ„์ „ ์ •๋„๋Š” ๋งž์ถฐ์ฃผ์ง€๋งŒ (์ปดํŒŒ์ผ ํƒ€์ž„์— QueryDsl์˜ ์ถ”๊ฐ€์ž‘์—…)์— ๋Œ€ํ•œ ์„ค์ • ์ž๋™ํ™”๋Š” ์ œ๊ณตํ•ด์ฃผ์ง€ ์•Š๋Š”๋‹ค.
๊ณต์‹๋ฌธ์„œ์—๋Š” Maven ์„ค์ •๋งŒ ์ œ๊ณตํ•ด์ค€๋‹ค. ๋ฌผ๋ก  ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ธฐ๋”๋ผ๋„, ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ๋žŒ์ด ๋งŽ์œผ๋ฏ€๋กœ ๊ตฌ๊ธ€๋งํ•˜๋ฉด ํ•ด๊ฒฐ๊ฐ€๋Šฅ.

/*๐Ÿ“Œ QueryDsl์ด ์ƒ์„ฑํ•œ ํŒŒ์ผ์ด ์œ„์น˜ํ•  ๊ฒฝ๋กœ */
def querydslDir = "$buildDir/generated/querydsl"

/*๐Ÿ“Œ querydsl์— ๊ด€ํ•œ ์„ค์ • */
querydsl {
 jpa = true
 querydslSourcesDir = querydslDir
}

/*๐Ÿ“Œ build์‹œ ์‚ฌ์šฉํ•  sourceSet์— ์ถ”๊ฐ€ํ•œ๋‹ค. ์ด๋ฅผ ํ•ด์ค˜์•ผ IDE์—์„œ QClass๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. */
sourceSets {
 main.java.srcDir querydslDir
}

/*๐Ÿ“Œ ์ปดํŒŒ์ผ ํด๋ž˜์ŠคํŒจ์Šค์— ์ถ”๊ฐ€ํ•˜๊ณ , ์–ด๋…ธํ…Œ์ด์…˜ ํ”„๋กœ์„ธ์„œ๋ฅผ ํ†ตํ•ด Q-Class๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. */
/* ๊ธฐ์กด์—๋Š” ์—†์–ด๋„ ๋˜์—ˆ์œผ๋‚˜, Gradle 5.X์—์„œ AnnotaionProcessor๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉฐ ์„ค์ •์ด ํ•„์š”ํ•ด์กŒ๋‹ค. */
compileQuerydsl {
 options.annotationProcessorPath = configurations.querydsl
}

/*๐Ÿ“Œ complie ํƒ€์ž„์— JPA ์˜์กด์„ฑ์— ์ ‘๊ทผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •(์ƒ์†)์„ ์ถ”๊ฐ€ํ•œ๋‹ค. */
/* ๊ธฐ์กด์—๋Š” ์—†์–ด๋„ ๋˜์—ˆ์œผ๋‚˜, Gradle 6.X์—์„œ compileClasspath๊ฐ€ ์ƒ๊ธฐ๋ฉฐ ํ•„์š”ํ•ด์กŒ๋‹ค. */
configurations {
 querydsl.extendsFrom compileClasspath
}

 

๐Ÿ“‘ ์ด์ „ ๋นŒ๋“œ์— ์ƒ์„ฑ๋œ QClass ์‚ญ์ œ ์ž๋™ํ™”

์—”ํ‹ฐํ‹ฐ ์œ„์น˜๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋  ๊ฒฝ์šฐ ๊ธฐ์กด ์ฟผ๋ฆฌ ํƒ€์ž…(Qํด๋ž˜์Šค)๋ฅผ ์‚ญ์ œํ•ด์ฃผ์–ด์•ผ ์ •์ƒ ๋™์ž‘ํ•œ๋‹ค.

์ด๋Š” ์ง์ ‘ ์ฐพ์•„์„œ ์‚ญ์ œํ•ด๋„ ๋˜์ง€๋งŒ, ์•„๋ž˜์™€ ๊ฐ™์ด ์ž๋™ํ™”ํ•ด์ฃผ๋ฉด ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๋‹ค.

์ฐธ๊ณ ๋กœ Gradle ๋นŒ๋“œ์˜ ๊ฒฝ์šฐ, ์ƒˆ๋กœ ๋นŒ๋“œํ•˜๋ฉด ๊ธฐ์กด ๋นŒ๋“œํŒŒ์ผ์„ ์‚ญ์ œํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์€ ์ž‘์—…์„ ์•ˆํ•ด์ค˜๋„ ๋œ๋‹ค.

/** gradle clean ํƒœ์Šคํฌ ์‹คํ–‰์‹œ ๊ธฐ์กด์˜ QClass ์‚ญ์ œ */
clean {
    delete file('src/main/generated') // ์ธํ…”๋ฆฌ์ œ์ด Annotation processor ์ƒ์„ฑ๋ฌผ ์ƒ์„ฑ์œ„์น˜
}

๋งŒ์•ฝ Clean ์‹คํ–‰์‹œ ํŒŒ์ผ์ด ์‚ญ์ œ๋˜๋Š”๊ฒŒ ๋ถˆํŽธํ•˜๋‹ค๋ฉด, ์œ„ ์ฝ”๋“œ๋ฅผ ๋„ฃ์ง€๋ง๊ณ  ์•„๋ž˜์ฒ˜๋Ÿผ gradle-task๋ฅผ ์ถ”๊ฐ€ํ•ด์ค˜๋„ ๋œ๋‹ค.

/** ์ธํ…”๋ฆฌ์ œ์ด Annotation processor ์— ์ƒ์„ฑ๋˜๋Š” 'src/main/generated' ๋””๋ ‰ํ„ฐ๋ฆฌ ์‚ญ์ œ */
task cleanGeneatedDir(type: Delete) { 
    delete file('src/main/generated')
}

 

์ฐธ๊ณ ๋กœ ์ „์ฒด ๋นŒ๋“œ๋ฅผ ํ•˜์ง€์•Š๋”๋ผ๋„, Intellij๋ฅผ ์“ด๋‹ค๋ฉด ์šฐ์ธก์ƒ๋‹จ Gradleํƒญ์—์„œ compileQuerydsl์„ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” ํ”„๋กœ์ ํŠธ๊ฒฝ๋กœ/src/main/generated ์— ์ƒ์„ฑ๋œ๋‹ค. ๊ทธ๋ž˜๋“ค๋กœ ์ƒ์„ฑํ–ˆ๋‹ค๋ฉด build๋ฅผ ์—ด์–ด๋ณด๋ฉด ์žˆ๋‹ค.

์œ„์—์„œ Gradle SourceSet์„ ์„ค์ •ํ•ด์ฃผ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋นŒ๋“œ ํ›„์— IDE์—์„œ ์ •์ƒ์ ์œผ๋กœ QClass๋ฅผ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 


๐Ÿ’ญ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

๋นŒ๋“œ ํ›„, ์ƒ์„ฑ๋œ Q-Type ( QMember )๋ฅผ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค.

์šฐ์„  ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ํ’€์–ด์“ฐ๋ฉด, ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

@Test
void querydsl() {
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);
    QMember member = new QMember("nick"); // ๋ณ„๋ช…์€ ๊ฐ™์€ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค. ๊ทธ ์™ธ ํ•„์š” X

    Member findMember = queryFactory
        .select(member)
        .from(member)
        .where(member.username.eq("member1"))
        .fetchOne();

    assertThat(findMember.getUsername()).isEqualTo("member1");
}

์ด ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๋˜‘๊ฐ™์€ JPQL์„ ์ƒ์„ฑํ•ด๋‚ธ๋‹ค.

@Test
void jpql(){
    var findMember = em.createQuery(
            "select m from Member m where m.username = :username", Member.class)
        .setParameter("username", "member1")
        .getSingleResult();

    assertThat(findMember.getUsername()).isEqualTo("member1");
}

 

์ด๋ฅผ ์กฐ๊ธˆ ๋” ๊น”๋”ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด๋ณด์ž.

  • querydsl . JPAQueryFactory๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ(em)์„ ๋ฐ›์•„์„œ ์‚ฌ์šฉํ•œ๋‹ค.
    ์Šคํ”„๋ง์—์„œ๋Š” ํŠธ๋žœ์žญ์…˜ ๋‹จ์œ„๋กœ em์„ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•ด๋„ ๊ดœ์ฐฎ๋‹ค. (๋™์‹œ์„ฑ ๋ฌธ์ œ์— ์•ˆ์ „ํ•˜๋‹ค.)
JPAQueryFactory queryFactory;
 
@BeforeEach
public void before() {
    queryFactory = new JPAQueryFactory(em);
}

 

  • QMember ์ฝ”๋“œ๋ฅผ ๋œฏ์–ด๋ณด๋ฉด, ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด๋ผ๊ณ  static QMember member = new QMember() ๋ฅผ ์ œ๊ณตํ•ด์ค€๋‹ค.
    ์ฐธ๊ณ ๋กœ QMember("~") ์•ˆ์— ๋“ค์–ด๊ฐ€๋Š”๊ฑด ๋ณ„๋ช…์ด๋‹ค. ํ‰์†Œ์—” ํ•„์š”์—†๊ณ , ๊ฐ™์€ ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

์ด๋ฅผ ์ด์šฉํ•˜๋ฉด QMember๋ฅผ ๋งค๋ฒˆ ๋งŒ๋“ค์ง€ ์•Š๊ณ ๋„, static import๋กœ ๋ถˆ๋Ÿฌ์™€ ์•„๋ž˜์™€ ๊ฐ™์ด ๊น”๋”ํ•œ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

import static mypackage.domain.QMember.member;

@Test
void querydsl() throws Exception {
    Member findMember = queryFactory
        .select(member) // import static QMember.member
        .from(member)
        .where(member.username.eq("member1"))
        .fetchOne();

    assertThat(findMember.getUsername()).isEqualTo("member1");
}

 


๐Ÿ’ญ ๊ฒ€์ƒ‰ ์กฐ๊ฑด ์ฟผ๋ฆฌ

JPQL์ด ์ง€์›ํ•˜๋Š” ๋ชจ๋“  ๊ฒ€์ƒ‰์กฐ๊ฑด์„ ๋ฉ”์„œ๋“œ๋กœ ์ง€์›ํ•œ๋‹ค.

member.username.eq("member1") // username = 'member1'
member.username.ne("member1") //username != 'member1'
member.username.eq("member1").not() // username != 'member1'
member.username.isNotNull() //์ด๋ฆ„์ด is not null

member.age.in(10, 20) // age in (10,20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10,30) //between 10, 30

member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30

member.username.like("member%") //ํ•ด๋‹น ๋ฌธ๊ตฌ๋ฅผ like ๊ฒ€์ƒ‰
member.username.contains("member") // like ‘%member%’ ๊ฒ€์ƒ‰
member.username.startsWith("member") //like ‘member%’ ๊ฒ€์ƒ‰

 

์—ฌ๋Ÿฌ ์กฐ๊ฑด์„ .and() ๋‚˜ .or()์œผ๋กœ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

@Test
void search() {
    Member findMember = queryFactory
        .selectFrom(member) // select(member).from(member)๋ฅผ ํ•ฉ์นœ ๋ฉ”์„œ๋“œ
        .where( member.username.eq("member1")
            .and(member.age.eq(10)) )
        .fetchOne();

    assertThat(findMember.getUsername()).isEqualTo("member1");
}

 

๋’ค์— ์„ค๋ช…ํ• ๊ฑฐ์ง€๋งŒ, where(~) ์•ˆ์— ํŒŒ๋ผ๋ฉ”ํƒ€๋กœ ์ „๋‹ฌํ•˜๋ฉด AND ์กฐ๊ฑด์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.

์ฐธ๊ณ ๋กœ ์กฐ๊ฑด์— null ๊ฐ’์ด ๋“ค์–ด๊ฐ€๋ฉด ํ•ด๋‹น ์กฐ๊ฑด์„ ๋ฌด์‹œํ•œ๋‹ค. ์ด๋ฅผ ์ด์šฉํ•ด ๋™์ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ํŽธํ•˜๋‹ค.

List<Member> result1 = queryFactory
    .selectFrom(member)
    .where(member.username.eq("member1"), member.age.eq(10)) // .and()์™€ ๋™์ผ
    .fetch();

 

์ฐธ๊ณ ๋กœ distinct()๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด select ๋’ค์— ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

์ด๋Š” JPQL์˜ distinct์™€ ๋™์ž‘์ด ์™„์ „ํžˆ ๊ฐ™๋‹ค.

List<String> result = queryFactory
        .select(member.username)
        .distinct()
        .from(member)
        .fetch();

๐Ÿ’ญ ๊ฒฐ๊ณผ ์กฐํšŒ

//List
List<Member> fetch = queryFactory
    .selectFrom(member)
    .fetch();
    
//๋‹จ ๊ฑด
Member findMember1 = queryFactory
    .selectFrom(member)
    .fetchOne();

//์ฒ˜์Œ ํ•œ ๊ฑด ์กฐํšŒ .limit(1).fetchOne()๊ณผ ๋™์ผํ•œ ๋™์ž‘
Member findMember2 = queryFactory
    .selectFrom(member)
    .fetchFirst();
  • fetch() ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ List<T>๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. (๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋‹ค๋ฉด ๋นˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.)

 

  • fetchOne()์„ ์‚ฌ์šฉํ•˜๋ฉด ํ•œ๊ฑด๋งŒ ๊ฐ€์ ธ์˜จ๋‹ค. (์—†์œผ๋ฉด null)
    JPA์—์„œ๋Š” 2๊ฑด ์กฐํšŒ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๋ฐ, ์Šคํ”„๋ง๊ณผ ๋‹ค๋ฅด๊ฒŒ QueryDsl์€ ์ด ์˜ˆ์™ธ๋ฅผ ๊ฐ์‹ธ์„œ ๋˜์ง„๋‹ค.

๊ฐ™์€ ์ด๋ฆ„์ธ com.querydsl.core.NonUniqueResultException(e) ์˜ˆ์™ธ๋กœ ๊ฐ์‹ผ๋‹ค.

 

  • fetchFirst()๋Š” fetchOne()๊ณผ ๋™์ผํ•˜๋‹ค. ์ฐจ์ด์ ์€ ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ์กฐํšŒ๋˜๋”๋ผ๋„ ํ•˜๋‚˜๋งŒ ๊ฐ€์ ธ์˜ค๋Š”๊ฑฐ ์ •๋„.

์‹ค์ œ ์ฝ”๋“œ๋„ ์ด๋ ‡๋‹ค.

 

์ฒ˜์Œ์—๋Š” ํŽ˜์ด์ง€(fetchResults)๋‚˜ ์ „์ฒด ๊ฐœ์ˆ˜(fetchCount)๋„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ, ์ง€๊ธˆ์€ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ ์—†์–ด์กŒ๋‹ค.

์ตœ์ ํ™” ๋ถ€๋ถ„๋„ ๊ทธ๋ ‡๊ณ , ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์—์„œ Count ์ฟผ๋ฆฌ์˜ ์„ฑ๋Šฅ์ด ๋‚˜๋น ์ ธ์„œ ๊ทธ๋ ‡๋‹ค๊ณ  ํ•œ๋‹ค.

๋”๋ณด๊ธฐ

์•„๋ž˜ ๊ธฐ๋Šฅ๋“ค์€ ํ˜„์žฌ Deprecated ๋˜์–ด์žˆ๋‹ค.

//ํŽ˜์ด์ง•์—์„œ ์‚ฌ์šฉ
QueryResults<Member> results = queryFactory
    .selectFrom(member)
    .fetchResults();
    
//count ์ฟผ๋ฆฌ๋กœ ๋ณ€๊ฒฝ
long count = queryFactory
    .selectFrom(member)
    .fetchCount();
@Test
public void paging2() {
    QueryResults<Member> queryResults = queryFactory
        .selectFrom(member)
        .orderBy(member.username.desc())
        .offset(1)
        .limit(2)
        .fetchResults();
    assertThat(queryResults.getTotal()).isEqualTo(4);
    assertThat(queryResults.getLimit()).isEqualTo(2);
    assertThat(queryResults.getOffset()).isEqualTo(1);
    assertThat(queryResults.getResults().size()).isEqualTo(2);
}

๊ฐ„๋‹จํ•œ Count ๊ฐ’์ด ํ•„์š”ํ•˜๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜์ž.

Long totalCount = queryFactory
    .select(member.count()) // select count(member.id)
    .from(member)
    .fetchOne();

Long totalCount = queryFactory
    .select(Wildcard.count) // select count(*)
    .from(member)
    .fetchOne();

 


๐Ÿ’ญ ์ •๋ ฌ

orderBy() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ์ •๋ง ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ฐ€๋Šฅํ•˜๋‹ค.

List<Member> result = queryFactory
    .selectFrom(member)
    .where(member.age.eq(100))
    .orderBy(member.age.desc(), member.username.asc())
    // 1์ˆœ์œ„ ํšŒ์›๋‚˜์ด ๋‚ด๋ฆผ์ฐจ์ˆœ - 2์ˆœ์œ„ ํšŒ์›์ด๋ฆ„ ์˜ค๋ฆ„์ฐจ์ˆœ
    .fetch();

๋‹จ, ํ•„๋“œ null ๊ฐ’์€ ์˜ค๋ฆ„์ฐจ์ˆœ(asc)๋Š” ์ œ์ผ ์•ž์œผ๋กœ, ๋‚ด๋ฆผ์ฐจ์ˆœ(desc)์ด๋ผ๋ฉด ์ œ์ผ ๋’ค๋กœ ์ •๋ ฌ๋œ๋‹ค.

์ด ์ •๋ ฌ ์ˆœ์„œ๋ฅผ ๋ฐ”๊พธ๊ณ  ์‹ถ๋‹ค๋ฉด .nullsLast()  .nullsFirst() ๋ฅผ ์ถ”๊ฐ€๋กœ ์„ค์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

List<Member> result = queryFactory
    .selectFrom(member)
    .where(member.age.eq(100))
    .orderBy(member.age.desc(), member.username.asc().nullsLast()) // ๋ฌด์กฐ๊ฑด null์ด ๋’ค๋กœ๊ฐ.
    .fetch();

 

 


๐Ÿ’ญ ํŽ˜์ด์ง• (Paging)

์ด๊ฒƒ๋„ ๋”ฑํžˆ ์„ค๋ช…ํ• ๊ฒŒ ์—†๋‹ค. offset()๊ณผ limit() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

๋‹จ, ์ด๋ ‡๊ฒŒ ์“ฐ๋ฉด ๋‹น์—ฐํžˆ count ์ฟผ๋ฆฌ๊ฐ€ ๋ณ„๋„๋กœ ์‹คํ–‰๋œ๋‹ค. ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด ํ•ด๋‹น ์ฟผ๋ฆฌ๋ฅผ ๋ณ„๋„๋กœ ์ž‘์„ฑํ•˜๋Š”๊ฒŒ ์ข‹๋‹ค.

๋งŒ์•ฝ ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•ด์„œ ํŽ˜์ด์ง•ํ–ˆ๋‹ค๋ฉด, count ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋„ ํ•„์š”์—†๋Š” ์กฐ์ธ์ด ๋ฐœ์ƒํ•œ๋‹ค. = ์„ฑ๋Šฅ์ƒ ์ข‹์ง€์•Š๋‹ค.

@Test
public void paging1() {
    List<Member> result = queryFactory
        .selectFrom(member)
        .orderBy(member.username.desc())
        .offset(1) //0๋ถ€ํ„ฐ ์‹œ์ž‘(zero index)
        .limit(2) //์ตœ๋Œ€ 2๊ฑด ์กฐํšŒ
        .fetch();
    assertThat(result.size()).isEqualTo(2);
}

 


๐Ÿ’ญ ์ง‘ํ•ฉ ( COUNT , SUM, AVG, MAX, MIN )

.select()์—์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ์ฐธ๊ณ ๋กœ ์ง‘ํ•ฉ์œผ๋กœ ์กฐํšŒํ•˜๋ฉด Querydsl์˜ Tuple ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜๋œ๋‹ค.

public interface Tuple { // import com.querydsl.core.Tuple;

    @Nullable
    <T> T get(int index, Class<T> type);

    @Nullable
    <T> T get(Expression<T> expr);

    int size();

    Object[] toArray();
}

 

๋‹ค๋งŒ ์‹ค๋ฌด์—์„œ๋Š” Tuple๋ณด๋‹ค๋Š” DTO๋กœ ๋ฐ›์•„์˜ค๋Š” ๊ฑธ ๋” ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋Š” ๋’ค์—์„œ ์•Œ์•„๋ณด๋„๋ก ํ•˜์ž.

@Test
public void aggregation() throws Exception {
    List<Tuple> result = queryFactory
        .select(member.count(),
            member.age.sum(),
            member.age.avg(),
            member.age.max(),
            member.age.min())
        .from(member)
        .fetch();
    Tuple tuple = result.get(0);
    
    assertThat(tuple.get(member.count())).isEqualTo(4);
    assertThat(tuple.get(member.age.sum())).isEqualTo(100);
    assertThat(tuple.get(member.age.avg())).isEqualTo(25);
    assertThat(tuple.get(member.age.max())).isEqualTo(40);
    assertThat(tuple.get(member.age.min())).isEqualTo(10);
}

 


๐Ÿ’ญ groupBy, having

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@Test
public void group() throws Exception {
	// [ํŒ€์˜ ์ด๋ฆ„, ๊ฐ ํŒ€์˜ ํ‰๊ท ์—ฐ๋ น]์„ ๊ตฌํ•ด๋ผ
    List<Tuple> result = queryFactory
        .select(team.name, member.age.avg())
        .from(member)
        .join(member.team, team)
        .groupBy(team.name)
        .fetch();
    
    Tuple teamA = result.get(0);
    Tuple teamB = result.get(1);
    
    assertThat(teamA.get(team.name)).isEqualTo("teamA");
    assertThat(teamA.get(member.age.avg())).isEqualTo(15);
    assertThat(teamB.get(team.name)).isEqualTo("teamB");
    assertThat(teamB.get(member.age.avg())).isEqualTo(35);
}

 

๋‹น์—ฐํžˆ having๋„ ์ง€์›ํ•œ๋‹ค.

groupBy()๋กœ ๊ทธ๋ฃน์„ ๋งŒ๋“  ๊ฒฐ๊ณผ๋“ค์„ ํ•„ํ„ฐ๋งํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด .having() ์„ ์‚ฌ์šฉํ•˜์ž.

.groupBy(item.price) // ๊ทธ๋ฃน์œผ๋กœ ๋‚˜๋ˆ„๊ณ 
.having(item.price.gt(1000)) // ๊ฐ ๊ทธ๋ฃน์€ price >= 1000 ์ธ ์•„์ดํ…œ๋งŒ ํฌํ•จ

 


๐Ÿ’ญ ์กฐ์ธ

๐Ÿ“‘ Inner Join, Outer Join

์กฐ์ธ ๋Œ€์ƒ๊ณผ, ์กฐ์ธํ•  Q-Type์„ ์ง€์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

JPQL๊ณผ ๋™์ผํ•˜๊ฒŒ ๊ธฐ๋ณธ์€ Inner Join์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.

@Test
public void join() throws Exception {
    QMember member = QMember.member;
    QTeam team = QTeam.team;

    // Member.team = "teamA" ์ธ ๋ชจ๋“  ํšŒ์› ์กฐํšŒ
    List<Member> result = queryFactory
        .select(member)
        .from(member)
        .join(member.team, team) // Member.team ์„ Team ์—”ํ‹ฐํ‹ฐ(QTeam.team)๊ณผ ์กฐ์ธํ•œ๋‹ค.
        .where(team.name.eq("teamA"))
        .fetch();

    assertThat(result)
        .extracting("username")
        .containsExactly("member1", "member2");
}

๋งŒ์•ฝ ๊ฐ™์€ ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•ด์•ผํ•œ๋‹ค๋ฉด QMember other = new QMember("๋ณ„๋ช…")์„ ๋งŒ๋“ค์–ด์„œ ์กฐ์ธํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

JPQL์€ ์ด๋ ‡๊ฒŒ ์ฐํžŒ๋‹ค. inner join

 

leftJoin(), rightJoin() ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ง€์›ํ•œ๋‹ค.

List<Member> result = queryFactory
    .selectFrom(member)
    .leftJoin(member.team, team) // Member.team ์„ Team ์—”ํ‹ฐํ‹ฐ(QTeam.team)๊ณผ ์กฐ์ธํ•œ๋‹ค.
    .where(team.name.eq("teamA"))
    .fetch();

์ƒ์„ฑ๋œ JPQL

 

๐Ÿ“‘ Theta Join

์ฐธ๊ณ ๋กœ ์„ธํƒ€์กฐ์ธ์€ ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์—†๋Š”(๊ณตํ†ต ๊ฐ’์ด ์—†๋Š”) ๋‘ ๊ฐ์ฒด๋ฅผ ์กฐ์ธํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

๊ณผ๊ฑฐ์˜ JPA(ํ•˜์ด๋ฒ„๋„ค์ดํŠธ)์—์„œ๋Š” ๋ถˆ๊ฐ€๋Šฅํ–ˆ๋Š”๋ฐ, ์ตœ์‹  ๋ฒ„์ „์—๋Š” ์ด๋Ÿฌํ•œ ๋ง‰ ์กฐ์ธ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

List<Member> result = queryFactory
    .select(member)
    .from(member, team) // ๊ทธ๋ƒฅ ๋‘ ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•œ๋‹ค.
    .where(member.username.eq(team.name)) // member.username == team.name
    .fetch();

์ด๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ ๋‹ค. ์ฐธ๊ณ ๋กœ ์ด๋Ÿฐ ์ฟผ๋ฆฌ๋Š” ๋ณดํ†ต DB์—์„œ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ํ•ด์ค€๋‹ค.

/* JPQL
select
    member1 
from
    Member member1,
    Team team 
where
    member1.username = team.name
*/    

select
    member0_.member_id as member_i1_0_,
    member0_.age as age2_0_,
    member0_.team_id as team_id4_0_,
    member0_.username as username3_0_ 
from
    member member0_ cross 
join
    team team1_ 
where
    member0_.username=team1_.name

๋‹ค๋งŒ ์ด ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” Inner ์กฐ์ธ๋งŒ ๊ฐ€๋Šฅํ•˜๊ณ , Outer ์กฐ์ธ์€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

ํ•„์š”ํ•˜๋‹ค๋ฉด ์•„๋ž˜์˜ on์ ˆ์„ ํ™œ์šฉํ•ด์„œ ํ•ด๊ฒฐํ•˜๋ฉด ๋œ๋‹ค.

 

 

๐Ÿ“‘ ์กฐ์ธ on ์ ˆ

์ฐธ๊ณ ๋กœ ์ด๋Š” JPA2.1 (ํ•˜์ด๋ฒ„๋„ค์ดํŠธ 5.1)๋ถ€ํ„ฐ ์ง€์›ํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

์กฐ์ธ ์—”ํ‹ฐํ‹ฐ(ํ…Œ์ด๋ธ”)๋ฅผ ํ•„ํ„ฐ๋งํ•˜๊ฑฐ๋‚˜, ์œ„์—์„œ ๋งํ•œ๋Œ€๋กœ ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์—†๋Š” ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐ์ธํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

-- ํšŒ์›๊ณผ ํŒ€์„ ์กฐ์ธํ•˜๋ฉด์„œ, [ํŒ€ ์ด๋ฆ„์ด teamA์ธ ํŒ€]๋งŒ ์กฐ์ธ, ํšŒ์›์€ ๋ชจ๋‘ ์กฐํšŒ
-- JPQL
SELECT m, t 
FROM Member m 
LEFT JOIN m.team t 
ON t.name = 'teamA'

-- SQL
SELECT m.*, t.* 
FROM Member m 
LEFT JOIN Team t 
ON m.TEAM_ID=t.id and t.name='teamA

 

๋ณด์ด๋Š” ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด๋œ๋‹ค. ๊ฐ„๋‹จํ•˜๋‹ค.

@Test
public void join_on_filtering() throws Exception {
    List<Tuple> result = queryFactory
        .select(member, team)
        .from(member)
        .leftJoin(member.team, team)
        .on(team.name.eq("teamA"))
        .fetch();

    for (Tuple tuple : result) {
        System.out.println("tuple = " + tuple);
    }

}

Member ์™€ [Team on team.name = 'teamA']์„  LEFT JOIN ํ–ˆ์œผ๋ฏ€๋กœ, ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋‚˜์˜จ๋‹ค.

team์€ ์ด๋ฆ„์ด "teamA"์ธ ๋ฐ์ดํ„ฐ๋งŒ ์กฐ์ธ๋˜์—ˆ๋‹ค. ( On t.name = 'teamA' )

 

๋‹น์—ฐํ•œ ๋ง์ด๊ธดํ•œ๋ฐ, Inner ์กฐ์ธ์—์„œ on์„ ์‚ฌ์šฉํ•˜๋Š”๊ฑด ๊ทธ๋ƒฅ where()์„ ์“ฐ๋Š”๊ฒƒ๊ณผ ๊ฒฐ๊ณผ๊ฐ€ ๋™์ผํ•˜๋‹ค.

Inner Join์€ ๊ตณ์ด ๋ณต์žกํ•˜๊ฒŒ on ์ ˆ์„ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. ๊ทธ๋ƒฅ where์„ ์“ฐ์ž.

List<Tuple> result = queryFactory
    .select(member, team)
    .from(member)
    .join(member.team, team) // inner Join
    .where(team.name.eq("teamA"))
//  .on(team.name.eq("teamA")) ์™€ ๋˜‘๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ๋‹ค.
    .fetch();

 

 

์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์—†๋Š” ์—”ํ‹ฐํ‹ฐ๋Š” on์„ ์ด์šฉํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด ์กฐ์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฌผ๋ก  Inner ์กฐ์ธ( .join() )์€ ์œ„์—์„œ ๋งํ•œ๋Œ€๋กœ from(member, team).where(...)์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

@Test
public void join_on_no_relation() throws Exception {
    em.persist(Member.of("teamA", 11));
    em.persist(Member.of("teamB", 11));

    List<Tuple> result = queryFactory
        .select(member, team)
        .from(member)
        .leftJoin(team) // Left Outer ์„ธํƒ€ ์กฐ์ธ
        .on(member.username.eq(team.name))
        .fetch();

    for (Tuple tuple : result) {
        System.out.println("t=" + tuple);
    }
}

 

LEFT OUTER ์กฐ์ธ์œผ๋กœ ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์—†๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”๋‹ค.

์‹ค์ œ ์ฟผ๋ฆฌ๋ฅผ ์ฐ์–ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

on ์„ ์ด์šฉํ•ด์„œ, ์—ฐ๊ด€๊ด€๊ณ„ ์—†์ด ์กฐํšŒํ•œ ๋ชจ์Šต.

 

 

๐Ÿ“‘ Join fetch (ํŒจ์น˜์กฐ์ธ)

์ฐธ๊ณ ๋กœ join fetch๋Š” JPQL์—๋งŒ ์กด์žฌํ•˜๋Š” ๋ฌธ๋ฒ•์ด๋‹ค. ์—ฐ๊ด€๊ด€๊ณ„์ธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ฟผ๋ฆฌํ•œ๋ฐฉ์— ๋‹ค ๊ฐ™์ด ๊ฐ€์ ธ์˜จ๋‹ค.

(๊ทธ๋ƒฅ ์กฐ์ธ๋งŒํ•˜๋ฉด ์กฐ์ธ ์กฐ๊ฑด์œผ๋กœ๋งŒ ์‚ฌ์šฉํ•˜๊ณ , ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€๋Š” ์•Š๋Š”๋‹ค.)

 

์‚ฌ์šฉ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜๋‹ค. ์กฐ์ธ๋’ค์— .fetchJoin() ์„ ๋ถ™์—ฌ์ฃผ๋ฉด ๋œ๋‹ค.

@Test
public void fetchJoinUse() throws Exception {
    em.flush();
    em.clear();
    
    Member findMember = queryFactory
        .selectFrom(member)
        .join(member.team, team).fetchJoin()
        .where(member.username.eq("member1"))
        .fetchOne();
 
    
    boolean isFetchJoin = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
 
    assertThat(isFetchJoin).as("ํŒจ์น˜ ์กฐ์ธ ์ ์šฉ").isTrue();
}

 

๋‹น์—ฐํ•œ๊ฑฐ์ง€๋งŒ, ์œ„์™€ ๊ฐ™์ด fetchJoin()์„ ๋ถ™์—ฌ์ฃผ์ง€ ์•Š์œผ๋ฉด ํŒจ์น˜์กฐ์ธ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ง€์—ฐ ๋กœ๋”ฉ๋œ๋‹ค.

// ํŒจ์น˜์กฐ์ธ ๋˜์ง€์•Š์Œ. ๊ทธ๋ƒฅ ์ง€์—ฐ๋กœ๋”ฉ (Lazy ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ฟผ๋ฆฌ๋ฐœ์ƒ)
Member findMember = queryFactory
    .selectFrom(member)
    .where(member.username.eq("member1"))
    .fetchOne();

 

 

๐Ÿ“‘ ์„œ๋ธŒ ์ฟผ๋ฆฌ where (select ... from ...)

QueryDsl์˜ JPAexpressions ๊ฐ์ฒด๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์–ด๋ ต์ง€ ์•Š๋‹ค.

@Test
public void subQuery() throws Exception {
    QMember memberSub = new QMember("memberSub");
	
    /* Where ์ ˆ ์„œ๋ธŒ์ฟผ๋ฆฌ */
    List<Member> result = queryFactory
        .select(member)
        .from(member)
        .where(member.age.eq(
            JPAExpressions
                .select(memberSub.age.max())
                .from(memberSub)
        ))
        .fetch();

    assertThat(result).extracting("age")
        .containsExactly(40); // oldest age.
}

์ฐธ๊ณ ๋กœ ์ฟผ๋ฆฌ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ฐํžŒ๋‹ค.

-- JPQL
select
    member1 
from
    Member member1 
where
    member1.age = (
        select
            max(memberSub.age) 
        from
            Member memberSub
    )
-- SQL
select
    member0_.member_id as member_i1_0_,
    member0_.age as age2_0_,
    member0_.team_id as team_id4_0_,
    member0_.username as username3_0_ 
from
    member member0_ 
where
    member0_.age=(
        select
            max(member1_.age) 
        from
            member member1_
    )

 

JPA ํ‘œ์ค€์—๋Š” Select ์„œ๋ธŒ์ฟผ๋ฆฌ๊ฐ€ ์—†์ง€๋งŒ, ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๋Š” ์ง€์›ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

/* select ์ ˆ ์„œ๋ธŒ์ฟผ๋ฆฌ */
QMember memberSub = new QMember("memberSub");

List<Tuple> result = queryFactory
    .select(member.username,
        JPAExpressions
            .select(memberSub.age.avg())
            .from(memberSub))
    .from(member)
    .fetch();

/* JPQL */ SQL

 

 

๐Ÿงจ JPA์—์„œ๋Š” ์ธ๋ผ์ธ๋ทฐ(from ์ ˆ ์„œ๋ธŒ์ฟผ๋ฆฌ)๊ฐ€ ๋ถˆ๊ฐ€๋Šฅ

QueryDsl์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ, ํ•˜์ด๋ฒ„๋„ค์ดํŠธ์—์„œ From ์ ˆ ์„œ๋ธŒ์ฟผ๋ฆฌ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ด๋Š” ์กฐ์ธ์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ฑฐ๋‚˜, ์ฟผ๋ฆฌ๋ฅผ 2๋ฒˆ์œผ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ํ•ด๊ฒฐํ•˜๋„๋ก ํ•˜์ž.

 

๋Œ€๊ทœ๋ชจ ์„œ๋น„์Šค๋ผ, ์„ฑ๋Šฅ์— ๋ฏผ๊ฐํ•ด์„œ ๊ผญ ์จ์•ผํ•œ๋‹ค๋ฉด JPA๋ฅผ ํฌ๊ธฐํ•˜๊ณ  ์Œฉ ์ฟผ๋ฆฌ(JDBC, Mybatis)๋ฅผ ์“ฐ๋Š” ๊ฑธ ์ถ”์ฒœํ•œ๋‹ค.

 

๋‹ค๋งŒ ์ด๋Ÿฐ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค SQL์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ํฌํ•จ๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

์„ค๊ณ„์ ์ธ ์ธก๋ฉด์—์„œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ•ด์•ผํ•  ์ผ์„ DB์— ์œ„์ž„ํ•˜๋Š”๊ฒŒ ์•„๋‹Œ๊ฐ€ ๊ณ ๋ฏผํ•ด๋ณผ ํ•„์š”๋Š” ์žˆ๋‹ค.

 


๐Ÿ’ญ Case ์กฐ๊ฑด๋ฌธ (์ž˜ ์‚ฌ์šฉ์•ˆํ•˜๋Š” ๊ธฐ๋Šฅ)

switch๋ฌธ์ฒ˜๋Ÿผ, ๊ฒฐ๊ณผ๊ฐ’์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

๋‹ค๋งŒ ๋ณดํ†ต DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์„œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์‚ฌ์šฉํ•  ์ผ์€ ๊ฑฐ์˜ ์—†๊ธดํ•˜๋‹ค.

  • ๊ฐ„๋‹จํ•œ ์กฐ๊ฑด์€ ์•„๋ž˜์™€ ๊ฐ™์ด when().then() , otherwise()๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
@Test
void case_condition1() throws Exception {
    List<String> result = queryFactory
        .select(member.age
            .when(10).then("์—ด์‚ด") // case 10: return "์—ด์‚ด"
            .when(20).then("์Šค๋ฌด์‚ด") // case 20: return "์Šค๋ฌด์‚ด"
            .otherwise("๊ธฐํƒ€")) // default: return "๊ธฐํƒ€"
        .from(member)
        .fetch();

    for (String s : result) {
        System.out.println("s = " + s);
    }
}

m(10), m(20), m(30), m(40)

  • ์กฐ๊ฑด์ด ๋”ฑ ๋–จ์–ด์ง€์ง€ ์•Š๊ณ , ๋ณต์žกํ•˜๋‹ค๋ฉด QueryDsl์˜ CaseBuilder๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
@Test
void case_condition2() throws Exception {
    List<String> result = queryFactory
        .select(new CaseBuilder()
            .when(member.age.between(0, 20)).then("0~20์‚ด")
            .when(member.age.between(21, 30)).then("21~30์‚ด")
            .otherwise("๊ธฐํƒ€"))
        .from(member)
        .fetch();

    for (String s : result) {
        System.out.println("memo = " + s);
    }
}

 

  • Order By๋ฅผ ๋ณต์žกํ•œ ์กฐ๊ฑด์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋• Expression ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค.
@Test
void case_condition3() throws Exception {

    NumberExpression<Integer> rankPath = new CaseBuilder()
        .when(member.age.between(0, 20)).then(2) // 2์ˆœ์œ„
        .when(member.age.between(21, 30)).then(1) // 1์ˆœ์œ„
        .otherwise(3); // 3์ˆœ์œ„

    List<Tuple> result = queryFactory
        .select(member.username, member.age, rankPath)
        .from(member)
        .orderBy(rankPath.desc())
        .fetch();

    for (Tuple tuple : result) {
        String username = tuple.get(member.username);
        Integer age = tuple.get(member.age);
        Integer rank = tuple.get(rankPath);
        System.out.println("username = " + username + " age = " + age + " rank = " + rank);
    }
}
username = member4 age = 40 rank = 3 // 3์ˆœ์œ„ (๊ทธ ์™ธ)
username = member1 age = 10 rank = 2 // 2์ˆœ์œ„ (0~20)
username = member2 age = 20 rank = 2 // 2์ˆœ์œ„ (0~20)
username = member3 age = 30 rank = 1 // 1์ˆœ์œ„ (21~30)

 

 

๐Ÿ’ญ ๊ฒฐ๊ณผ ๊ฐ’์— ์ƒ์ˆ˜, ๋ฌธ์ž ์ถ”๊ฐ€ํ•˜๊ธฐ

์šฐ์„ , ๋‹จ์ˆœํžˆ ์ƒ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š”๊ฑด QueryDsl์—์„œ ์ตœ์ ํ™”๊ฐ€ ์ด๋ฃจ์–ด์ง„๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ์ƒ์ˆ˜์— ๊ด€ํ•œ JPQL (SQL constant)๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

Tuple result = queryFactory
    .select(member.username, Expressions.constant("A"))
    .from(member)
    .fetchFirst();
    
/* ๊ฒฐ๊ณผ๊ฐ’
    ["name1", "A"]
    ["name2", "A"]
    ["name3", "A"]...
*/

๋ฌผ๋ก  ๋ณต์žกํ•˜๊ฒŒ ์ƒ์ˆ˜๊ฐ’๊ณผ ๊ณ„์‚ฐ์„ ํ•ด๋ฒ„๋ฆฌ๋ฉด, SQL constant๋กœ ๊ฐ’์„ ๋„˜๊ธด๋‹ค.

 

๊ฒฐ๊ณผ๊ฐ’์— ํŠน์ • ๋ฌธ์ž์—ด์„ ๋”ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, concat() ๊ณผ stringValue() ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

๋‚˜์ค‘์— ๋ฌธ์ž๊ฐ€ ์•„๋‹Œ ํƒ€์ž…๋“ค (enum๋“ฑ)์„ ์ฒ˜๋ฆฌํ•  ๋•Œ, ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

String result = queryFactory
    .select(member.username.concat("_").concat(member.age.stringValue()))
    .from(member)
    .where(member.username.eq("member1"))
    .fetchOne();

์ด๋ ‡๊ฒŒ ์ž˜ ๋ถ™์–ด์„œ ๋‚˜์˜จ๋‹ค.

 

 


๐Ÿ’ญ ํ”„๋กœ์ ์…˜ ( Tuple, DTO ์กฐํšŒ )

์ฐธ๊ณ ๋กœ ํ”„๋กœ์ ์…˜์€ ํ…Œ์ด๋ธ”์˜ ํŠน์ • ํ–‰์„ ๋ฝ‘์•„๋‚ด๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

ํ”„๋กœ์ ์…˜ ๋Œ€์ƒ์ด ํ•˜๋‚˜๋ผ๋ฉด, ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

List<String> result = queryFactory
    .select(member.username) // ํ”„๋กœ์ ์…˜์ด member ํ•œ ๊ฐœ
    .from(member)
    .fetch();

 

ํ•˜์ง€๋งŒ ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ ๋ฐ›๊ณ ์‹ถ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ? [String, Team] ์ด๋ ‡๊ฒŒ ์—ฌ๋Ÿฌ ํƒ€์ž…์„ ์กฐํšŒํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?

  • QueryDsl์€ ์ด๋Ÿฐ ๊ฒฝ์šฐ, ๊ธฐ๋ณธ์ ์œผ๋กœ Tuple๋กœ ํฌ์žฅํ•ด์„œ ๋ฐ˜ํ™˜ํ•œ๋‹ค. (ํŠœํ”Œ ํ”„๋กœ์ ์…˜)
    ํ•˜์ง€๋งŒ Tuple์€ QueryDsl์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ฐ์ฒด๋ผ์„œ, ์ปจํŠธ๋กค๋Ÿฌ๋‚˜ ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ต๋‹ค.
List<Tuple> result = queryFactory
    .select(member.username, member.age)
    .from(member)
    .fetch();

for (Tuple tuple : result) {
    String username = tuple.get(member.username);
    Integer age = tuple.get(member.age);
    System.out.println("username=" + username);
    System.out.println("age=" + age);
}

 

 

๐Ÿ“‘ DTO ํ”„๋กœ์ ์…˜

์ˆœ์ˆ˜ JPQL์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด [ new MemeberDto(...) ]์œผ๋กœ DTO ํ”„๋กœ์ ์…˜์ด ๊ฐ€๋Šฅํ–ˆ์—ˆ๋‹ค.

์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•œ ๋ฐฉ๋ฒ•๋งŒ ์ œ๊ณตํ–ˆ๊ณ , ์‚ฌ์šฉํ•˜๊ธฐ ๊นŒ๋‹ค๋กญ๊ธด ํ•˜๋‹ค.

List<MemberDto> result = em.createQuery(
        "select new study.querydsl.dto.MemberDto(m.username, m.age) " +
            "from Member m", MemberDto.class)
    .getResultList();
@Data
public class MemberDto {
    private String username;
    private int age;
    
    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

 

QueryDsl์—์„œ๋Š” 3๊ฐ€์ง€ ๋ฐฉ๋ฒ•์˜ DTO ํ”„๋กœ์ ์…˜์„ ์ง€์›ํ•œ๋‹ค.
QueryDsl์ด ์ œ๊ณตํ•˜๋Š” Projections ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ƒ์„ฑ์ž - Projections.constructor
List<MemberDto> result = queryFactory
    .select(Projections.constructor(MemberDto.class, member.username, member.age))
    .from(member)
    .fetch();

 

  • ํ”„๋กœํผํ‹ฐ(Getter, Setter) ์ ‘๊ทผ - Projections.bean
    ๐Ÿงจ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“  ๋‹ค์Œ ๊ฐ’์„ ๋„ฃ๋Š”๊ฑฐ๋ผ, ๋น„์–ด์žˆ๋Š” ๊ธฐ๋ณธ์ƒ์„ฑ์ž๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ์—†์œผ๋ฉด QueryDsl.ExpressionException ๋ฐœ์ƒ
    ๐Ÿงจ ๋‹น์—ฐํžˆ Setter๊ฐ€ ์—†์œผ๋ฉด ์ •์ƒ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.
List<MemberDto> result = queryFactory
    .select(Projections.bean(MemberDto.class, member.username, member.age))
    .from(member)
    .fetch();

 

  • ํ•„๋“œ ์ง์ ‘ ์ ‘๊ทผ - Projections.fields
    ๐Ÿงจ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“  ๋‹ค์Œ ๊ฐ’์„ ๋„ฃ๋Š”๊ฑฐ๋ผ, ๋น„์–ด์žˆ๋Š” ๊ธฐ๋ณธ์ƒ์„ฑ์ž๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ์—†์œผ๋ฉด QueryDsl.ExpressionException ๋ฐœ์ƒ
    ๐Ÿงจ ๋ฆฌํ”Œ๋ž™์…˜์„ ํ†ตํ•ด ์ ‘๊ทผ์ œ์–ด์ž(private)๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ๊ฐ•์ œ๋กœ ๊ฐ’์„ ๋Œ€์ž…ํ•œ๋‹ค.
List<MemberDto> result = queryFactory
    .select(Projections.fields(MemberDto.class, member.username, member.age))
    .from(member)
    .fetch();

 

 

๐Ÿ“‘ ํ•„๋“œ ์ด๋ฆ„์ด ๋‹ค๋ฅธ๊ฒฝ์šฐ ( as )

์ฐธ๊ณ ๋กœ ๊ฐ™์€ ์ด๋ฆ„์˜ ํ•„๋“œ๋ฅผ ์ฐพ์•„์„œ ๋„ฃ์–ด์ค€๋‹ค.

๋งŒ์•ฝ DTO ํ•„๋“œ์™€ ๊ฐ์ฒด ํ•„๋“œ์˜ ์ด๋ฆ„์ด ๋‹ค๋ฅด๋‹ค๋ฉด, .as() ๋ฅผ ์ด์šฉํ•ด์„œ ๋ณ„์นญ์„ ์ ์šฉ์‹œ์ผœ์ฃผ์ž.

List<UserDto> fetch = queryFactory
    .select(Projections.fields(UserDto.class,
        member.username.as("name"), // Member.username -> userDto.name
        member.age.as("userAge"))) // Member.age -> UserDto.userAge
    .from(member)
    .fetch();

 

๋‹จ์ˆœ ํ•„๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ, ์„œ๋ธŒ์ฟผ๋ฆฌ๋ฅผ ํ†ตํ•ด ํŠน์ • ๊ฐ’์„ ์กฐํšŒํ•œ ๊ฒฝ์šฐ์—๋Š” ExpressionUtils.as()๋ฅผ ์ด์šฉํ•ด์„œ ์ด๋ฆ„์„ ์ง€์–ด์ฃผ๋ฉด ๋œ๋‹ค.

๋ฌผ๋ก  ์ด๋Ÿฐ์‹์œผ๋กœ ์ด๋ฆ„์„ ๋ฐ”๊ฟ”์„œ ์‚ฌ์šฉํ•  ์ผ์€ ๊ฑฐ์˜ ์—†๋‹ค.

List<UserDto> fetch = queryFactory
    .select(Projections.fields(UserDto.class,
                ExpressionUtils.as(member.username, "name"), // ํ•„๋“œ๋กœ ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•ด๋„ ๋œ๋‹ค.
                ExpressionUtils.as( // ์„œ๋ธŒ์ฟผ๋ฆฌ์ธ๊ฒฝ์šฐ
                    JPAExpressions.select(memberSub.age.max()).from(memberSub),
                    "userAge")
                )
            )
    .from(member)
    .fetch();

 

 

๐Ÿ“‘ @QueryProjection ( + ์ƒ์„ฑ์ž )

์ œ์ผ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค. DTO๋ฅผ Q Type ์œผ๋กœ ์ƒ์„ฑํ•ด๋ฒ„๋ฆฐ๋‹ค. (๋นŒ๋“œ ์‹œ)

ํ•˜์ง€๋งŒ DTO์— QueryDsl์˜ ์˜์กด์„ฑ์ด ์ƒ๊ฒจ์„œ ์‹ค๋ฌด์— ์‚ฌ์šฉํ•˜๊ธฐ๋Š” ์–ด๋ ค์šด ๋ฐฉ๋ฒ•์ด๋‹ค.

@Data
public class MemberDto {

    private String username;
    private int age;

    @QueryProjection
    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

 

๊ทธ๋Ÿผ Tuple๋กœ ๋ฐ›๊ฑฐ๋‚˜ ๊ท€์ฐฎ์€ ํ”„๋กœ์ ์…˜ ๊ณผ์ •์—†์ด, ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹ค๋ฅธ ํ”„๋กœ์ ์…˜๊ณผ ๋‹ค๋ฅด๊ฒŒ, ์ž…๋ ฅ ๊ฐ’์ด ํ‹€๋ ค์„œ ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ธธ์ผ๋„ ์—†๋‹ค. ์ƒ์„ฑ์ž๊ฐ€ ๋‹ค๋ฅด๋ฉด ์ปดํŒŒ์ผ ์ž์ฒด๊ฐ€ ์•ˆ๋˜๋‹ˆ๊นŒ.

@Test
void dto() throws Exception {
    List<MemberDto> result = queryFactory
        .select(new QMemberDto(member.username, member.age))
        .from(member)
        .fetch();
}

 


๐Ÿ’ญ ๋™์ ์ฟผ๋ฆฌ

๐Ÿ“‘ Where(BooleanBulider)

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค SQL๋กœ ๋™์ ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“œ๋Š”๊ฑด ๊นŒ๋‹ค๋กญ์ง€๋งŒ, QueryDsl์„ ์‚ฌ์šฉํ•˜๋ฉด ๋งค์šฐ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

Querydsl์ด ์ œ๊ณตํ•ด์ฃผ๋Š” BolleanBuilder ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด์„œ, ์—ฌ๋Ÿฌ ์กฐ๊ฑด๋“ค์„ ๋ฐ›์œผ๋ฉด ๋œ๋‹ค.

private List<Member> searchMember1(String usernameCond, Integer ageCond) {
    BooleanBuilder builder = new BooleanBuilder();

    if (usernameCond != null) {
        builder.and(member.username.eq(usernameCond));
    }

    if (ageCond != null) {
        builder.and(member.age.eq(ageCond));
    }

    return queryFactory
        .selectFrom(member)
        .where(builder)
        .fetch();
}

 

๐Ÿ“‘ Where ( ..., ..., null ) ์‚ฌ์šฉ

์•ž์—์„œ ์–ธ๊ธ‰ํ–ˆ์ง€๋งŒ, where(~)์—๋Š” ์—ฌ๋Ÿฌ ์กฐ๊ฑด์„ ํŒŒ๋ผ๋ฉ”ํƒ€๋กœ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋‹ค.

List<Member> result1 = queryFactory
    .selectFrom(member)
    .where(member.username.eq("member1"), member.age.eq(10)) // .and()์™€ ๋™์ผ
    .fetch();

 

where(...) ์•ˆ์— null์ด ๋“ค์–ด๊ฐ€๋ฉด, ํ•ด๋‹น ์กฐ๊ฑด์„ ๋ฌด์‹œํ•˜๋Š”๋ฐ ์ด๋ฅผ ์ด์šฉํ•ด์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

๋ฉ”์„œ๋“œ ๋ฐ˜ํ™˜ ๊ฐ’์„ BooleanExpression ์œผ๋กœ ํ•˜๋ฉด ๋œ๋‹ค.

private List<Member> searchMember2(String usernameCond, Integer ageCond) {
        return queryFactory
            .selectFrom(member)
            .where(usernameEq(usernameCond), ageEq(ageCond))
            .fetch();
}

private BooleanExpression usernameEq(String usernameCond) {
    return (usernameCond != null) ? member.username.eq(usernameCond) : null;
}

private BooleanExpression ageEq(Integer ageCond) {
    return (ageCond != null) ? member.age.eq(ageCond) : null;
}

 

 

์ˆœ์ˆ˜ํ•œ ์ž๋ฐ”์ฝ”๋“œ์ด๊ธฐ์—, ์—ฌ๋Ÿฌ ์กฐ๊ฑด์„ ์กฐํ•ฉํ•ด์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

๋‹จ, .and(null) ์€ ์ปดํŒŒ์ผ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋‹ˆ ์กฐ์‹ฌํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.

private BooleanExpression allEq(String usernameCond, Integer ageCond) {
    return usernameEq(usernameCond).and(ageEq(ageCond));
}

 


๐Ÿ’ญ ๋ฒŒํฌ ์—ฐ์‚ฐ (์ˆ˜์ •, ์‚ญ์ œ)

JPA์˜ executeUpdate()์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ๋‹ค.

์ด ์—ญ์‹œ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์ด ๊นจ์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ ์ ˆํ•˜๊ฒŒ flush, clear ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ์ž.

long count = queryFactory
            .update(member)// ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
            .set(member.username, "๋น„ํšŒ์›")
            .where(member.age.lt(28))
            .execute();

em.flush(); // ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ DB์— ์ „์†กํ•˜๊ณ 
em.clear(); // ์ดˆ๊ธฐํ™”

๊ทธ ์™ธ๋Š” ์–ด๋ ค์šด๊ฒŒ ์—†๋‹ค. ๊ทธ๋ƒฅ ์“ฐ๋ฉด ๋œ๋‹ค.

long count1 = queryFactory
    .update(member)
    .set(member.age, member.age.add(1)) // ๋ชจ๋“  ๋ฐ์ดํ„ฐ์— +1
    .execute();

long count2 = queryFactory
    .delete(member)// ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์‚ญ์ œ
    .where(member.age.gt(18))
    .execute();

 

 


๐Ÿ’ญ DB์˜ SQL function ํ˜ธ์ถœํ•˜๊ธฐ

์ฐธ๊ณ ๋กœ JPA์—์„œ SQL function์€ Dialect (ex H2Dialect )์— ๋“ฑ๋ก๋œ ๋‚ด์šฉ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.

Dialect์— ์—†์œผ๋ฉด ์ง์ ‘ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๊ธดํ•œ๋ฐ, ๊ทธ๋ ‡๊ฒŒ๊นŒ์ง€ ์“ฐ๋Š” ๊ฒฝ์šฐ๋Š” ์ž˜ ์—†์œผ๋ฏ€๋กœ ๋„˜์–ด๊ฐ€๋„๋ก ํ•˜์ž.

 

์ด๊ฒƒ๋„ Expressions.stringTemplate(..)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์›ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.

String result1 = queryFactory
    .select(Expressions.stringTemplate("function('replace', {0}, {1}, {2})",
        member.username, "member", "M"))
    .from(member)
    .fetchFirst();
    
    
String result2 = queryFactory
    .select(member.username)
    .from(member)
    .where(member.username.eq(Expressions.stringTemplate("function('lower', {0})",
        member.username)));

 

๋ฌผ๋ก  ํŠน์ • DB์˜ SQLํ•จ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ, ANSI ํ‘œ์ค€ํ•จ์ˆ˜๋“ค์€ QueryDsl์ด ๋Œ€๋ถ€๋ถ„ ๋‚ด์žฅํ•˜๊ณ  ์žˆ๋‹ค. ๊ทธ๋ƒฅ ์“ฐ๋ฉด ๋œ๋‹ค.

.where(member.username.eq(member.username.lower())) // lower ํ•จ์ˆ˜

 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

JiwonDev

JiwonDev

ํ™œ๋™ํ•˜๊ธฐ