본문 바로가기
JPA/QueryDSL

[QueryDSL] 2. 기본 문법

by yoon_seon 2023. 5. 28.

기본 문법

1. 시작 - JPQL vs Querydsl

2. 기본 - Q-Type 활용

3. 검색 조건 쿼리

4. 결과 조회

5. 정렬

6. 페이징

7. 집합

8. 조인 - 기본 조인

9. 조인 - on절

10. 조인 - 페치 조인

11. 서브 쿼리

12. Case 문

13. 상수, 문자 더하기


📌 시작 - JPQL vs Querydsl

테스트 기본 코드

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @PersistenceContext
    private EntityManager em;

    @BeforeEach
    void before() {
        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);
    }
}
  • 앞으로 진행할 예제에서 사용할 원천 데이터를 @BeforeEach을 사용하여 세팅한다.

 

Querydsl vs JPQL

@Test
@DisplayName("JPQL로 member1을 조회한다.")
void startJPQL() {
    Member findMember = em.createQuery("select m from Member m where m.username = :name", Member.class)ㅊ
            .setParameter("name", "member1")
            .getSingleResult();

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

@Test
@DisplayName("queryqsl로 member1을 조회한다.")
void queryqsl() {
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);
    QMember m = new QMember("m");

    Member findMember = queryFactory
            .select(m)
            .from(m)
            .where(m.username.eq("member1")) // 파라미터 바인딩 처리
            .fetchOne();

    assertThat(findMember.getUsername()).isEqualTo("member1");
}
  • Querydsl을 사용하기 위해 EntityManagerJPAQueryFactory를 생성한다.
  • Querydsl은 JPQL 빌더
  • JPQL : 문자(실행 시점 오류)
    Querydsl : 코드(컴파일 시점 오류) → 오류를 컴파일 시점에 알 수 있다는 큰 장점이 있다
  • JPQL : 파라미터 바인딩 직접 세팅
    Querydsl : 파라미터 바인딩 자동 세팅

 

JPAQueryFactory를 필드로 뺄 수 있다.

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @PersistenceContext
    private EntityManager em;

    JPAQueryFactory queryFactory;
    
    @BeforeEach
    public void before() {
        queryFactory = new JPAQueryFactory(em); // 초기화
		    ...
    }
    
    //...   
}
  • JPAQueryFactory를 필드로 제공하면 동시성 문제는 어떻게 될까?
    동시성 문제는 JPAQueryFactory를 생성할 때 제공하는 EntityManager(em)에 달려있다. 스프링 프레임워크는 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도, 트랜잭션마다 별도의 영속성 컨텍스트를 제공하기 때문에, 동시성 문제는 걱정하지 않아도 된다.

 


📌 기본 - Q-Type 활용

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

QMember qMember = new QMember("m"); // 별칭 직접 지정
QMember qMember = QMember.member; // 기본 인스턴스 사용

 

기본 인스턴스를 static-import하여 사용 가능

import static study.querydsl.entity.QMember.member; // static-import 처리함

@Test
@DisplayName("queryqsl로 member1을 조회한다.")
void queryqsl() {
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

    Member findMember = queryFactory
            .select(member)
            .from(member)
            .where(member.username.eq("member1")) // 파라미터 바인딩 처리
            .fetchOne();

    assertThat(findMember.getUsername()).isEqualTo("member1");
}
  • 기본 인스턴스 사용 후 Q클래스를 static-import하여 member를 바로 사용
  • Querydsl에서 제공하는 기본 인스턴스를 사용하는 이 방법을 많이 사용한다.

 

별칭이 같을 경우

@Test
@DisplayName("queryqsl로 member1을 조회한다.")
void queryqsl() {
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

    QMember m1 = new QMember("m1");
    QMember m2 = new QMember("m2");
            
    Member findMember = queryFactory
            .select(m1)
            .from(m1)
            .where(m1.username.eq("member1")) // 파라미터 바인딩 처리
            .fetchOne();

    assertThat(findMember.getUsername()).isEqualTo("member1");
}
  • 같은 테이블을 두 번 JOIN 해야 하는 등 별칭이 같을 경우 static-import가 아닌 Q클래스를 직접 생성하고 이름을 다르게 할당하여 사용해야 한다. 

 


