QueryDSL

[QueryDSL] 기본 문법

쌈뽕코딩 2025. 2. 17. 22:15

기본 Q-Type 활용

1️⃣ Q클래스 인스턴스를 사용하는 2가지 방법

QMember qMember = new QMember("m"); // 1. 별칭 직접 지정 
QMember qMember = QMember.member; // 2. 기본 인스턴스 사용
  • 같은 테이블을 조인해야 하는 경우가 아니면 기본 인스턴스를 사용하자

✏️ 기본 인스턴스를 static import와 함께 사용

import static study.querydsl.entity.QMember.*;

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Autowired
    EntityManager em;

    JPAQueryFactory queryFactory;

    @BeforeEach
    public void before() {
        queryFactory = new JPAQueryFactory(em);
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        em.persist(teamA);
        em.persist(teamB);

        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);
        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);
        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);
    }

    @Test
    public void startQuerydsl() {
        queryFactory = new JPAQueryFactory(em);
        Member findMember = queryFactory
                .select(member)
                .from(member)
                .where(member.username.eq("member1"))
                .fetchOne();

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

2️⃣ 검색 조건 쿼리

1. 기본 검색 쿼리

@Test
public void search() {
    Member findMember = queryFactory
            .selectFrom(member)
            .where(member.username.eq("member1")
                    .and(member.age.eq(10)))
            .fetchOne();

    Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
  • 검색 조건은 .and(), .or() 을 메서드 체인으로 연결할 수 있다.
  • select , from을 selectFrom() 으로 합칠 수 있음

2. 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%’ 검색 
...

 

3. AND 조건을 파라미터로 처리

@Test
public void searchAndParam() {
    List<Member> result1 = queryFactory
            .selectFrom(member)
            .where(
                    member.username.eq("member1"),
                    member.age.eq(10)
            )
            .fetch();

    Assertions.assertThat(result1.size()).isEqualTo(1);
}
  • where()에 파라미터를 콤마(,)로 검색조건을 추가하면 AND 조건이 추가됨
  • AND 조건만 있는 경우, 이 방법을 추천함.
  • 이 경우 null 값은 무시 → 메서드 추출을 활용해서 동적쿼리를 깔끔하게 만들 수 있음 

3️⃣ 결과 조회

@Test
public void resultFetch() {

	//List
    List<Member> fetch = queryFactory
            .selectFrom(member)
            .fetch();
	
    //단 건
    Member fetchOne = queryFactory
            .selectFrom(member)
            .where(member.username.eq("member1"))
            .fetchOne();
	
    //처음 한 건 조회
    Member fetchFirst = queryFactory
            .selectFrom(member)
            .fetchFirst();
	
    //페이징에서 사용
    QueryResults<Member> results = queryFactory
            .selectFrom(member)
            .fetchResults();

    results.getTotal();
    List<Member> content = results.getResults();

	//count 쿼리로 변경
    long total = queryFactory
            .selectFrom(member)
            .fetchCount();
}
  • fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
  • fetchOne() : 단 건 조회
    • 결과가 없으면 : null
    • 결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
  • fetchFirst() : limit(1).fetchOne()
  • fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행
  • fetchCount() : count 쿼리로 변경해서 count 수 조회

4️⃣ 정렬

/**
 * 회원 정렬 순서
 * 1. 회원 나이 내림차순(desc)
 * 2. 회원 이름 올림차순(asc)
 * 단 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
 **/
@Test
public void sort() {
    em.persist(new Member(null, 100));
    em.persist(new Member("member5", 100));
    em.persist(new Member("member6", 100));

    List<Member> result = queryFactory
            .selectFrom(member)
            .orderBy(member.age.desc(), member.username.asc().nullsLast())
            .fetch();

    Member member5 = result.get(0);
    Member member6 = result.get(1);
    Member memberNull = result.get(2);

    Assertions.assertThat(member5.getUsername()).isEqualTo("member5");
    Assertions.assertThat(member6.getUsername()).isEqualTo("member6");
    Assertions.assertThat(memberNull.getUsername()).isNull();
}
  • desc() , asc() : 일반 정렬
  • nullsLast() , nullsFirst() : null 데이터 순서 부여

5️⃣ 페이징

1. 조회 건수 제한

@Test
public void paging1() {
    List<Member> result = queryFactory
            .selectFrom(member)
            .orderBy(member.username.desc())
            .offset(1)
            .limit(2)
            .fetch();

    Assertions.assertThat(result.size()).isEqualTo(2);
}

 

2. 전체 조회 수가 필요하면?

@Test
public void paging2() {
    QueryResults<Member> queryResults = queryFactory
            .selectFrom(member)
            .orderBy(member.username.desc())
            .offset(1)
            .limit(2)
            .fetchResults();

    Assertions.assertThat(queryResults.getTotal()).isEqualTo(4);
    Assertions.assertThat(queryResults.getLimit()).isEqualTo(2);
    Assertions.assertThat(queryResults.getOffset()).isEqualTo(1);
    Assertions.assertThat(queryResults.getResults().size()).isEqualTo(2);
}
  • count 쿼리가 실행되니 성능상 주의해야 한다.
참고: 실무에서 페이징 쿼리를 작성할 때, 데이터를 조회하는 쿼리는 여러 테이블을 조인해야 하지만, count 쿼리는 조인이 필요 없는 경우도 있다. 그런데 이렇게 자동화된 count 쿼리는 원본 쿼리와 같이 모두 조인을 해버리기 때문에 성능이 안나올 수 있다. count 쿼리에 조인이 필요없는 성능 최적화가 필요하다면, count 전용 쿼리를 별도로 작성해야 한다.

 

🚨 문제 상황

JPAQuery<Member> query = queryFactory
    .selectFrom(member)
    .leftJoin(member.friends, friend)
    .where(member.name.eq("John"));

// 페이징 처리
QueryResults<Member> results = query.fetchResults();
long totalCount = results.getTotal();

 

1. 실제 데이터 조회 쿼리 (JOIN 포함)

SELECT m.*, f.*
FROM member m
LEFT JOIN friend f ON m.id = f.member_id
WHERE m.name = 'John'
LIMIT 10 OFFSET 0;

 

2. 자동 생성된 COUNT 쿼리 (JOIN 포함)

SELECT COUNT(*)
FROM member m
LEFT JOIN friend f ON m.id = f.member_id
WHERE m.name = 'John';
  • 이때, COUNT 쿼리는 JOIN이 필요 없을 수도 있지만, QueryDSL의 fetchResults()는 기본적으로 원본 쿼리와 동일한 구조로 COUNT 쿼리를 생성한다.
  • 불필요한 JOIN으로 인해 성능이 저하될 수 있다.

✅ 해결 방법: count 전용 쿼리 분리

 

1. 데이터 조회 쿼리

List<Member> members = queryFactory
    .selectFrom(member)
    .leftJoin(member.friends, friend).fetchJoin() // 필요한 경우만 JOIN 유지
    .where(member.name.eq("John"))
    .offset(0)
    .limit(10)
    .fetch();

 

2. 최적화된 COUNT 쿼리

long totalCount = queryFactory
    .select(member.count())  // COUNT 쿼리는 JOIN 제거
    .from(member)
    .where(member.name.eq("John"))
    .fetchOne();

 

  • 불필요한 JOIN이 성능 문제를 유발하면, COUNT 쿼리를 별도로 작성하여 최적화할 것.
  • fetchResults() 대신 .fetch()와 .fetchOne()을 조합하여 직접 페이징 처리하는 것이 성능상 유리할 수 있음.

6️⃣ 집합

1. 집합 함수

/**
     * JPQL
     * select
     *    COUNT(m),//회원수
     *    SUM(m.age), //나이 합
     *    AVG(m.age), //평균 나이
     *    MAX(m.age), //최대 나이
     *    MIN(m.age) //최소 나이
     * from Member m
     */
    @Test
    public void aggregation() {
        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);
        Assertions.assertThat(tuple.get(member.count())).isEqualTo(4);
        Assertions.assertThat(tuple.get(member.age.sum())).isEqualTo(100);
        Assertions.assertThat(tuple.get(member.age.avg())).isEqualTo(25);
        Assertions.assertThat(tuple.get(member.age.max())).isEqualTo(40);
        Assertions.assertThat(tuple.get(member.age.min())).isEqualTo(10);
    }

 

2. 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)
                .having(team.name.ne("teamB"))
                .fetch();

        Tuple teamA = result.get(0);
        //Tuple teamB = result.get(1);

        Assertions.assertThat(teamA.get(team.name)).isEqualTo("teamA");
        Assertions.assertThat(teamA.get(member.age.avg())).isEqualTo(15);

        //Assertions.assertThat(teamB.get(team.name)).isEqualTo("teamB");
        //Assertions.assertThat(teamB.get(member.age.avg())).isEqualTo(35);
    }
  • groupBy , 그룹화된 결과를 제한하려면 having

