ํ”„๋กœ์ ์…˜

ํ”„๋กœ์ ์…˜๊ณผ ๊ธฐ๋ณธ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜

ํ”„๋กœ์ ์…˜ ๋Œ€์ƒ์ด ํ•˜๋‚˜์ธ ๊ฒฝ์šฐ

    @Test
    public void simpleProjection() {
        List<String> result = queryFactory
                .select(member.username)
                .from(member)
                .fetch();
    }
  • ํƒ€์ž…์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Œ.
  • ํ”„๋กœ์ ์…˜ ๋Œ€์ƒ์ด ๋‘˜ ์ด์ƒ์ด๋ฉด Tuple์ด๋‚˜ DTO๋กœ ์กฐํšŒํ•ด์•ผํ•จ

ํ”„๋กœ์ ์…˜ ๋Œ€์ƒ์ด ๋‘˜ ์ด์ƒ์ผ ๊ฒฝ์šฐ

Tuple๋กœ ์กฐํšŒ

    @Test
    public void tupleProjection() {
        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);
        }
    }
  • com.querydsl.core.Tuple๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ
  • get()์— ์ฟผ๋ฆฌ ํƒ€์ž…(Q)์œผ๋กœ alias๋ฅผ ์ฃผ์ž…ํ•ด ๊ฐ’์„ ๋ฐ˜ํ™˜ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค
    ์˜ˆ: tuple.get(member.username)

์ฐธ๊ณ ! ํˆฌํ”Œ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๊ณ„์ธต์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ดœ์ฐฎ์œผ๋‚˜, ๊ทธ ํ•˜๋ถ€(์„œ๋น„์Šค, ์ปจํŠธ๋กค๋Ÿฌ) ๊ณ„์ธต์œผ๋กœ ๋‚ด๋ ค๊ฐ€๊ฒŒ ๋˜๋ฉด ์ข‹์€ ์„ค๊ณ„๊ฐ€ ์•„๋‹ˆ๋‹ค. ์•ž๋‹จ์˜ ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด QueryDSL์˜ ์˜์กด์„ฑ์œผ๋กœ ์—ฎ์—ฌ๋ฒ„๋ฆฌ๋ฉด ์ข‹์ง€ ์•Š๋‹ค. QueryDSL์˜ ์˜์กด์„ฑ์ด ์•ž๋‹จ๊นŒ์ง€ ๋„˜์–ด๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ์ข‹์ง€ ์•Š์Œ.

DTO ์กฐํšŒ

ํšŒ์› ์—”ํ‹ฐํ‹ฐ DTO ์˜ˆ์‹œ

import lombok.Data;

@Data
public class MemberDto {

    private String username;

    private int age;

    public MemberDto() {
    }

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

QueryDSL์—์„œ ๊ฒฐ๊ณผ๋ฅผ DTO๋กœ ์กฐํšŒํ•  ๋•Œ ๋นˆ ์ƒ์„ฑ ๋ฐฉ๋ฒ• 3๊ฐ€์ง€๋ฅผ ์ง€์›ํ•œ๋‹ค.

  • ํ”„๋กœํผํ‹ฐ ์ ‘๊ทผ: setter๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฐ’์„ ์ฃผ์ž…
  • ํ•„๋“œ ์ง์ ‘ ์ ‘๊ทผ
  • ์ƒ์„ฑ์ž ์‚ฌ์šฉ
    // setter๋ฅผ ์ด์šฉํ•˜์—ฌ ์ฃผ์ž…
    @Test
    public void findDtoBySetter() {
        List<MemberDto> result = queryFactory
                .select(Projections.bean(MemberDto.class, 
                            member.username,
                            member.age))
                .from(member)
                .fetch();
    }

    // ํ•„๋“œ์— ๋ฐ”๋กœ ๊ฐ’์„ ๊ผฝ์•„๋ฒ„๋ฆผ
    @Test
    public void findDtoByField() {
        List<MemberDto> result = queryFactory
                .select(Projections.fields(MemberDto.class, 
                            member.username,
                            member.age))
                .from(member)
                .fetch();
    }