📌 검색 조건 쿼리

검색 조건 테스트

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

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

 

where() 파라미터로 검색조건

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

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

 

  • where()에 파라미터로 검색조건을 추가하면 AND 조건이 추가된다.
  • where()에 파라미터로 null이 존재할 경우 무시한다. → 동적 쿼리 생성에 용이

 

Querydsl은 JPQL이 제공하는 모든 검색 조건을 제공한다.

  • member.username.eq("a") : username = 'name'
  • member.username.ne("a") : username != 'a'
  • member.username.eq("a").not() : username != 'a'
  • member.username.isNotNull() : username 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) : age 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%") : username like 'member%'
  • member.username.contains("member') : username like '%member%'
  • member.username.startsWith("member") : like 'member%' 
  • .... 기타 등등

 


📌 결과 조회

@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(); // limit(1).fetchOne()과 동일

    // 페이징에서 사용
    QueryResults<Member> results = queryFactory
            .selectFrom(member)
            .fetchResults();
    long getTotal = results.getTotal(); // result에 count 포함
    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  수 조회

 


📌 정렬

sort 테스트

/**
 * 회원 정렬 순서
 * 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)
            .where(member.age.eq(100))
            .orderBy(member.age.desc(), member.username.asc().nullsLast())
            .fetch();

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

    assertThat(member5.getUsername()).isEqualTo("member5");
    assertThat(member6.getUsername()).isEqualTo("member6");
    assertThat(member7.getUsername()).isNull();
}
  • orderBy()desc(), asc()메서드를 사용하여 정렬 가능
  • nullsLast(), nullsFirst() : null 데이터의 순서 부여

 


📌 페이징

조회건수 제한

@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);
}
  • offset(1).limit(2) : index(0)을 생략하고 두 개를 선택한다. [0][1][2][3]

 

전체 조회 수가 필요한 경우

@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 쿼리가 같이 실행되어 총 2번 실행된다.
참고 : 실무에서 페이징 쿼리를 작성할 때, 데이터를 조회하는 쿼리는 여러 테이블을 조인해야 하지만, count 쿼리는 조인이 필요 없는 경우도 있다. 그런데 이렇게 자동화된 count 쿼리는 원본 쿼리와 같이 모두 조인하기 때문에 성능이 안 나올 수 있다. count 쿼리에 조인이 필요 없는 성능 최적화가 필요하다면 count 전용 쿼리를 별도로 작성해야 한다.

 


📌 집합

집합 함수

@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);

    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);
}
  • JPQL이 제공하는 모든 집합 함수를 제공한다.
  • Tuple을 사용하는 이유는 데이터 타입이 단일 타입이 아닌 여러 타입으로 들어오기 때문이다.
    더 자세한 사항은 다음 포스팅 '[QueryDSL] 3. 중급문법'에서 설명

Group By 사용 → 팀의 이름과 각 팀의 평균 연령을 구해라.

/**
 * 팀의 이름과 각 팀의 평균 연령을 구해라
 */
@Test
public void group() {
    // given
    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); // (10 + 20) / 2

    assertThat(teamB.get(team.name)).isEqualTo("teamB");
    assertThat(teamB.get(member.age.avg())).isEqualTo(35); // (30 + 40) / 2
}

 

group by의 그룹화된 결과를 제한하려면 having을 사용할 수 있다.

 

groupBy(), having() 예시

 …
.groupBy(item.price)
.having(item.price.gt(1000))
 …

 


📌 조인 - 기본 조인

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

기본 조인

/**
 * 팀 A에 소속된 모든 회원
 */
