Querydsl-기본

개발하는 도비·2023년 5월 9일

JPA

목록 보기
12/13
post-thumbnail

라이브러리 살펴보기

  • querydsl-apt: Querydsl 관련 코드 생성 기능 제공
  • querydsl-jpa: querydsl 라이브러리
    • jpa에 특화된 기능 -> 여러가지 다른 것도 존재.

기본 문법

Querydsl vs JPQL

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

  • EntityManager 로 JPAQueryFactory 생성
  • Querydsl은 JPQL 빌더
  • 특징
    • 코드(컴파일 시점 오류)
    • 파라미터 바인딩 자동 처리
@Test
public void startQuerydsl() {
	//member1을 찾아라.
    JPAQueryFactory queryFactory = new JPAQueryFactory(em); QMember m = new QMember("m");
    Member findMember = queryFactory
                .select(m)
                .from(m)
                .where(m.username.eq("member1"))//파라미터 바인딩 처리
                .fetchOne();
	assertThat(findMember.getUsername()).isEqualTo("member1");
}

기본 Q-Type 활용

  • Q클래스 인스턴스를 사용하는 2가지 방법
QMember qMember = new QMember("m"); //별칭 직접 지정 QMember qMember = QMember.member; //기본 인스턴스 사용
  • 기본 인스턴스를 static import와 함께 사용
import static study.querydsl.entity.QMember.*;
@Test
public void startQuerydsl3() {
//member1을 찾아라.
Member findMember = queryFactory
              .select(member)
              .from(member)
              .where(member.username.eq("member1"))
              .fetchOne();
      assertThat(findMember.getUsername()).isEqualTo("member1");
  }

검색 조건 쿼리

  • where 절 이후 end(), or() 추가 가능
  • 예제 코드
@Test
public void searchAndParam() {
	List<Member> result1 = queryFactory
              .selectFrom(member)
              .where(member.username.eq("member1"),
                      member.age.eq(10))
              .fetch();
    assertThat(result1.size()).isEqualTo(1);
}

결과 조회

  • 종류
    • fetch()
      • 리스트 조회, 데이터 없으면 빈 리스트 반환
    • fetchOne() :
      • 단 건 조회 결과가 없으면 : null
      • 결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
    • fetchFirst()
      • limit(1).fetchOne()와 같음
    • fetchResults()
      • 페이징 정보 포함, total count 쿼리 추가 실행
    • fetchCount()
      • count 쿼리로 변경해서 count 수 조회
  • 예제 코드
//List
List<Member> fetch = queryFactory
          .selectFrom(member)
          .fetch();
//단 건
Member findMember1 = queryFactory
          .selectFrom(member)
          .fetchOne();
//처음 한 건 조회
Member findMember2 = queryFactory
          .selectFrom(member)
          .fetchFirst();
//페이징에서 사용
QueryResults<Member> results = queryFactory
          .selectFrom(member)
          .fetchResults();
//count 쿼리로 변경
long count = queryFactory
          .selectFrom(member)
          .fetchCount();

정렬

  • 일반 정렬 : desc() , asc()
  • null 데이터 순서 부여: nullsLast() , nullsFirst()
    • {일반 정렬}.{null 데이터 순서 부여}로 사용.

페이징

  • offset() : 시작지점
  • limit() : 개수
  • 조회 건수 제한 예제
 @Test
 public void paging1() {
 	List<Member> result = queryFactory
              .selectFrom(member)
			  .orderBy(member.username.desc()) 
              .offset(1) //0부터 시작(zero index) .limit(2) //최대 2건 조회
              .fetch();
	assertThat(result.size()).isEqualTo(2);
}
  • 전체 조회 수 예제
    • spring boot 2.6이상부터 .fetchResults()가 deprecated 됨
    • fetchResult의 경우 count를 하기위해 count용 쿼리를 만든 뒤 실행하는데, select 쿼리를 기반으로 작동
      • 단순한 쿼리에서는 잘 동작하는데, 복잡한 쿼리에서는 잘 작동하지 않는 문제 존재.
    • 최근 버젼의 코드의 경우 이후 추가 예정
@Test
public void paging2() {
	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);
}

집합

집합함수

  • JPQL이 제공하는 모든 집합 함수를 제공.
    • COUNT(m) : 수
    • SUM(m.age) : 합
    • AVG(m.age) : 평균
    • MAX(m.age) : 최대
    • MIN(m.age) : 최소

GroupBy

  • 일반적으로 사용하는 의미와 같음. -> 같은 값을 가진 행끼리 하나의 그룹으로 만듬.
  • having() 지원 -> 그룹화된 결과를 제한헤줌

조인

기본 조인

  • join(조인 대상, 별칭으로 사용할 Q타입)
  • 종류
    • join(), innerJoin() : 내부 조인(inner join)
    • leftJoin() : left 외부 조인(left outer join)
    • rightJoin() : rigth 외부 조인(rigth outer join)
  • 예제
