[Spring] 23.06.05 QueryDSL 란 (2)

hyewon jeong·2023년 6월 5일
0

Spring

목록 보기
57/65

1. QueryDSL이란?

QueryDSL은 하이버네이트 쿼리 언어(HQL: Hibernate Query Language)의 쿼리를 타입에 안전하게 생성 및 관리해주는 프레임워크이다.

QueryDSL은 정적 타입을 이용하여 SQL과 같은 쿼리를 생성할 수 있게 해 준다.

⭐️ 자바 백엔드 기술은 Spring Boot와 Spring Data JPA를 함께 사용한다. 하지만, 복잡한 쿼리, 동적 쿼리를 구현하는 데 있어 한계가 있다.

📌 이러한 문제점을 해결할 수 있는 것이 QueryDSL이다.

QueryDSL이 등장하기 이전에는 Mybatis, JPQL, Criteria 등 문자열 형태로 쿼리문을 작성하여 컴파일 시에 오류를 발견하는 것이 불가능했다.

하지만, QueryDSL은 자바 코드로 SQL 문을 작성할 수 있어 컴파일 시에 오류를 발생하여 잘못된 쿼리가 실행되는 것을 방지할 수 있다.

2. QueryDSL의 원리

2-1. 쿼리 타입(Q타입) 생성:

QueryDSL을 사용하기 위해 먼저 엔티티 클래스에 대응하는 쿼리 타입(Q타입) 클래스를 생성해야 합니다. 쿼리 타입 클래스는 엔티티의 속성과 관련된 필드와 메서드를 가지며, 쿼리 작성에 사용됩니다. 쿼리 타입 클래스는 Annotation Processor를 사용하여 컴파일 시점에 생성됩니다.

2-2. 쿼리 작성:

생성된 쿼리 타입 클래스를 사용하여 타입 안전한 쿼리를 작성합니다. QueryDSL은 Fluent API를 제공하여 쿼리를 체인 형태로 작성할 수 있도록 합니다. 예를 들어, 테이블과 조인, 필터링, 정렬 등의 쿼리 조건을 메서드 체인으로 표현할 수 있습니다.

2-3. 쿼리 실행:

작성된 쿼리를 실행하기 위해 JPAQueryFactory를 생성하고, 쿼리 타입과 연결하여 쿼리를 실행합니다. JPAQueryFactory는 EntityManager와 연동하여 쿼리를 수행하고 결과를 반환합니다.

2-4. 결과 처리:

실행된 쿼리의 결과는 엔티티 객체 또는 DTO 객체로 반환됩니다. QueryDSL은 결과를 컬렉션 형태로 반환하며, fetch(), fetchOne(), fetchFirst() 등 다양한 반환 메서드를 제공합니다.

이러한 원리를 통해 QueryDSL은 자바 코드로 쉽게 SQL 쿼리를 작성하고 실행할 수 있으며, 타입 안정성과 가독성을 높일 수 있습니다. 또한, QueryDSL은 동적 쿼리 작성과 쿼리 조합을 지원하여 유연한 쿼리 생성이 가능하도록 합니다.

3. Querydsl 라이브러리 살펴보기

querydsl-apt: Querydsl 관련 코드 생성 기능 제공
querydsl-jpa: querydsl 라이브러리

4. JPQL vs QueryDSL

Querydsl은 JPQL 빌더
JPQL: 문자(실행 시점 오류), Querydsl: 코드(컴파일 시점 오류) JPQL: 파라미터 바인딩 직접, Querydsl: 파라미터 바인딩 자동 처리

5. JPAQueryFactory를 필드로

package study.querydsl;
  import com.querydsl.jpa.impl.JPAQueryFactory;
  import org.assertj.core.api.Assertions;
  import org.junit.jupiter.api.BeforeEach;
  import org.junit.jupiter.api.Test;
  import org.springframework.boot.test.context.SpringBootTest;
  import org.springframework.transaction.annotation.Transactional;
  import study.querydsl.entity.Member;
  import study.querydsl.entity.QMember;
  import study.querydsl.entity.Team;
  import javax.persistence.EntityManager;
  import javax.persistence.PersistenceContext;
  import java.util.List;
  import static org.assertj.core.api.Assertions.*;
  import static study.querydsl.entity.QMember.*;
  @SpringBootTest
  @Transactional
  public class QuerydslBasicTest {
      @PersistenceContext
      EntityManager em;
JPAQueryFactory queryFactory;
 @BeforeEach
      public void before() {
          queryFactory = new JPAQueryFactory(em);
//...
}
      @Test
      public void startQuerydsl2() {
//member1을 찾아라.
QMember m = new QMember("m");
          Member findMember = queryFactory
                  .select(m)
                  .from(m)
                  .where(m.username.eq("member1"))
                  .fetchOne();
          assertThat(findMember.getUsername()).isEqualTo("member1");
      }
}

JPAQueryFactory를 필드로 제공하면 동시성 문제는 어떻게 될까? 동시성 문제는 JPAQueryFactory를 생성할 때 제공하는 EntityManager(em)에 달려있다. 스프링 프레임워크는 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도, 트랜잭션 마다 별도의 영속성 컨텍스트를 제공하기 때문에, 동시성 문제는 걱 정하지 않아도 된다. 즉 @PersistenceContext 으로 주입 받은 EntityManager 를 proxy로 감싼 후 , 호출 할때마다 proxy를 통해 entityManager가 생성하여 Thread-Safe를 보장한다.

6. 기본문법

6-1. 기본 Q-Type 활용

Q클래스 인스턴스를 사용하는 2가지 방법

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

⭐️ 같은 테이블을 조인해야 하는 경우가 아니면 기본 인스턴스를 사용하자

  • 기본 인스턴스를 static import와 함께 사용

6-1-1. 예제

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

다음 설정을 추가하면 실행되는 JPQL을 볼 수 있다.

spring.jpa.properties.hibernate.use_sql_comments: true

주석처리되어 콘솔창에 출력된다.

6-2. 검색 조건 쿼리

기본 검색 쿼리

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

검색조건은 .and(),.or()를메서드체인으로연결할수있다.

참고: select , from 을 selectFrom 으로 합칠 수 있음

JPQL이 제공하는 모든 검색 조건 제공

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

AND 조건을 파라미터로 처리

  @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);
}                   
  • where() 에 파라미터로 검색조건을 추가하면 AND 조건이 추가됨
  • 위와 같이 ',' 으로 and 조건을 추가한 경우 null 값은 무시 ,메서드추출을활용해서동적쿼리를깔끔하게만들수있음

결과 조회

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

참고
김영한 QueryDSL 강의 자료
[Spring] QueryDsl gradle 설정 (Spring boot 3.x , 2.x )

@PersistenceContext 사용 이유

profile
개발자꿈나무

0개의 댓글