개발/JPA

[Querydsl] 검색 쿼리를 Querydsl로 구현하기(1)

highright96 2021. 7. 14.

기본 쿼리 타입(Q-Type)

Q클래스 인스턴스 사용 방법

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

 

별칭은 같은 테이블을 조인할 경우 인스턴스 구분을 위해 지정한다.

위의 경우가 아니면 기본 인스턴스를 사용하는 것이 좋다.

 

static import와 함께 사용

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

@Test
public void startQuerydsl3() {
Member findMember = queryFactory
            .select(member)
            .from(member)
            .where(member.username.eq("member1"))
            .fetchOne();
}

 

static import를 사용해 기본 인스턴스를 사용하면 코드를 간결하게 작성할 수 있다.

 

기본 검색 쿼리

@Test
void search() {
  //QMember qmember = QMember.member;
  Member findMember = queryFactory
    .selectFrom(member) //QMember.member를 static import
    .where(
      member.username.eq("member1").and(member.age.eq(10))
     )
    .fetchOne();
}

 

select, from을 selectFrom을 합칠 수 있다.

검색 조건은 and(), or() 메서드를 연결해 사용할 수 있다.

AND 조건을 아래와 같이 파라미터로 처리할 수 있다.

 

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

 

검색 조건

쿼리 타입(Q-Type) 필드는 필요한 대부 분의 메소드를 명시적으로 제공한다.

아래는 많이 사용되는 검색 조건 메소드 예시이다.(where절에서 사용)

 

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

 

결과 조회

전부 조회(fetch)

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

리스트를 조회하며, 데이터가 없으면 빈 리스트를 반환한다.

 

단 건 조회(fetchOne)

Member fetchOne = queryFactory
        .selectFrom(member)
        .fetchOne();

결과가 없으면 null을 반환하며, 결과가 둘 이상이면 com.querydsl.core.NonUniqueResultException이 발생한다.

 

처음 단 건 조회(fetchFirst)

Member fetchFirst = queryFactory
        .selectFrom(member)
        .fetchFirst();

limit(1).fetchOne과 동일하다.

 

정렬

정렬은 orderBy를 사용하는데 쿼리 타입(Q-Type)이 제공하는 asc(), desc() 메소드를 사용한다.

추가적으로 null 데이터의 순서를 부여하는 nullsLast(), nullsFirst() 메소드도 제공한다.

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

 

페이징

위에서 전체 조회를 하기 위해 fetch() 메서드만을 사용했다.

하지만 페이징 처리를 하려면 검색된 전체 데이터 수offset, limit를 알아야 한다.

이때는 fetch() 대신에 fetchResults() 메서드를 사용하면 된다.

QueryResults<Member> fetchResults = queryFactory
        .selectFrom(member)
        .orderBy(member.username.desc())
        .offset(1)
        .limit(2)
        .fetchResults();

 

fetchResults()의 반환값은 다음과 같다.

 

fetchResults.getTotal() //전체 조회 수
fetchResults.getLimit() //limit
fetchResults.getOffset() //offset
fetchResults.getResults().size() //검색 조회 수

 

하지만 데이터를 조회하는 쿼리는 여러 테이블을 조인해야 하지만, 전체 조회 수를 반환하는 count 쿼리는 조인이 필요없는 경우가 많다. 이렇게 fetchResults()로 자동화된 count 쿼리는 원본 쿼리(조인 쿼리)에 count를 하기 때문에 성능이 안나올 수 있다. 따라서 count 쿼리를 별도로 작성해 성능 최적화가 필요하다. 이 부분은 나중에 자세히 다루겠다.

 

집합 함수

쿼리 타입(Q-Type)이 제공하는 함수이다.

member.count() //조회 수
member.age.sum() //합
member.age.avg() //평균
member.age.max() //최대
member.age.min() //최소

 

그룹

그룹은 groupBy를 사용하고 그룹화된 결과를 제한하려면 having을 사용하면 된다.

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

 

참고

김영한님의 실전! Querydsl

김영한님의 자바 ORM 표준 JPA 프로그래밍

댓글