@Test
public void join() throws Exception {
    QMember member = QMember.member;
    QTeam team = QTeam.team;
    List<Member> result = queryFactory
            .selectFrom(member)
            .join(member.team, team)
            .where(team.name.eq("teamA"))
            .fetch();
    assertThat(result)
            .extracting("username")
            .containsExactly("member1", "member2");

세타 조인

  • 연관관계가 없는 필드로 조인
    • 외부 조인 불가능 -> 최근에는 가능함(on절 사용)
  • 예제
@Test
public void theta_join() throws Exception {
	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");
    }

on절

조인 대상 필터링

  • 내부조인(inner join)을 하면, where 절에서 필터링 하는 것과 기능이 동일
    • on 절을 활용한 조인 대상 필터링을 사용할 때, 내부조인 이면 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용.
  • 예제
    • 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
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);
        }
    }

연관관계 없는 엔티티 외부 조인

  • 일반 조인과 달리 entity하나만 들어감.
    • 일반조인: leftJoin(member.team, team)
    • on조인: from(member).leftJoin(team).on(xxx)
  • 예제
@Test
public void join_on_no_relation() throws Exception {
	em.persist(new Member("teamA"));
    em.persist(new Member("teamB"));
    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);
        } 
    }

조인-페치조인

  • SQL 기능 X -> entity를 한번에 조회하는 기능으로 성능 최적화에 사용
  • 조인 기능 뒤에 fetchJoin() 추가.
  • 예제
@Test
public void fetchJoinUse() throws Exception {
        em.flush();
        em.clear();
        Member findMember = queryFactory
                .selectFrom(member)
                .join(member.team, team).fetchJoin()
                .where(member.username.eq("member1"))
                .fetchOne();
        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
        assertThat(loaded).as("페치 조인 적용").isTrue();
    }

서브 쿼리

  • com.querydsl.jpa.JPAExpressions 사용

서브 쿼리 eq 사용

@Test
public void subQuery() throws Exception {
	QMember memberSub = new QMember("memberSub");
    List<Member> result = queryFactory
    	.selectFrom(member)
        .where(member.age.eq(
        	JPAExpressions
            	.select(memberSub.age.max())
                .from(memberSub)
		)) 
		.fetch();
	assertThat(result).extracting("age")
            .containsExactly(40);
}

서브 쿼리 goe 사용

@Test
public void subQueryGoe() throws Exception {
	QMember memberSub = new QMember("memberSub");
    List<Member> result = queryFactory
    	.selectFrom(member)
        .where(member.age.goe(
        	JPAExpressions
            	.select(memberSub.age.avg())
                .from(memberSub)
        )) 
        .fetch();
	assertThat(result).extracting("age")
    	.containsExactly(30,40);
}

서브쿼리 여러 건 처리 in 사용

@Test
public void subQueryIn() throws Exception {
    QMember memberSub = new QMember("memberSub");
    List<Member> result = queryFactory
            .selectFrom(member)
            .where(member.age.in(
                    JPAExpressions
                            .select(memberSub.age)
                            .from(memberSub)
                            .where(memberSub.age.gt(10))
            ))
            .fetch();
    assertThat(result).extracting("age")
            .containsExactly(20, 30, 40);
}

static import 활용 가능

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

서브 쿼리의 한계

  • JPA JPQL 서브쿼리의 한계점으로 from 절의 서브쿼리는 지원하지 않음
  • 해결 방안
    • 서브쿼리를 join으로 변경
    • 애플리케이션에서 쿼리를 2번 분리해서 실행 -> 속도 이슈가 있을 수는 있음
    • nativeSQL

case문

  • 단순한 조건

    • 예제
    @Test
    public void basicCase(){
      List<String> result = queryFactory
              .select(member.age
                      .when(10).then("열살") .when(20).then("스무살") .otherwise("기타"))
              .from(member)
              .fetch();
    
    }
  • 복잡한 조건

    • CaseBuilder를 사용.
    • 예제
    @Test
    public void ComplexCase(){
      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();
    }

상수, 문자 처리

  • 상수

    • Expressions.constant 사여=ㅛㅇ
    • 예제
    @Test
    public void constant(){
      List<Tuple> result = queryFactory
              .select(member.username, Expressions.constant("A"))
              .from(member)
              .fetch();
    
      for (Tuple tuple : result){
          System.out.println("tuple = " + tuple);
      }
    
    }
  • 문자

    • stringValue()
      • 문자가 아닌 다른 타입들을 문자로 바꿈
      • ENUM에서 자주 사용
    • 예제
    @Test
    public void concat(){
      String result = queryFactory
              .select(member.username.concat("_").concat(member.age.stringValue()))
              .from(member)
              .where(member.username.eq("member1"))
              .fetchOne();
    
    }

참조

  • 인프런 : 실전! Querydsl
  • 링크
profile
도비의 양말을 찾아서

0개의 댓글