@Test
public void join() {
    List<Member> result = queryFactory
            .selectFrom(member)
            .join(member.team, team) // inner join과 동일
            .where(team.name.eq("teamA"))
            .fetch();

    assertThat(result)
            .extracting("username")
            .containsExactly("member1", "member2");
}
  • 조인의 기본 문법은 첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭(alias)으로 사용할 Q 타입을 지정하면 된다.
    .join(조인 대상, 별칭으로 사용할 Q타입)
  • 참고 : Assertions.assertThat()
    .extracting() : "username" 필드 지정
    .containsExactly() : 컬렉션 순서가 "member1", "member2"와 일치하는지 확인한다.

 

세타 조인 : 연관관계가 없는 필드로 조인

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

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

    assertThat(result)
            .extracting("username")
            .containsExactly("teamA", "teamB");
}
  • from 절에 여러 엔티티를 선택해서 세타 조인이 가능
  • 전혀 연관관계가 없는 필드로 조인 가능
  • 외부 조인이 불가능하지만 on 절을 사용하면 외부조인 가능

 


📌 조인 - on절

ON절을 활용한 조인(JPA2.1부터 지원)

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

 

1. 조인대상 필터링

회원과 팀을 조인하면서 팀이름이 teamA인 팀만 조인, 회원은 모두 조회

/**
 * 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
 * JPQL : select m, t, from Member m
 *          from Member m
 *          left join m.team t
 *            on t.name = 'teamA'
 */
@Test
public void join_on_filtering(){
    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=3, username=member1, age=10), Team(id=1, name=teamA)]
    tuple : [Member(id=4, username=member2, age=20), Team(id=1, name=teamA)]
    tuple : [Member(id=5, username=member3, age=30), null]
    tuple : [Member(id=6, username=member4, age=40), null]
     */
}

 

조인 대상 필터링 참고

  • on 절을 활용해 조인 대상을 필터링할 때, 외부조인(outer join)이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링하는 것과 기능이 동일하다.
  • 따라서 on 절을 활용한 조인 대상 필터링을 사용할 때, 내부조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자.

 

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

회원의 이름과 팀의 이름이 같은 대상 외부 조인

/**
 * 연관관계가 없는 엔티티를 외부 조인
 * 회원의 이름이 팀 이름과 같은 대상을 외부 조인
 */
@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);
    }
    
    /* 결과
    tuple : [Member(id=3, username=member1, age=10), null]
    tuple : [Member(id=4, username=member2, age=20), null]
    tuple : [Member(id=5, username=member3, age=30), null]
    tuple : [Member(id=6, username=member4, age=40), null]
    tuple : [Member(id=7, username=teamA, age=0), Team(id=1, name=teamA)]
    tuple : [Member(id=8, username=teamB, age=0), Team(id=2, name=teamB)]
    tuple : [Member(id=9, username=teamC, age=0), null]
    */
}
  • 하이버네이트 5.1부터 on을 사용해서 서로 관계가 없는 필드로 외부 조인하는 기능이 추가되었다. 물론 내부 조인도 가능하다.
  • 주의! 문법을 잘 봐야 한다. leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어간다.
    • 일반조인 : leftJoin(member.team, team)
    • on 조인 : from(member).leftJoin(team).on(xxx)

📌 조인 - 페치 조인

페치 조인은 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());
    assertThat(loaded).as("패치조인 미적용").isFalse();
}

 

페치 조인 적용

즉시로딩으로 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());
    assertThat(loaded).as("패치조인 미적용").isTrue();
}

사용 방법

  • join(), leftJoin()등 조인 기능 뒤에 fetchJoin()이라고 추가하면 된다.

 


📌 서브 쿼리

com.querydsl.jpa.JPAExpressions 사용하면 서브쿼리 사용이 가능하다.

 

서브 쿼리 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();

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

 

서브 쿼리 goe 사용 ( >= )

/**
 * 나이가 평균 이상인 회원
 */