    // ์ƒ์„ฑ์ž๋ฅผ ์ด์šฉํ•˜์—ฌ ์ฃผ์ž…
    @Test
    public void findDtoByConstructor() {
        List<MemberDto> result = queryFactory
                .select(Projections.constructor(MemberDto.class, 
                            member.username,
                            member.age))
                .from(member)
                .fetch();
    }
  • ํ”„๋กœํผํ‹ฐ ์ ‘๊ทผ์ด๋‚˜ ํ•„๋“œ ์ง์ ‘ ์ ‘๊ทผ ์‹œ DTO์˜ ํ•„๋“œ๋ช…๊ณผ ์—”ํ‹ฐํ‹ฐ์˜ ์†์„ฑ๋ช…์„ ๋งž์ถฐ์ค˜์•ผํ•จ.
  • ์ƒ์„ฑ์ž ์‚ฌ์šฉ์˜ ๊ฒฝ์šฐ์—๋Š” ์ˆœ์„œ์™€ ํƒ€์ž…๋งŒ ๋งž์ถฐ์ฃผ๋ฉด ๋œ๋‹ค.

ํ”„๋กœํผํ‹ฐ ์ ‘๊ทผ์ด๋‚˜ ํ•„๋“œ ์ง์ ‘ ์ ‘๊ทผ ์‹œ ์ด๋ฆ„์ด ๋‹ค๋ฅผ ๋•Œ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

    @Test
    public void findUserDto() {
        QMember memberSub = new QMember("memberSub");    

        List<UserDto> result = queryFactory
                .select(Projections.bean(UserDto.class, 
                            member.username.as("name"),
                            // ์„œ๋ธŒ ์ฟผ๋ฆฌ๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ๋ฒ• ExpressionUtils๋กœ ๊ฐ์‹ธ์ค˜์•ผ ํ•œ๋‹ค.
                            ExpressionUtils.as(JPAExpressions
                                        .select(memberSub.age.max())
                                        .from(memberSub), "age")))
                .from(member)
                .fetch();
    }
  • ExperssionUtils.as(source, alias): ํ•„๋“œ๋‚˜ ์„œ๋ธŒ ์ฟผ๋ฆฌ์— ๋ณ„์นญ ์ ์šฉ
  • username.as("memberName"): ํ•„๋“œ์— ๋ณ„์นญ ์ ์šฉ

@QueryProjection ์‚ฌ์šฉํ•˜์—ฌ DTO ์ƒ์„ฑ์ž ์‚ฌ์šฉํ•˜์—ฌ ์กฐํšŒ

์ƒ์„ฑ์ž + @QueryProjection

@Data
public class MemberDto {

    private String username;

    private int age;

    public MemberDto() {
    }

    @QueryProjection
    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}
  • ์‚ฌ์šฉํ•  ์ƒ์„ฑ์ž์— @QueryProjection ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ฃผ๋ฉด ๋จ.
  • ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์˜€๋‹ค๋ฉด Qํƒ€์ž…์œผ๋กœ DTO๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@QueryProjection ํ™œ์šฉ ์˜ˆ์‹œ

    @Test
    public void findDtoByQueryProjection() {
        List<MemberDto> result = queryFactory
                .select(new QMemberDto(member.username, member.age))
                .from(member)
                .fetch();
    }
  • ์ƒ์„ฑ์ž ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…๋งŒ ๋งž์ถฐ์ฃผ๋ฉด ์ž˜ ์ฃผ์ž…๋œ๋‹ค.

@QueryProjection๋Š” QueryDSL์—์„œ ์ œ๊ณตํ•˜๋Š” ํ”„๋กœ์ ์…˜ ๊ถ๊ทน์˜ ๋ฐฉ๋ฒ•์ด์ง€๋งŒ ์žฅ๋‹จ์ ์ด ์กด์žฌํ•œ๋‹ค.

์žฅ์ 

