QueryDsl

QueryDSL 기본문법

유휴 2023. 1. 3. 00:49
 

실전! Querydsl - 인프런 | 강의

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

www.inflearn.com

 

문법 관련해서는 이미 정리가 잘된 강의자료가 있지만 SpringBoot + JPA + QueryDSL을 사용하여 토이프로젝트를 진행하기 전에 강의 내용을 한번 더 정리하고 진행하기 위해 해당 포스팅을 작성한다.

 

기본 Q-Type 활용

Q클래스 인스턴스를 사용하는 방법으로 다음과 같은 2가지 방법이 있다.

  • new 키워드를 사용하여 별칭을 직접 지정하여 생성하는 방법
  • QType에 public static final 키워드로 미리 생성되어있는 기본 인스턴스를 사용하는 방법
QMember qMember = new QMember("m"); //별칭 직접 지정 
QMember qMember = QMember.member; //기본 인스턴스 사용

 

같은 테이블을 조인해야 하는 경우가 아니면 기본 인스턴스를 사용하면 되며 QType을 static import 하여 사용하면 편리하다.

// 기본 인스턴스를 static import와 함께 사용하면 편리하다.
import static study.querydsl.entity.QMember.*;

...

public Member findByUserName(String userName) {
    Member findMember = queryFactory.select(member)
                                    .from(member)
                                    .where(m.userName.eq(userName))
                                    .fetchOne();
    return findMember;
}

검색 조건 쿼리

select().from()  selectFrom() 으로 합칠 수 있다.

Member findMember = queryFactory.select(member).from(member)
//========================================================//
Member findMember = queryFactory.selectFrom(member)

 

 

.and() 또는 .or() 메서드 체인으로 연결할 수 있다.

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

 

 

where 절에 다중 파라미터를 넣으면 and조건으로 처리하며 값이 null인 경우 해당 조건이 무시되어 동적쿼리를 편리하게 작성할 수 있다.

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

 

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

eq() = ne() != eq().not() !=
isNotNull() is not null in() in notIn() not in
between() between goe() >= gt() >
loe() <= lt() < like() like
contains() like(% || '  ' || %) startwith() like('  ' || %)    

정렬

  • desc(), asc() : 일반 정렬
  • nullsLast(), nullsFirst() : null 데이터 순서 부여
 List<Member> result = queryFactory.selectFrom(member)
                                   .where(member.age.eq(100))
                                   .orderBy(member.age.desc(), member.username.asc().nullsLast())
                                   .fetch();

결과 조회

메서드 설명
fetch() 리스트를 조회한다. 데이터가 없는경우 빈리스트를 반환한다.
fetchOne() 단 건 조회한다. 결과가 없으면 null을 반환하고,
여러건이 반환되는 경우 com.querydsl.core.NonUniqueResultException 예외를 발생시킨다.
fetchFirst() 단 건 조회한다. 결과가 없으면 null을 반환하고, 여러 건이 조회되는경우 첫번째 행을 반환한다.
fetchResults()
페이징 처리를 위한 정보를 포함하여 반환한다. ( total count 쿼리 추가 실행 )
fetchCount()
count 쿼리를 실행한다.

* fetchResults()와 fetchCount()가 명확하게 동작하지 않는 이슈가 발생하여 QueryDSL 5.0부터 deprecated 되었다.

  totalcount가 필요없다면 fetch()를 통해 페이징 처리하고 fetchCount() 대신 fetch().size()를 사용해야 한다.

페이징

fetch() 메서드를 사용하여 데이터 조회 시 offset()과 limit() 메서드를 이용해 조회 건 수를 제한할 수 있다.

 public Page<Member> findMemberWithPaging(Pageable pageable) {
 
     List<Member> content = queryFactory.selectFrom(member)
                                        .orderBy(member.username.desc())
                                        .offset(pageable.getOffset())
                                        .limit(pageable.getPageSize())
                                        .fetch();
                                   
     int totalCount = queryFactory.selectFrom(member)
                                  .orderBy(member.username.desc())
                                  .fetch().size();
                             
     return new PageImpl<>(content, pageable, totalCount);
}

집합

JPQL이 제공하는 모든 집합 함수를 제공한다.

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

 

조인

기본 조인

  • .join()
  • .leftJoin()
  • .rigthJoin()

첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭(alias)으로 사용할 Q 타입을 지정하면 된다.

List<Member> result = queryFactory.selectFrom(member)
                                  .join(member.team, team)
                                  .where(team.name.eq("teamA"))
                                  .fetch();

 

on절을 활용하여 native query처럼 작성할 수도 있다. 이런경우 연관관계가 없는 엔티티 간의 조인이 가능하다

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

연관관계가 없는 엔티티 간의 조인 시에는 leftJoin부분에 파라미터로 기준이 되지않는 엔티티만 들어간다.

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

세타 조인

join 키워드를 사용하지않고 연관관계가 없는 필드 간의 innerJoin이 가능하다. (outerJoin은 불가능)

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

Fetch 조인

join(), leftJoin() 등 조인 메서드 뒤에 .fetchJoin() 키워드를 사용하여 즉시로딩으로 조회할 수 있다.

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

서브쿼리

com.querydsl.jpa.JPAExpressions 클래스를 사용하여 select절과 where절에서 서브쿼리를 사용할 수 있다.

아쉽게도 from 절에서는 기능상의 문제로 사용할 수 없다고한다.

 

 

where절에서의 서브쿼리

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

 

select절에서의 서브쿼리

import static com.querydsl.jpa.JPAExpressions.select;

...

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

 

select절에서의 예제처럼 import static 키워드를 사용하여 편리하게 사용할 수 있다.

Case

일반적인 방법과 CaseBuilder 객체를 사용하여 복잡한 조건을 정의하는 방법을 지원한다.

-일반적인 방법-

List<String> result = queryFactory.select(member.age.when(10).then("열살")
                                                    .when(20).then("스무살")
                                                    .otherwise("기타"))
                                  .from(member)
                                  .fetch();

 

-CaseBuilder 객체를 사용하는 방법-

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

상수, 문자 더하기

Tuple result = queryFactory.select(member.username, Expressions.constant("A"))
                           .from(member)
                           .fetchFirst();
String result = queryFactory.select(member.username.concat("_").concat(member.age.stringValue()))
                            .from(member)
                            .where(member.username.eq("member1"))
                            .fetchOne();

문자가 아닌 타입들은 stringValue() 메서드를 통해 문자 타입으로 변경할 수 있다.