7️⃣ 조인

1. 기본 조인

join(조인 대상, 별칭으로 사용할 Q타입)
  • 조인의 기본 문법은 첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭(alias)으로 사용할 Q 타입을 지정하면 된다.
/**
 * 팀 A에 소속된 모든 회원을 찾아라
 */
@Test
public void join() {
    List<Member> result = queryFactory
            .selectFrom(member)
            .join(member.team, QTeam.team)
            .where(team.name.eq("teamA"))
            .fetch();
    
    Assertions.assertThat(result)
            .extracting("username")
            .contains("member1", "member2");

}
  • join() , innerJoin() : 내부 조인(inner join)
  • leftJoin() : left 외부 조인(left outer join)
  • rightJoin() : rigth 외부 조인(rigth outer join)
  • JPQL의 on과 성능 최적화를 위한 fetch`조인 제공 

2. 세타 조인

연관관계가 없는 필드로 조인

/**
 * 세타 조인
 * 회원의 이름이 팀 이름과 같은 회원 조회
 */
@Test
public void theta_join() {
    em.persist(new Member("teamA"));
    em.persist(new Member("teamB"));
    em.persist(new Member("teamC"));

    List<Member> result = queryFactory
            .select(member)
            .from(member, team)
            .where(member.username.eq(team.name))
            .fetch();

    Assertions.assertThat(result)
            .extracting("username")
            .containsExactly("teamA", "teamB");
}
  • from 절에 여러 엔티티를 선택해서 세타 조인
  • 외부 조인 불가능 → 조인 on을 사용하면 외부 조인 가능 (연관관계 없는 엔티티 외부 조인 부분 확인)

3. ON절을 활용한 조인(JPA 2.1부터 지원)

  1. 조인 대상 필터링
  2. 연관관계 없는 엔티티 외부 조인

✅ 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);
    }
}

 

결과

tuple = [Member(id=1, username=member1, age=10), Team(id=1, name=teamA)]
tuple = [Member(id=2, username=member2, age=20), Team(id=1, name=teamA)]
tuple = [Member(id=3, username=member3, age=30), null]
tuple = [Member(id=4, username=member4, age=40), null]
  • ON 조건으로 team.name = 'teamA'인 팀만 조인됨
  • member에 매칭되는 팀이 없으면 NULL이 들어감 (LEFT JOIN이기 때문)
참고: on 절을 활용해 조인 대상을 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링 하는 것과 기능이 동일하다. 따라서 on 절을 활용한 조인 대상 필터링을 사용할 때, 내부조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자.

 

예시 1) INNER JOIN + ON vs WHERE

queryFactory
    .select(member, team)
    .from(member)
    .join(member.team, team) // INNER JOIN
    .on(team.name.eq("teamA")) // 필터링
    .fetch();

 

INNER JOIN을 사용하면, 아래 SQL과 동일함

SELECT m.*, t.*
FROM member m
INNER JOIN team t
ON t.team_id = m.team_id 
AND t.name = 'teamA';
  • 이 경우, WHERE 절을 쓰는 것과 동일한 결과가 됨!

👉 WHERE 절로 변경해도 같은 결과

queryFactory
    .select(member, team)
    .from(member)
    .join(member.team, team) // INNER JOIN
    .where(team.name.eq("teamA")) // WHERE 조건
    .fetch();
SELECT m.*, t.*
FROM member m
INNER JOIN team t
ON t.team_id = m.team_id 
WHERE t.name = 'teamA';

 

  • INNER JOIN에서 ON 절을 이용한 필터링은 사실상 WHERE 절 필터링과 동일!
  • 따라서 INNER JOIN이면 익숙한 WHERE 절을 사용하는 것이 좋음.

 

 

예시 2) 🚨 LEFT JOIN + ON은 다름!

queryFactory
    .select(member, team)
    .from(member)
    .leftJoin(member.team, team)
    .on(team.name.eq("teamA"))
    .fetch();

 

LEFT JOIN을 쓰면, 아래 SQL과 동일

SELECT m.*, t.*
FROM member m
LEFT JOIN team t
ON t.team_id = m.team_id 
AND t.name = 'teamA';
  • 팀이 없는 멤버도 남지만, team이 "teamA"인 경우만 조인됨
  • 즉, LEFT JOIN이므로 매칭되는 팀이 없으면 NULL이 들어간다

하지만 WHERE 절을 사용하면, 조인 후 필터링되어 "teamA"가 아닌 멤버 자체도 사라짐!

SELECT m.*, t.*
FROM member m
LEFT JOIN team t
ON t.team_id = m.team_id 
WHERE t.name = 'teamA';
  • WHERE 절을 쓰면, "teamA"가 아닌 멤버는 아예 사라짐
  • 즉, WHERE 절은 전체 결과를 필터링하는 반면, ON 절은 조인하는 대상만 제한함.

🎯 결론

조인 사용한 필터링 결과
LEFT JOIN + ON ON (team.name = 'teamA') TeamA가 아닌 경우 NULL 처리, 팀 없는 멤버도 유지
INNER JOIN + ON ON (team.name = 'teamA') TeamA가 아닌 멤버 제거 (WHERE 절과 동일)
INNER JOIN + WHERE WHERE team.name = 'teamA' INNER JOIN + ON과 동일한 결과
LEFT JOIN + WHERE WHERE team.name = 'teamA' INNER JOIN처럼 동작 (팀이 없으면 결과에서 제외됨)
  • INNER JOIN이라면 ON 보다는 익숙한 WHERE을 쓰는 게 좋다
  • LEFT JOIN에서 ON을 사용하면, 조인 대상만 제한할 수 있다 (WHERE과 다름)
  • WHERE을 쓰면 최종 결과를 필터링하고, ON은 조인 대상을 제한하는 차이가 있음
  • 👉 INNER JOIN이면 익숙한 WHERE 절 사용, 정말 LEFT JOIN이 필요한 경우에만 ON 절 필터링 사용! 🚀

✅ 2. 연관관계 없는 엔티티 외부 조인

/**
 * 2. 연관관계 없는 엔티티 외부 조인
 * 예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
 * JPQL: SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
 * SQL: SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
 **/
@Test
public void join_on_no_relation() {
    em.persist(new Member("teamA"));
    em.persist(new Member("teamB"));
    em.persist(new Member("teamC"));

    List<Tuple> result = queryFactory
            .select(member, team)
            .from(member)
            .leftJoin(team)
            .on(member.username.eq(team.name))
            .fetch();

    for (Tuple tuple : result) {
        System.out.println("tuple = " + tuple);
    }
}
  • 하이버네이트 5.1부터 on`을 사용해서 서로 관계가 없는 필드로 외부 조인하는 기능이 추가되었다. 물론 내부 조인도 가능하다.
  • 주의! 문법을 잘 봐야 한다. leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어간다.
    • 일반 조인: leftJoin(member.team, team)
    • on 조인: from(member).leftJoin(team).on(xxx)