  • Projections.constructor()๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํŒŒ์ผ ์˜ค๋ฅ˜๋ฅผ ์žก์„ ์ˆ˜ ์—†์ง€๋งŒ, ํ•ด๋‹น ๋ฐฉ๋ฒ•์€ ์ปดํŒŒ์ผ ์˜ค๋ฅ˜๋กœ ์žก์„ ์ˆ˜ ์žˆ๋‹ค.

๋‹จ์ 

  • QํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์•ผํ•จ.
  • ์˜์กด๊ด€๊ณ„๊ฐ€ ๋ฌธ์ œ, DTO ์ž์ฒด๊ฐ€ QueryDSL์— ์˜์กด์„ฑ์ด ์ƒ๊น€. ๊ทธ๋Ÿผ ์ปจํŠธ๋กค๋Ÿฌ, ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ QueryDSL์˜ ์˜์กด์„ฑ์ด ์ƒ๊ธฐ๊ธฐ ๋•Œ๋ฌธ์— DTO๊ฐ€ ์ˆœ์ˆ˜ํ•จ์„ ์žƒ์–ด๋ฒ„๋ฆฐ๋‹ค. ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„๊ด€์ ์—์„œ ์ข‹์ง„ ์•Š๋‹ค.

@QueryProjection ๊ฒฐ๋ก ! ์‹ค์šฉ์ ์ธ ๊ด€์ ์—์„œ๋Š” ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์ง€๋งŒ, ์„œ๋น„์Šค์˜ ์šด์˜ ์‹œ์ ์—์„œ ๋ดค์„ ๋•Œ๋Š” ๊ณ ๋ คํ•ด๋ณผ ์‚ฌํ•ญ์ด๋‹ค.


Distinct

List<String> result = queryFactory
 .select(member.username).distinct()
 .from(member)
 .fetch();
  • JPQL์˜ distinct์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

๋™์  ์ฟผ๋ฆฌ

๋™์  ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ์กฐ๊ฑด์— ๋”ฐ๋ฅธ ์ฟผ๋ฆฌ๋ฅผ ํŽธ๋ฆฌํ•˜๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

๋™์  ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ์‹์—๋Š” ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

  • BooleanBuilder ํ™œ์šฉ
  • where()์— ๋‹ค์ค‘ ํŒŒ๋ผ๋ฏธํ„ฐ ์‚ฌ์šฉ

BoolenBuilder ํ™œ์šฉ ์˜ˆ์‹œ

    @Test
    public void dynamicQuery_BooleanBuilder() {
        String usernameParam = "member1";
        int ageParam = 10;
        List<Member> result = searchMember1(usernameParam, ageParam);
    }

    public 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();
    }
  • BooleanBuilder ์ธ์Šคํ„ด์Šค์— .and()๋ฅผ ์‚ฌ์šฉํ•ด ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ๋ถ™์ผ ์ˆ˜ ์žˆ๋‹ค.

where ๋‹ค์ค‘ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™œ์šฉ ์˜ˆ์‹œ (๊ฐ•์‚ฌ๋‹˜์ด ์‹ค๋ฌด์—์„œ ์ž์ฃผ ์“ฐ์‹œ๋Š” ๋ฐฉ์‹)

    @Test
    public void dynamicQuery_WhereParam() {
        String usernameParam = "member1";
        int ageParam = 10;

        List<Member> result = searchMember(usernameParam, ageParam);
    }
    public List<Member> searchMember(String usernameCond, Integer ageCond) {
        return queryFactory
                .selectFrom(member)
                .where(usernameEq(usernameCond), ageEq(ageCond))
                .fetch();
    }

    // ์•„๋ž˜์™€ ๊ฐ™์ด ๋™์  ์กฐ๊ฑด ์ฟผ๋ฆฌ๋ฅผ ๋ฉ”์†Œ๋“œ๋กœ ๋นผ๋ฉด ์žฌ์‚ฌ์šฉ์„ฑ์ด ์ฆ๊ฐ€ํ•œ๋‹ค. (๋‹ค๋ฅธ ์ฟผ๋ฆฌ์—์„œ ์žฌ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•จ)
    private Predicate usernameEq(String usernameCond) {
        return usernameCond != null ? member.username.eq(usernameCond) : null;
    }

    private Predicate ageEq(Integer ageCond) {
        if (ageCond == null) {
            return null;
        }
        return member.age.eq(ageCond);
    }
  • where ์กฐ๊ฑด์— null ๊ฐ’์ด ์žˆ์œผ๋ฉด ๋ฌด์‹œ๋˜๋Š”๋ฐ ์ด๋ฅผ ํ™œ์šฉํ•จ.
  • ๋ฉ”์†Œ๋“œ๋ฅผ ๋”ฐ๋กœ ๋นผ์„œ ์ƒ์„ฑํ•˜๋ฉด ๋‹ค๋ฅธ ์ฟผ๋ฆฌ์—์„œ๋„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ.
  • ์ฟผ๋ฆฌ ์ž์ฒด์˜ ๊ฐ€๋…์„ฑ์ด ๋†’์•„์ง.

