queryDSL 기초 (조회, 정렬, 페이징, 집합)

queryDSL 시작전


간단한 원리

: 앞으로 어떻게 queryDSL을 이용해 쿼리를 작성하는 지에 대해 아주 간단하게 적어보자.

우선 당연 사용하기 위해선 관련 의존성을 가져와야겠지.

queryDSL 관련 plugin, dependency를 gradle or maven을 이용해 추가 후 컴파일하면 q type의 파일이 생성되는 것을 확인할 수 있어.

이 q type의 파일은 우리가 정의한 Entity를 바탕으로 생성된 거고 앞으로 queryDSL 쿼리를 날릴 때에는 이 q type의 생성된 녀석을 이용하게 되는거야.

참고로 이 q type 파일 관련해서는 형상관리에서 제외시키는게 좋아. 버전 마다 생성되는 부분이 달라질 수도 있는거고… 그래서 build 폴더 하위에 두면 어차피 git ignore에 포함되니 이렇게 사용하는 경우가 많아.



jpql vs queryDSL

우선 요즈음은 queryDSL을 이용해 쿼리를 작성할 때에는 JPAQueryFactory 객체를 사용해서 쿼리를 날려.

jpql의 경우 결국 작성하는 쿼리는 String이야. 즉 이말은 컴파일 타임에 잘못된 정보를 알 수 없다는 거야. (일부 ide의 발전으로 잡아주긴 하지만 일반적인 경우를 얘기)

하지만 queryDSL을 이용할 경우 결국 자바코드를 이용해 쿼리를 작성하는 것이고 그렇기에 컴파일 시점에 문법적으로 잘못된 부분들에 대해 오류를 찾을 수 있어. 이런 장점이 있지. 그 외에 동적인 쿼리를 작성하는 부분에서도 이점이 있고 아무래도 자바코드로 쿼리를 작성하다보니 익숙해지면 조금 더 편하데.



Q-Type

Q-Type은 직접 자기가 생성자를 통해 객체를 만들어줘두 되고 제공되는 static field를 통해 객체를 얻을 수도 있어.

보통은 static 변수로 선언되어 있는 걸 가져와서 사용하는 식으로 많이 써. 그게 조금 더 가독성이 좋아!!

1
2
3
4
5
6
7
8
//Member Entity를 바탕으로 Q-Type 즉 QMember가 만들어졌는 상호아

QMember m = new QMember("alias");
Member findMember = queryFactory
.select(m)
.from(m)
.where(m.username.eq("member1"))
.fetchOne();
1
2
3
4
5
6
7
//static variable을 가져와서 사용하되 
//앞단에 상위 경로는 static import를 통해 제거할 경우 깔끔하게 보임
Member findMember = queryFactory
.select(member)
.from(member)
.where(member.username.eq("member1"))
.fetchOne();

😀 참고 사항으로 실행할 때 queryDSL이 어떤 jpql로 변환되는지를 직접 확인하고 싶다면 application.yml(properties)파일 내 hibernate.user_sql_comment:true 넣어주면 돼!!




queryDSL 기초 (기본 검색, 정렬, 페이징, 집합)


기본 검색


조회

1
2
3
4
5
6
7
8
9
10
11
//전체 조회
List<Member> result = queryFactory
.select(member)
.From(member)
.fetch();

//위의 select, from을 아래처럼 합칠 수 있어
List<Member> result = queryFactory
.selectFrom(member)
.fetch();

위 처럼 할 수 있어.

fetch()의 결과로는 List 형태를 반환해.

fetchOne()은 단건에 대한 결과를 반환해.


조건

  1. AND
1
2
3
4
5
List<Member> result = queryFactory
.selectFrom(member)
.where(member.username.eq("member1")
.and(member.age.eq(10)))
.fetch();

위처럼 작성할 수도 있지만 and의 경우 ,를 구분자로 사용할 경우 자동으로 and로 인식해줘.

아래의 코드처럼 말이야.

1
2
3
4
5
List<Member> result = queryFactory
.selectFrom(member)
.where(member.username.eq("member1"),
member.age.eq(10))
.fetch();
  1. OR - 특별할 건 없고 위에서 .and 대신에 .or로 체이닝하면 돼!!

결과조회

  • fetch() : 리스트 조회. 값이 없다면 빈 리스트 반환해줘
  • fetchOne() : 단건 조회. (없으면 null, 둘 이상이면 NonUniqueResultException)
  • fetchFirst() : 얘는 limit(1).fetchOne() 과 똑같애. 그냥 맨 앞단 꺼 하나 가지고 오는거지
  • fetchResults() : 반환으로 QueryResults<> 형태의 결과를 던져줘. results라는 참조변수가 결과를 받았다고 가정해본다면, results.getTotal() 이런 메소드도 제공해줘. 물론 전체 카운트를 세기 위한 쿼리도 새로 날라가겠지. results.getResults()메소드를 실행하면 그 결과로 엔티티를 던져줘.
    😀  참고로 성능 때문에 total count를 알아내는 쿼리를 튜닝해야하는 경우가 있는데 이때는 total count용 쿼리는 따로 커스텀해서 날리는 게 좋아
  • fetchCount() : count 쿼리로 변경해서 count 수 조회.



정렬

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

위 처럼 .orderBy 괄호 안에 원하는 정렬 기준에 대해 차례로 , 를 구분자로하여 적어주면 돼!!

asc(), desc() 상황에 맞게 오름차순 내림차순을 쓰면 되고 null 값에 대해서는 앞에 오게할지(nullsFirst()) 마지막에 (nullsLast()) 오게 할지 설정 가능해.



페이징

1
2
3
4
5
6
List<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1) //0부터 시작(zero index)
.limit(2) //최대 2건 조회
.fetch();

위 처럼 offset과 limit를 설정해서 페이징 쿼리를 만들 수 있어.

만약 전체 카운트에 대한 부분이 필요하다면 위에서 나왔던 fetchResults()를 사용하면 돼!!

1
2
3
4
5
6
7
8
9
10
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 쿼리는 그렇지 않은 경우도 있어. 하지만 위처럼 자동화된 count 쿼리는 원본 쿼리(만약 조인을 하는 경우)와 같이 모두 조인을 해버리기 때문에 성능상 좋지 않아.

그래서 성능을 고려한다면 count 전용 쿼리를 별도로 작성해야해.



집합

1
2
3
4
5
6
7
8
List<Tuple> result = queryFactory
.select(member.count(),
member.age.sum(),
member.age.avg(),
member.age.max(),
member.age.min())
.from(member)
.fetch();

위처럼 카운트, 합, 평균, 최대, 최소를 구할 수 있어.

조회하고자하는 타입이 하나가 아니기에 그 결과는 Tuple 이라는 형태로 뱉어내.

group by는 아래처럼 사용하면 돼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);

//이렇게 뽑아낼 수 있어
teamA.get(team.name);
teamA.get(member.age.avg());







https://www.inflearn.com/course/Querydsl-실전