queryDSL

 

querydsl

 

페이징

List<Member> result = queryFactory
        .selectFrom(member)
        .orederBy(member.username.desc())
        .offset(1) // zero-based
        .limit(2)
        .fetch()

orderBy, offset, limit의 조합을 기억하자.

집합

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

Tuple은 queryDSL에서 제공하는 Type이다.
데이터 타입이 여러 종류 들어오기 때문에 위 예제처럼 Tuple로 반환하고 select에 넣었던 파라미터로 불러올 수 있다.

DTO로 받도록 설계할 수도 있다.

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);
assertThat(teamB.get(team.name)).isEqualTo("teamB");
assertThat(teamB.get(member.age.avg())).isEqualTo(35);

join과 groupBy를 활용해서 팀의 이름으로 묶고 팀의 나이 평균를 구하는 질의이다.

join

첫 번째 파라미터에 조인 대상을, 두 번재 파라미터에 별칭으로 사용할 Q-Type을 지정한다.

기본 활용은 위 예제에서 확인할 수 있다.

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 에 table을 복수 개 나열해는 방법.
연결하는 column없이 테이블을 모두 이어붙이기 때문에 매우 크다.
데이터베이스 별로 최적화를 한다.(아마도 hibernate가?)

추가
외부 조인이 불가능하다.
하지만 on을 사용하면 외부 조인 가능하다.

on clause(절)

일반적으로 inner join에서 where절과 같은 동작을 수행하므로 where로 해결한다.
left join에서는 where과 다르게 조인할 대상을 필터링하므로 사용할 수 있다.

좀 더 풀어서 설명하면,
from 절에 있는 테이블은 다 보여준다.
left join 절에 있는 테이블의 값들은 on에 부합한 row들만 출력되기 때문에 필터링되는 것이다.

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("t=" + tuple);
 }

위 예시는 leftJoin에 별칭으로 사용할 Q-Type인 team만 전달했다.
이러면 join할 id를 받지 않았다는 뜻이다.
이 방식으로 연관관계가 없는 테이블에서 사용할 수 있다.
뒤의 on 절로 조인할 테이블의 필터링을 적용했다.

fetch join

페치 조인은 sql에서 제공하는 기능이 아니다.
sql 조인을 활용해서 연관된 엔티티를 sql 한번에 조회하는 기능이다.
성능 최적화를 기대할 수 있다.

사용법은 join 절에 .fetchJoin()을 추가하면 된다.
LAZY로 되어있어도 한번의 질의를 통해 다 가져온다.

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

서브쿼리

com.querydsl.jpa.JPAExpressions 사용
멤버 메서드를 static으로 참조할 수 있다.

QMember memberSub = new QMember("memberSub");

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

서브 쿼리에서 주의해야할 점은 alias를 static 변수와 다르게 적용해야하기 때문에
새로운 Q-Type을 선언해야 한다는 점이다.

중간에 JPAExpressions.select(memberSub.age.max()).from(memberSub) 의 결과는 특정 max값을 가지고, 결과적으로 where에 의해서 가장 많은 나이를 가진 멤버를 출력한다.

서브쿼리 한계

JPA, JPQL, Hibernate의 한계로 from 절에서는 서브쿼리(인라인 뷰)를 지원하지 않는다.
Hibernate는 select의 서브 쿼리를 지원하기 때문에 하이버네이트를 사용하면 querydsl에서도 사용할 수 있다.

from 절에서 서브쿼리를 사용해야 한다면,
서브 쿼리를 join으로 변경하고, 쿼리를 분리해서 실행한다. 이마저도 안된다면 native sql로 해결한다.

상수 문자 더하기

Tuple result = queryFactory
   .select(member.username, Expressions.constant("A"))
   .from(member)
   .fetchFirst();

Expressions.constant()로 상수 구현
구현체가 최적화를 할 수 있다면 sql 질의에 상수를 추가하지 않고 보낸다.

String result = queryFactory
   .select(member.username.concat("_").concat(member.age.stringValue()))
   .from(member)
   .where(member.username.eq("member1"))
   .fetchOne();

concat으로 문자를 이을 수 있으며, 당연히 파라미터 타입은 String 이여야한다.
.stringValue()는 Enum Type에 대해서 유용하게 사용할 수 있다.

'스프링' 카테고리의 다른 글

QueryDSL 쪄먹기  (1) 2023.10.23
QueryDSL 볶아먹기  (0) 2023.10.22
QueryDSL 먹어버리기  (0) 2023.10.20
테스트 코드 정의와 이점, 왜 해야 할까? 그리고 무엇을 해야 할까?  (0) 2023.07.08
JPA란?  (0) 2023.07.06