@Test
public void subQueryGoe() {
    QMember memberSub = new QMember("memberSub");
    List<Member> result = queryFactory
            .selectFrom(member)
            .where(member.age.goe(
                    JPAExpressions
                            .select(memberSub.age.avg())
                            .from(memberSub)
            ))
            .fetch();

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

 

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

@Test
public void subQueryIn() {
    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();

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

 

select 절에 subquery

@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);
    }
}
  • JPAExpressions를 static-import 하여 코드를 더 간략하게 구현할 수 있다.

 

from 절의 서브쿼리 한계

JPA JPQL 서브쿼리의 한계점으로 from 서브쿼리(인라인 뷰)는 지원하지 않는다. Querydsl은 JPQL 빌더이기에 역시 지원하지 않는다.

하이버네이트 구현체를 사용하면 select 절의 서브 쿼리는 지원한다. Querydsl도 하이버네이트 구현체를 사용하면 select 절의 서브쿼리를 지원한다.

 

 

from 절의 서브쿼리 해결방안

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

 


📌 Case 문

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

 

단순한 조건

@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 s : result) {
        System.out.println("s : "+s);
    }
}
  • age가 10이면 '10살', 20이면 '20살' 이외는 '기타' 출력

 

복잡한 조건

@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 s : result) {
        System.out.println("s : "+s);
    }
}
  • new CaseBuilder() 하여 동작한다.
  • 단순한 조건과 다르게 when() 안에 조건이 들어간다.

 

orderBy에서 Case문을 사용하고 싶다면?

@Test
public void orderByCase() {
    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 name = tuple.get(member.username);
        Integer age = tuple.get(member.age);
        Integer rank = tuple.get(rankPath);
        System.out.println("name : "+name+", age : "+age+", rank : "+rank);
    }
    /*
    [결과]
    name : member4, age : 40, rank : 3
    name : member1, age : 10, rank : 2
    name : member2, age : 20, rank : 2
    name : member3, age : 30, rank : 1
    */
}
  • 21~30살, 0~20살, 이외 순으로 출력
  • Querydsl은 자바 코드로 작성하기 때문에 rankPath 처럼 복잡한 조건을 변수로 선언해서 select 절, orderBy 절에서 함께 사용할 수 있다

 


📌 상수, 문자 더하기

상수를 출력해야 한다면 Expressions.constant(xxx)를 사용하여 출력할 수 있다.

@Test
public void constant() {
    List<Tuple> result = queryFactory
            .select(member.username, Expressions.constant("A"))
            .from(member)
            .fetch();

    for (Tuple tuple : result) {
        System.out.println("tuple : "+tuple);
    }
    /*
    [실행 SQL]
    select
        member1.username 
    from
        Member member1
        
    [결과]
    tuple : [member1, A]
    tuple : [member2, A]
    tuple : [member3, A]
    tuple : [member4, A]
    */
}
참고 : 위와 같이 최적화가 가능하면 SQL에 constant 값을 넘기지 않는다. 상수를 더하는 것 처럼 최적화가 어려우면 SQL에 constant 값을 넘긴다.

 

문자 더하기 concat

@Test
public void concat() {
    // {username}_{age}
    List<String> result = queryFactory
            .select(member.username.concat("_").concat(member.age.stringValue()))
            .from(member)
            .fetch();

    for (String s : result) {
        System.out.println("s : "+s);
    }
    /*
    [실행 SQL]
    select
        concat(concat(member1.username,
        ?1),
        str(member1.age)) 
    from
        Member member1
        
    [결과]
    s : member1_10
    s : member2_20
    s : member3_30
    s : member4_40
    */
}
  • member.username은 String 타입이고 member.age는 int 타입이므로 member.age를 stringValue()를 통해 String 타입으로 변환 후 concat하여 데이터를 가져올 수 있다.
  • 이 방법은 ENUM 타입을 처리할 때 자주 사용되니 기억해두자.

 


해당 글은 인프런의 [실전! Querydsl] 강의를 정리한 내용입니다.

 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, - 강의 소개 | 인프런

www.inflearn.com

댓글