결과

tuple = [Member(id=1, username=member1, age=10), null]
tuple = [Member(id=2, username=member2, age=20), null]
tuple = [Member(id=3, username=member3, age=30), null]
tuple = [Member(id=4, username=member4, age=40), null]
tuple = [Member(id=5, username=teamA, age=0), Team(id=1, name=teamA)]
tuple = [Member(id=6, username=teamB, age=0), Team(id=2, name=teamB)]
tuple = [Member(id=7, username=teamC, age=0), null]

 

4. 조인 - 페치 조인

페치 조인은 SQL에서 제공하는 기능은 아니다.

SQL조인을 활용해서 연관된 엔티티를 SQL 한번에 조회하는 기능이.

로 성능 최적화에 사용하는 방법이다.

 

❌ 페치 조인 미적용 -지연로딩으로 Member, Team SQL 쿼리 각각 실행

@PersistenceUnit
EntityManagerFactory emf;

@Test
public void fetchJoinNo() {
    em.flush();
    em.clear();

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

    boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
    Assertions.assertThat(loaded).as("페치 조인 미적용").isFalse();
}
  • 지연 로딩(Lazy Loading) 때문에 조인을 하더라도 fetchJoin()을 사용하지 않으면 Member 객체는 실체 객체로 가져오지만, Team 객체는 프록시 객체로 가져온다. 
  • Team은 지연 로딩(Lazy Loading)이므로, 처음에는 프록시로 로딩되고, 실제로 member.getTeam()을 호출할 때 두 번째 쿼리가 실행되어 Team 데이터를 가져온다.

