Post

[JPA] QueryDSL

[JPA] QueryDSL

๐Ÿ“Œ QueryDSL์ด๋ž€?

QueryDSL์€ HQL(Hibernate Query Language)๋ฅผ ํƒ€์ž…์— ์•ˆ์ „ํ•˜๊ฒŒ(type-safe) ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค. ์ •์  ํƒ€์ž…์„ ํ†ตํ•ด SQL์ด๋‚˜ JPQL ์ฟผ๋ฆฌ๋ฅผ ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

์ •์  ํƒ€์ž…์ด๋ž€ ์ปดํŒŒ์ผ ์‹œ์ ์— ๋ณ€์ˆ˜๋‚˜ ๊ฐ์ฒด์˜ ๋ฐ์ดํ„ฐ ํƒ€์ž…์ด ๋ฏธ๋ฆฌ ๊ฒฐ์ •๋˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

โ€˜type-safeโ€™๋ผ๋Š” ๊ฒƒ์€ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ์ „ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ํ™•์ธํ•˜์—ฌ ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

DSL์€ Domain-Specific Language์˜ ์•ฝ์ž๋กœ, ํŠน์ • ๋„๋ฉ”์ธ์— ์ดˆ์ ์„ ๋งž์ถ˜ ์†Œํ”„ํŠธ์›จ์–ด ์–ธ์–ด์ด๋‹ค. QueryDSL์—์„œ ๋„๋ฉ”์ธ์ด๋ž€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“Œ ๋“ฑ์žฅ ๋ฐฐ๊ฒฝ

QueryDSL ์ด์ „์—๋Š” Mybatis, JPQL, Criteria๊ณผ ๊ฐ™์ด ๋ฌธ์ž์—ด ํ˜•ํƒœ๋กœ ์ฟผ๋ฆฌ๋ฌธ์„ ์ž‘์„ฑํ•˜์˜€๋‹ค. ๋”ฐ๋ผ์„œ ์ปดํŒŒ์ผ ์‹œ์ ์— ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ๊ณ , ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ์กŒ๋‹ค. ๊ณต์‹ JPA์—์„œ ์ œ๊ณตํ•˜๋Š” Criteria API๋Š” ์ฟผ๋ฆฌ๋ฅผ ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜์ง€๋งŒ, ์ฝ”๋“œ๊ฐ€ ๋‚œํ•ดํ•˜๊ณ  ์ง๊ด€์„ฑ์ด ๋–จ์–ด์ง„๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์—ˆ๋‹ค. ๋˜ํ•œ ์‹ค๋ฌด์—์„œ๋Š” ๋‹ค์–‘ํ•œ ์กฐ๊ฑด์˜ ๋™์  ์ฟผ๋ฆฌ์˜ ํ•„์š”์„ฑ์ด ์ฆ๊ฐ€ํ•˜์˜€๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด QueryDSL์ด ๋“ฑ์žฅํ•˜์˜€๋‹ค.

๐Ÿ“Œ ์žฅ์ 

  • ๋ฌธ์ž์—ด์ด ์•„๋‹Œ ์ฝ”๋“œ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ปดํŒŒ์ผ ์‹œ์ ์— ์˜ค๋ฅ˜๋ฅผ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  • Q-Class, ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋‚˜ ๋™์  ์ฟผ๋ฆฌ ์ž‘์„ฑ์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • JPQL ๋ฌธ๋ฒ•๊ณผ ์œ ์‚ฌํ•œ ํ˜•ํƒœ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“Œ Q-Class

Q-Class ๋Š” QueryDSL์ด ์ œ๊ณตํ•˜๋Š” ๋ฉ”ํƒ€ ๋ชจ๋ธ ํด๋ž˜์Šค์ด๋‹ค. ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์˜ ๊ตฌ์กฐ์™€ ํ•„๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋ฉฐ, ๊ฐ ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ ์ •๋ณด๋ฅผ ๋ณ€์ˆ˜๋กœ ๊ฐ€์ง„๋‹ค.

๋ฉ”ํƒ€ ๋ชจ๋ธ ํด๋ž˜์Šค๋Š” ํŠน์ • ๋ชจ๋ธ์˜ ๊ตฌ์กฐ๋ฅผ ์ปดํŒŒ์ผ ์‹œ์ ์— ์ •์ ์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ํด๋ž˜์Šค์ด๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ํ•„๋“œ๋ช… ์˜คํƒ€, ํƒ€์ž… ๋ถˆ์ผ์น˜ ๋“ฑ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๋ฅผ ์‹คํ–‰ ์ „์— ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์— @Entity ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด์žˆ๋‹ค๋ฉด Q-Class๋Š” ๋นŒ๋“œ ์‹œ์ ์— JPAAnnotationProcessor ์— ์˜ํ•ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ๋‹ค. Q-Class๋Š” ์—”ํ‹ฐํ‹ฐ ์ด๋ฆ„ ์•ž์— โ€˜Qโ€™๊ฐ€ ๋ถ™์–ด ์ƒ์„ฑ๋œ๋‹ค. ๋งŒ์•ฝ ์—”ํ‹ฐํ‹ฐ ์ด๋ฆ„์ด User ๋ผ๋ฉด ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์˜ Q-Class๋Š” QUser ๊ฐ€ ๋œ๋‹ค.

JPAAnnotationProcessor๋Š” APT(Annotation Processing Tool) ์ด๋‹ค. APT๋Š” Java ์ปดํŒŒ์ผ๋Ÿฌ์˜ ์ผ๋ถ€๋กœ, ์ปดํŒŒ์ผ ์‹œ์ ์— ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ„์„ํ•˜๊ณ  ์ถ”๊ฐ€์ ์ธ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋„๊ตฌ์ด๋‹ค. ๋‹ค๋ฅธ ์˜ˆ์‹œ๋กœ Lombok์˜ @Getter ๊ฐ€ ์žˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
public class Member {
    @Id
    private Long id;
    private String username;
    private int age;
}

public class QMember extends EntityPathBase<Member> {
    public static final QMember member = new QMember("member");
    public final StringPath username = createString("username");
    public final NumberPath<Integer> age = createNumber("age", Integer.class);
    public final NumberPath<Long> id = createNumber("id", Long.class);
}

์ปดํŒŒ์ผ ์‹œ์ ์— ์œ„์™€ ๊ฐ™์€ Q-Class๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

JPAQueryFactory ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Q-Class ๊ธฐ๋ฐ˜์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋นŒ๋” ํŒจํ„ด์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
3
4
5
6
7
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QMember member = QMember.member;
Member result = queryFactory
    .selectFrom(member)
    .where(member.username.eq("whqtker"))
    .fetchOne();

1
2
3
4
5
6
7
8
@Configuration
public class QueryDSLConfig {
    @Bean
    JPAQueryFactory jpaQueryFactory(EntityManager em) {
        return new JPAQueryFactory(em);
    }
}

๐Ÿ“Œ QueryDSL ์„ค์ •

1
2
3
4
5
    // QueryDSL
    implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"

dependencies ์— QueryDSL ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

1
2
3
clean {
    delete file('src/main/generated')
}

์œ„ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด gradle clean ๋ช…๋ น์œผ๋กœ ์ƒ์„ฑ๋œ Q-Class๋ฅผ ์ž๋™์œผ๋กœ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“Œ ๊ธฐ๋ณธ ๋ฌธ๋ฒ•

1
2
3
4
5
6
QMember member = QMember.member;
Member result = queryFactory
    .selectFrom(member)
    .where(member.username.eq("member1"))
    .fetchOne();

QueryDSL์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฉ”์„œ๋“œ ์ฒด์ด๋‹์„ ํ†ตํ•ด ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

selectFrom() : ์กฐํšŒํ•  ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง€์ •ํ•œ๋‹ค.

where() : ์กฐ๊ฑด์ ˆ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

fetchOne() : ๋‹จ์ผ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.

์กฐ๊ฑด

1
2
3
4
5
.where(
    member.age.between(10, 30)
    .and(member.username.like("%user%"))
)

where์ ˆ์— ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฉ”์„œ๋“œ ์ค‘ ์ผ๋ถ€๋งŒ ์‚ดํŽด๋ณด์ž.

eq(): ๊ฐ’์ด ๊ฐ™์€์ง€ ๋น„๊ตํ•œ๋‹ค.

ne(): ๊ฐ’์ด ๋‹ค๋ฅธ์ง€ ๋น„๊ตํ•œ๋‹ค.

goe(): ์ด์ƒ(โ‰ฅ)

lt(): ๋ฏธ๋งŒ(<)

like(): ๋ฌธ์ž์—ด ํŒจํ„ด์„ ๋งค์นญํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

์กฐ๊ฑด์„ null๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ์กฐ๊ฑด์€ ๋ฌด์‹œ๋œ๋‹ค.

์กฐํšŒ

1
2
3
4
QueryResults<Member> results = queryFactory
    .selectFrom(member)
    .fetchResults();