์œ„์˜ ๋™์  ์กฐ๊ฑด ์ฟผ๋ฆฌ ๋ฉ”์†Œ๋“œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์กฐํ•ฉํ•˜์—ฌ ํ•˜๋‚˜์˜ ๋ฉ”์†Œ๋“œ๋กœ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๋‹ค.

private BooleanExpression allEq(String usernameCond, Integer ageCond) {
 return usernameEq(usernameCond).and(ageEq(ageCond));
}
  • BooleanExpression: Predicate์„ ์ƒ์† ๋ฐ›์•„ ๊ตฌํ˜„๋œ ์ถ”์ƒ ํด๋ž˜์Šค
  • BooleanExpression๋ฅผ ์ด์šฉํ•˜๋ฉด and()๋ฅผ ์ฒด์ธ์œผ๋กœ ํ•œ ๋ฒˆ์— ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์—ฎ์„ ์ˆ˜ ์žˆ๋‹ค.
  • ๋‹จ ์ด ๊ฒฝ์šฐ์—๋Š” null ๊ฐ’ ์ฒ˜๋ฆฌ๋ฅผ ์ž˜ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

์ˆ˜์ •, ์‚ญ์ œ ์‹œ ๋ฒŒํฌ ์—ฐ์‚ฐ

์ˆ˜์ •

    @Test
    public void bulkUpdate() {    
        long count = queryFactory
                .update(member)
                .set(member.username, "๋น„ํšŒ์›")
                .where(member.age.lt(14))
                .execute();
    }
  • ๋ฐ˜ํ™˜๋˜๋Š” long ํƒ€์ž… ๊ฐ’์€ ๋ฐ˜์˜๋œ ๋ฐ์ดํ„ฐ์˜ ์ˆ˜

์•„๋ž˜์™€ ๊ฐ™์ด ๊ธฐ์กด ๊ฐ’์— ๋”ํ•˜๊ธฐ, ๊ณฑ ์—ฐ์‚ฐ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

// ๋”ํ•˜๊ธฐ (๋งˆ์ด๋„ˆ์Šค ์‹œ ์Œ์ˆ˜ ์ฃผ์ž…)
    @Test
    public void bulkAdd() {
        long count = queryFactory
                .update(member)
                .set(member.age, member.age.add(2))
                .execute();
    }

    // ๊ณฑ
    @Test
    public void bulkMultiply() {
        long count = queryFactory
                .update(member)
                .set(member.age, member.age.multiply(2))
                .execute();
    }

์‚ญ์ œ

@Test
    public void bulkDelete() {
        long count = queryFactory
                .delete(member)
                .where(member.age.gt(13))
                .execute();
    }

์ฃผ์˜! JPQL ๋ฐฐ์น˜ ์ฟผ๋ฆฌ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์žˆ๋Š” ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐฐ์น˜ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ ํ›„์—๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ์ดˆ๊ธฐํ™” ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•˜๋‹ค.