✅ 페치 조인 적용 - 즉시로딩으로 Member, Team SQL 쿼리 조인으로 한번에 조회

@Test
public void fetchJoinUse() {
    em.flush();
    em.clear();

    Member findMember = queryFactory
            .selectFrom(member)
            .join(member.team, team).fetchJoin()
            .where(member.username.eq("member1"))
            .fetchOne();

    boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
    Assertions.assertThat(loaded).as("페치 조인 적용").isTrue();
}
  • fetchJoin()을 사용하면 프록시 객체를 거치지 않고 실제 객체를 가져오게 된다. (즉시 로딩 방식)
  • join(), leftJoin() 등 조인 기능 뒤에 fetchJoin() 이라고 추가하면 된다.

8️⃣ 서브 쿼리 - com.querydsl.jpa.JPAExpressions 사용

 

1. 서브 쿼리 eq 사용

/**
 * 나이가 가장 많은 회원을 조회
 */
@Test
public void subQuery() {

    QMember memberSub = new QMember("memberSub");

    List<Member> result = queryFactory
            .selectFrom(member)
            .where(member.age.eq(
                    JPAExpressions
                            .select(memberSub.age.max())
                            .from(memberSub)
            ))
            .fetch();

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

 

2. 서브 쿼리 goe 사용

/**
 * 나이가 평균 나이 이상인 회원 */
@Test
public void subQueryGoe() throws Exception {

    QMember memberSub = new QMember("memberSub");

    List<Member> result = queryFactory
            .selectFrom(member)
            .where(member.age.goe(
                    JPAExpressions
                            .select(memberSub.age.avg())
                            .from(memberSub)
            ))
            .fetch();

    Assertions.assertThat(result)
            .extracting("age")
            .containsExactly(30, 40);
}

3. 서브쿼리 여러 건 처리 in 사용

/**
 * 서브쿼리 여러 건 처리, in 사용
 **/
@Test
public void subQueryIn() throws Exception {

    QMember memberSub = new QMember("memberSub");

    List<Member> result = queryFactory
            .selectFrom(member)
            .where(member.age.in(
                    JPAExpressions
                            .select(memberSub.age)
                            .from(memberSub)
                            .where(memberSub.age.gt(10))
            ))
            .fetch();
    Assertions.assertThat(result)
            .extracting("age")
            .containsExactly(20, 30, 40);
}

 

4. select 절에 서브 쿼리

@Test
public void selectSubQuery() {

    QMember memberSub = new QMember("memberSub");

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

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

 

static import 활용

@Test
public void selectSubQuery() {

    QMember memberSub = new QMember("memberSub");

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

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

 

from 절의 서브쿼리 한계

JPA JPQL 서브쿼리의 한계점으로 from 절의 서브쿼리(인라인 뷰)는 지원하지 않는다. 당연히 Querydsl도 지원하지 않는다. 하이버네이트 구현체를 사용하면 select 절의 서브쿼리는 지원한다. Querydsl도 하이버네이트 구현체를 사용하면 select 절의 서브쿼리를 지원한다.

 

from 절의 서브쿼리 해결방안

  1. 서브쿼리를 join으로 변경한다. (가능한 상황도 있고, 불가능한 상황도 있다.)
  2. 애플리케이션에서 쿼리를 2번 분리해서 실행한다.
  3. nativeSQL을 사용한다.

9️⃣ Case

select, 조건절(where), order by에서 사용 가능

 

1. 단순한 조건

@Test
public void basicCase() {
    List<String> result = queryFactory
            .select(
                    member.age
                            .when(10).then("10살")
                            .when(20).then("20살")
                            .otherwise("기타"))
            .from(member)
            .fetch();

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

 

2. 복잡한 조건

@Test
public void complexCase() {
    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 str : result) {
        System.out.println("str = " + str);
    }
}

 

3. orderBy에서 Case 문 함께 사용하기 예제

예를 들어서 다음과 같은 임의의 순서로 회원을 출력하고 싶다면?

  • 1. 0 ~ 30살이 아닌 회원을 가장 먼저 출력
  • 2. 0 ~20살 회원 출력
  • 3. 21 ~ 30살 회원 출력
@Test
public void complexCase2() {

    NumberExpression<Integer> rankPath = new CaseBuilder()
            .when(member.age.between(0, 20)).then(2)
            .when(member.age.between(21, 30)).then(1)
            .otherwise(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
username = member1 age = 10 rank = 2
username = member2 age = 20 rank = 2
username = member3 age = 30 rank = 1
  • Querydsl은 자바 코드로 작성하기 때문에 rankPath 처럼 복잡한 조건을 변수로 선언해서 select 절, orderBy 절에서 함께 사용할 수 있다.

🔟 상수, 문자 더하기

Tuple result = queryFactory
         .select(member.username, Expressions.constant("A"))
         .from(member)
         .fetchFirst();
  • 상수가 필요하면 Expressions.constant(xxx) 사용
  • 위와 같이 최적화가 가능하면 SQLconstant 값을 넘기지 않는다.
  • 상수를 더하는 것 처럼 최적화가 어려우면 SQLconstant 값을 넘긴다.

문자 더하기 concat

 String result = queryFactory
         .select(member.username.concat("_").concat(member.age.stringValue()))
         .from(member)
         .where(member.username.eq("member1"))
         .fetchOne();
  • 결과: member1_10
  • member.age.stringValue() 부분이 중요한데, 문자가 아닌 다른 타입들은 stringValue()로 문자로 변환할 수 있다.
  • 이 방법은 ENUM을 처리할 때도 자주 사용한다.