fetch(): ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค. ๋ฆฌํ„ด ํƒ€์ž…์€ List<T> ์ด๋‹ค.

fetchOne(): ๋‹จ์ผ ๊ฒฐ๊ณผ ๊ฐ์ฒด๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค. ๊ฒฐ๊ณผ๊ฐ€ ์—†๋‹ค๋ฉด null, ๊ฒฐ๊ณผ๊ฐ€ ๋‘˜ ์ด์ƒ์ด๋ผ๋ฉด NonUniqueResultException์„ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

fetchFirst(): ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์—์„œ ์ฒซ ๋ฒˆ์งธ ๊ฒฐ๊ณผ๋งŒ ๋ฆฌํ„ดํ•œ๋‹ค. limit(1).fetchOne() ๊ณผ ๋™์ผํ•œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ๊ฒฐ๊ณผ๊ฐ€ ์—†๋‹ค๋ฉด null ์„ ๋ฆฌํ„ดํ•œ๋‹ค.

fetchResults(): ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ์™€ ์ „์ฒด ๊ฐœ์ˆ˜๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค. ๋ฆฌํ„ด ํƒ€์ž…์€ QueryResults<T> ์ด๋ฉฐ, ๋‚ด๋ถ€์ ์œผ๋กœ getResults(), getTotal() ์ด ์ˆ˜ํ–‰๋œ๋‹ค. ๋‹จ, ์„ฑ๋Šฅ์ ์œผ๋กœ ๋ฌธ์ œ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ์‚ฌ์šฉ์„ ์ง€์–‘ํ•œ๋‹ค.

๊ทธ๋ฃนํ™”๋ฅผ ์‚ฌ์šฉํ•œ ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์—์„œ ์ •ํ™•ํ•œ ์ „์ฒด ๊ฐœ์ˆ˜๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต๊ณ , ์ „์ฒด ๊ฒฐ๊ณผ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œํ•œ ํ›„ size() ๋กœ ๊ฐœ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๋ฏ€๋กœ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ๊ฐ€๋Šฅ์„ฑ์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. fetch() ํ›„ ๋ณ„๋„์˜ count ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

fetchCount() : ์กฐํšŒ ์ฟผ๋ฆฌ๋ฅผ count ์ฟผ๋ฆฌ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๊ฐœ์ˆ˜๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.

๋‚ด๋ถ€์ ์œผ๋กœ fetch().size() ๋กœ ์ฒ˜๋ฆฌ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์–ด ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ด์œ ๋Š” ๋™์ผํ•˜๋‹ค.

์ •๋ ฌ๊ณผ ํŽ˜์ด์ง•

1
2
3
4
5
6
7
List<Member> members = queryFactory
    .selectFrom(member)
    .orderBy(member.age.desc(), member.username.asc().nullsLast())
    .offset(10)
    .limit(5)
    .fetch();

orderBy() : ์ •๋ ฌ ์ˆœ์„œ๋ฅผ ์ง€์ •ํ•œ๋‹ค.

nullsLast(): null ๊ฐ’์„ ๋งˆ์ง€๋ง‰์— ๋ฐฐ์น˜ํ•œ๋‹ค.

offset(): ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์—์„œ ๋ช‡ ๋ฒˆ์งธ ํ–‰๋ถ€ํ„ฐ ๋ฆฌํ„ดํ• ์ง€ ์ง€์ •ํ•œ๋‹ค.

limit(): ์ตœ๋Œ€ ๋ช‡ ๊ฐœ์˜ ํ–‰(ํŽ˜์ด์ง€ ํฌ๊ธฐ)์„ ๋ฆฌํ„ดํ• ์ง€ ์ง€์ •ํ•œ๋‹ค.

์ง‘๊ณ„ ํ•จ์ˆ˜์™€ ๊ทธ๋ฃนํ™”

1
2
3
4
5
6
7
8
9
10
11
List<Tuple> result = queryFactory
    .select(
        team.name,
        member.age.avg()
    )
    .from(member)
    .join(member.team, team)
    .groupBy(team.name)
    .having(member.age.avg().gt(20))
    .fetch();

groupBy(), having(): ๊ทธ๋ฃนํ™”ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค. avg() ๊ณผ ๊ฐ™์ด ๋‹ค์–‘ํ•œ ์ง‘๊ณ„ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Projections

1
2
3
4
List<Tuple> result = queryFactory
    .select(member.username, member.age)
    .from(member)
    .fetch();

์ „์ฒด ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์•„๋‹Œ ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์„ ํƒํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ๊ฒฝ์šฐ Projections ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. QueryDSL์˜ Tuple ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ํ•„๋“œ๋ฅผ ์กฐํšŒํ•œ๋‹ค.

๊ฒฐ๊ณผ๋ฅผ DTO๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ 4๊ฐ€์ง€๊ฐ€ ์กด์žฌํ•œ๋‹ค.

1. Projections.constructor()

1
2
3
4
5
6
7
8
9
List<MemberDto> result = queryFactory
    .select(Projections.constructor(
        MemberDto.class,
        member.username,
        member.age
    ))
    .from(member)
    .fetch();

DTO์˜ ์ƒ์„ฑ์ž๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ์ƒ์„ฑ์ž๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋งคํ•‘ํ•˜๋ฏ€๋กœ ๋ถˆ๋ณ€ DTO๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹จ, ์ปดํŒŒ์ผ ํƒ€์ž„์— ์˜ค๋ฅ˜ ๊ฒ€์ถœ์ด ์–ด๋ ต๊ณ , ๋งค๊ฐœ๋ณ€์ˆ˜ ์ˆœ์„œ๋ฅผ ์ผ์น˜์‹œ์ผœ์•ผ ํ•œ๋‹ค. ์ˆœ์„œ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด IllegalArgumentException ์ด ๋ฐœ์ƒํ•œ๋‹ค.

๋ถˆ๋ณ€ DTO๋Š” ํ•œ ๋ฒˆ ์ƒ์„ฑ๋œ ํ›„ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” DTO๋ฅผ ๋งํ•œ๋‹ค. ๋ชจ๋“  ํ•„๋“œ๋Š” final ๋กœ ์„ ์–ธ๋œ๋‹ค.

2. Projections.fields()

1
2
3
4
5
6
7
8
9
List<MemberDto> result = queryFactory
    .select(Projections.fields(
        MemberDto.class,
        member.username,
        member.age
    ))
    .from(member)
    .fetch();

DTO์˜ ํ•„๋“œ์— ์ง์ ‘ ์ ‘๊ทผํ•˜์—ฌ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. DTO์˜ ํ•„๋“œ๋ช…๊ณผ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์˜ ํ•„๋“œ๋ช…์ด ์ผ์น˜ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋งคํ•‘๋œ๋‹ค. ์ง์ ‘ ๊ฐ’์„ ์ฃผ์ž…ํ•˜๋ฏ€๋กœ Setter๊ฐ€ ํ•„์š” ์—†๋‹ค. ๋‹จ, DTO์— @NoArgsConstructor ๊ฐ€ ํ•„์ˆ˜๋กœ ๋ช…์‹œ๋˜์–ด์•ผ ํ•œ๋‹ค.

3. Projections.bean()

1
2
3
4
5
6
7
8
9
List<MemberDto> result = queryFactory
    .select(Projections.bean(
        MemberDto.class,
        member.username,
        member.age
    ))
    .from(member)
    .fetch();

DTO์˜ Setter๋ฅผ ํ†ตํ•ด ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ๋”ฐ๋ผ์„œ Setter์™€ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ํ•„์ˆ˜๋กœ ๋ช…์‹œ๋˜์–ด์•ผ ํ•œ๋‹ค.

4. @QueryProjection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Getter
public class UserDTO {
    private String username;
    private int age;

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

List<UserDTO> dtos = queryFactory
    .select(new QUserDTO(member.username, member.age))
    .from(member)
    .fetch();

DTO์˜ ์ƒ์„ฑ์ž์— @QueryProjection ์„ ์ถ”๊ฐ€ํ•˜๋ฉด QueryDSL์ด ํ•ด๋‹น DTO์— ๋Œ€ํ•œ Q-Class๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜์—ฌ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ type-safeํ•˜๊ฒŒ ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ƒ์„ฑ์ž ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ Setter ์—†์ด ๋ถˆ๋ณ€ DTO๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐ€์žฅ ์ถ”์ฒœํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

BooleanBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
BooleanBuilder builder = new BooleanBuilder();
if (username != null) {
    builder.and(member.username.eq(username));
}
if (minAge != null) {
    builder.and(member.age.goe(minAge));
}

List<Member> result = queryFactory
    .selectFrom(member)
    .where(builder)
    .fetch();

์กฐ๊ฑด์„ ์œ ๋™์ ์œผ๋กœ ์กฐํ•ฉํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ BooleanBuilder ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. and() ๋‚˜ or() ์„ ํ†ตํ•œ ๋ฉ”์„œ๋“œ ์ฒด์ด๋‹ ๋ฐฉ๋ฒ•์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
BooleanBuilder builder = new BooleanBuilder(Expressions.TRUE);

์กฐ๊ฑด์ด ํ•˜๋‚˜๋„ ์ถ”๊ฐ€๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์œ„์™€ ๊ฐ™์ด ์ž‘์„ฑํ•œ๋‹ค. WHERE 1=1 ๊ณผ ๋™์ผํ•œ ํšจ๊ณผ๋ฅผ ๊ฐ€์ง„๋‹ค.

์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์ž์ฃผ ๊ฑธ๋Ÿฌ์ง€๋Š” ์กฐ๊ฑด์„ ๋จผ์ € ์ž‘์„ฑํ•˜์—ฌ ๋ฐ์ดํ„ฐ์˜ ๋ฒ”์œ„๋ฅผ ๋น ๋ฅด๊ฒŒ ์ถ•์†Œํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

์กฐ์ธ

1
2
3
4
5
6
7
List<Order> orders = queryFactory
    .selectFrom(order)
    .join(order.member, member)
    .leftJoin(order.product, product)
    .where(member.age.gt(30))
    .fetch();

QueryDSL์—์„œ ์กฐ์ธ์€ JPQL๊ณผ ์œ ์‚ฌํ•œ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค. join(), innerjoin(), leftjoin(), fetchJoin()๋“ฑ๊ณผ ๊ฐ™์€ ์กฐ์ธ ๋ฉ”์„œ๋“œ๊ฐ€ ์กด์žฌํ•˜๋ฉฐ, on() ์„ ํ†ตํ•ด ์กฐ์ธ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

์„œ๋ธŒ์ฟผ๋ฆฌ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// SELECT ์ ˆ ์„œ๋ธŒ์ฟผ๋ฆฌ
List<Tuple> result = queryFactory
    .select(
        member.username,
        JPAExpressions.select(memberSub.age.max())
                     .from(memberSub)
    )
    .from(member)
    .fetch();

// WHERE ์ ˆ ์„œ๋ธŒ์ฟผ๋ฆฌ
List<Member> result = queryFactory
    .selectFrom(member)
    .where(member.age.eq(
        JPAExpressions.select(memberSub.age.max())
                     .from(memberSub)
    ))
    .fetch();

// FROM ์ ˆ ์„œ๋ธŒ์ฟผ๋ฆฌ
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
    .selectFrom(member)
    .from(JPAExpressions.selectFrom(memberSub)
                       .where(memberSub.age.gt(10)), member)
    .fetch();

JPAExpressions ๋ฅผ ํ†ตํ•ด ์„œ๋ธŒ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ์„œ๋ธŒ์ฟผ๋ฆฌ๋Š” SELECT, WHERE, FROM ์ ˆ์— ์ž‘์„ฑ๋  ์ˆ˜ ์žˆ๋‹ค.

๋‹จ, FROM ์ ˆ ์„œ๋ธŒ์ฟผ๋ฆฌ๋Š” Hibernate 6 ๋ฏธ๋งŒ์—์„œ๋Š” ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.

์กฐ๊ฑด ๋ถ„๊ธฐ

1
2
3
4
5
6
7
8
List<String> result = queryFactory
    .select(new CaseBuilder()
        .when(member.age.between(10, 20)).then("10๋Œ€")
        .when(member.age.between(20, 30)).then("20๋Œ€")
        .otherwise("๊ธฐํƒ€"))
    .from(member)
    .fetch();

CaseBuilder ๋ฅผ ํ†ตํ•ด ๋ณต์žกํ•œ ์กฐ๊ฑด ๋ถ„๊ธฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. SQL๋ฌธ์—์„œ CASE WHEN ๊ตฌ๋ฌธ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

์ˆ˜์ • ๋ฐ ์‚ญ์ œ

1
2
3
4
5
6
long count = queryFactory
        .update(member)
        .set(member.name, newName)
        .where(member.name.eq(oldName))
        .execute();

update() ์™€ delete() ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ • ๋ฐ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค. execute() ๋ฅผ ํ†ตํ•ด ๋ฉ”์„œ๋“œ์— ์˜ํ–ฅ์„ ๋ฐ›์€ ํ–‰์˜ ์ˆ˜๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.

๐Ÿ“Œ ์ฐธ๊ณ 

https://velog.io/@evan523/JPA-QueryDSL

This post is licensed under CC BY 4.0 by the author.