[QueryDSL] 기본 문법

황인찬·2024년 7월 15일
post-thumbnail

JPA vs QueryDSL

"member1" 찾는 코드 작성

  • JPQL
@Test
    public void startJPQL() {
        //member1 찾기
        String qlString =
                "select m from Member m " +
                "where m.username=:username";
        Member findMember = em.createQuery(qlString, Member.class)
                .setParameter("username", "member1")
                .getSingleResult();

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

    }
  • QueryDSL
@Test
    public void startQuerydsl() {
        QMember m = new QMember("m");

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

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }
  • JPQL
    • String 형태라 컴파일 시점에 오류를 잡기 어려움
    • 파라미터 바인딩이 필요함
  • QueryDSL
    • 코드 형태라 컴파일 시점에 오류를 잡을 수 있음
    • 파라미터 바인딩을 자동 처리해줌.

기본 Q-Type 활용

QMember qMember = new QMember("m"); //별칭 지정
QMember qMember = QMember.member; //기본 인스턴스 사용
  • static import를 활용하면 깔끔한 코드 작성 가능
import static study.querydsl.entity.QMember.*;

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

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

검색 조건 쿼리

  • JPQL이 제공하는 모든 검색 조건
member.username.eq("member1") //username = 'member1'
member.username.ne("member1") //username != 'member1'
member.username.eq("member1").not() //username != 'member1'

member.username.isNotNull() // 이름이 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%") //like 검색
member.username.contains("member") //like '%member%' 검색
member.username.startsWith("member") //like 'member%' 검색
  • AND 조건을 파라미터로 처리
    • ex) where(member.username.eq("member1"), member.age.eq(10))
      • 이 경우 null은 무시
  • 결과 조회
    • fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반호나
    • fetchOne() : 단 건 조회
      • 결과가 없으면 null
      • 결과가 여러개면 com.querydsl.core.NonUniqueResultException
    • fetchFirst() : limit(1).fetchOne()

정렬

  • desc(), asc() : 일반 정렬
  • nullsLast(), nullsFirst() : null 데이터에 순서 부여

페이징

  • offset() : 0부터 시작
  • limit() : 페이징 갯수

집합

  • 집합함수
	/**
     * JPQL
     * select
     *  COUNT(m),
     *  SUM(m.age),
     *  AVG(m.age),
     *  MAX(m.age),
     *  MIN(m.age)
     * from Member m
     */
    @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);

	}
  • GroupBy 사용
    • 그룹화된 결과를 제한하려면 having 사용

조인 - 기본조인

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

조인 - on절

  • on절을 활용한 조인(JPA 2.1버전 이상 지원)
    1. 조인 대상 필터링
    2. 연관관계 없는 엔티티 외부 조인(중요)
  1. 조인 대상 필터링
   /**
    * 예) 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
    * JPQL : select m,t from Member m left join m.team t on t.name = 'teamA'
    */
    @Test
    public void join_on_filtering() throws Exception{
        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);
        }

    }

참고: on절을 활용해 내부조인을 하게 될 경우 where 절과 결과가 동일. 따라서 on절을 활용한 조인 대상 필터링을 사용할 때, 내부 조인이면 where 절을 사용하고, 외부 조인이 필요한 경우는 on절을 사용.

  1. 연관관계 없는 엔티티 외부 조인
   /**
    * 연관관계가 없는 엔티티 외부 조인
    * 회원의 이름이 팀 이름과 같은 대상을 외부 조인
    */
    @Test
    public void join_on_no_relation() throws Exception{
        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);
        }

    }
 
  • 하이버네이트 5.1부터 서로 관계가 없는 필드로 외부 조인 하는 기능이 추가되었음.
  • 주의🚫 leftJoin()에 일반 조인과 다르게 엔티티가 하나만 들어간다!!
    • 일반 조인 : leftJoin(member.team, team)
    • on 조인 : from(member).leftJoin(team).on(xxxx)

조인 - 페치 조인

  • 사용방법 : join(), leftJoin() 뒤에 .fetchJoin()을 추가

서브 쿼리

  • com.querydsl.jpa.JPAExpressions 사용

  • select 절에도 서브 쿼리 사용 가능

  • from 절의 서브 쿼리 한계

    • JPA, JPQL의 서브 쿼리 한계점으로 from 절의 서브 쿼리는 지원X
    • 해결 방안
      • 서브 쿼리를 join으로 변경(가능한 상황도 있고, 불가능한 상황도 있음)
      • 애플리케이션에서 쿼리를 2번 분리하여 실행
      • nativeSQL 사용

    Case 문

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

  • 단순한 조건

       @Test
       public void basicCase() throws Exception{
           List<String> result = queryFactory
                   .select(member.age
                           .when(10).then("열살")
                           .when(20).then("스무살")
                           .otherwise("기타"))
                   .from(member)
                   .fetch();
    
           for (String s : result) {
               System.out.println("s = " + s);
           }
       }
  • 복잡한 조건

    • new CaseBulider() 사용
       @Test
       public void complexCase() throws Exception{
           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);
           }
       } 
  • order by 사용

    • Querydsl은 자바 코드로 작성하기 때문에 rankPath처럼 복잡한 조건을 변수로 선언해서 select 절, order by 절에서 사용 가능
        /**
        * 1. 0-30살이 아닌 회원을 가장 먼저 출력
        * 2. 0-20살 회원 출력
        * 3. 21-30살 회원 출력
        * @throws Exception
        */
       @Test
       public void orderByCase() throws Exception{
           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 username = tuple.get(member.username);
               Integer age = tuple.get(member.age);
               Integer rank = tuple.get(rankPath);
    
               System.out.println("username = " + username + " age = " + age + " rank = " + rank);
           }
    
       }

    상수, 문자 더하기

  • 상수가 필요하면 Expressions.constant(xxx) 사용

       @Test
       public void constant() throws Exception{
           List<Tuple> result = queryFactory
                   .select(member.username, Expressions.constant("A"))
                   .from(member)
                   .fetch();
    
           for (Tuple tuple : result) {
               System.out.println("tuple = " + tuple);
           }
       }

    최적화가 가능하면 SQL에 constant 값을 넘기지 않는다. 상수를 더하는 것 처럼 최적화가 어려우면 SQL에 constant 값을 넘긴다.

  • 문자 더하기 concat

    • 문자가 아닌 다른 타입들은 stringValue()로 문자로 변환 가능
    • ENUM을 처리할 때 자주 사용
        @Test
       public void concat() throws Exception{
           //{username}_{age}
           List<String> result = queryFactory
                   .select(member.username.concat("_").concat(member.age.stringValue()))
                   .from(member)
                   .where(member.username.eq("member1"))
                   .fetch();
    
           for (String s : result) {
               System.out.println("s = " + s);
           }
           
       }
profile
찬이's 개발로그

1개의 댓글

comment-user-thumbnail
2024년 7월 27일

1빠요

답글 달기