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์ ์ด ์์ธ๋ฅผ ๊ฐ์ธ์ ๋์ง๋ค.
- 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("๋ณ๋ช ")์ ๋ง๋ค์ด์ ์กฐ์ธํด์ฃผ๋ฉด ๋๋ค.
leftJoin(), rightJoin() ๋ ๋ง์ฐฌ๊ฐ์ง๋ก ์ง์ํ๋ค.
List<Member> result = queryFactory
.selectFrom(member)
.leftJoin(member.team, team) // Member.team ์ Team ์ํฐํฐ(QTeam.team)๊ณผ ์กฐ์ธํ๋ค.
.where(team.name.eq("teamA"))
.fetch();
๐ 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 ํ์ผ๋ฏ๋ก, ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ์ด ๋์จ๋ค.
๋น์ฐํ ๋ง์ด๊ธดํ๋ฐ, 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);
}
}
์ค์ ์ฟผ๋ฆฌ๋ฅผ ์ฐ์ด๋ณด๋ฉด ์๋์ ๊ฐ๋ค.
๐ 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();
๐งจ 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);
}
}
- ์กฐ๊ฑด์ด ๋ฑ ๋จ์ด์ง์ง ์๊ณ , ๋ณต์กํ๋ค๋ฉด 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"]...
*/
๊ฒฐ๊ณผ๊ฐ์ ํน์ ๋ฌธ์์ด์ ๋ํ๊ณ ์ถ๋ค๋ฉด, 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 ํจ์
'๐ฑBackend > JDBC & JPA' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
JDBC Connection ์ ๋ํ ์ดํด, HikariCP ์ค์ ํ (0) | 2023.11.21 |
---|---|
์คํ๋งJPA์ ์์์ฑ์ปจํ ์คํธ (EntityManager) (0) | 2022.02.03 |
Spring Data JPA (0) | 2022.01.31 |
JPA ์ฑ๋ฅ ๊ฐ์ ํ (0) | 2022.01.22 |
JPA 10 # ๊ฐ์ฒด ์ฟผ๋ฆฌ ์ธ์ด, JPQL (0) | 2021.11.16 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev