QueryDSL 개념 및 활용

WOOK JONG KIM·2022년 10월 31일
0
post-thumbnail
post-custom-banner

QueryDSL 이란?

정적 타입을 이용해 SQL과 같은 쿼리를 생성할 수 있게 지원하는 프레임워크

문자열이나 XML파일을 통해 쿼리를 작성하는 대신 QueryDSL이 제공하는 Fluent API를 활용해 쿼리 작성 가능

장점

  • IDE에서 제공하는 코드 자동완성 기능 사용 가능
  • 문법적으로 잘못된 쿼리 허용 X
  • 고정된 SQL쿼리를 작성하지 않기에 동적으로 쿼리 생성 가능
  • 코드를 작성하므로 가독성 + 생산성 향상
  • 도메인타입과 프로퍼티를 안전하게 참조 가능

QueryDSL을 사용하기 위한 프로젝트 설정

의존성 추가

<dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <scope>provided</scope>
</dependency>
<dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
</dependency>

< plugins > 태그에 APT 플러그인 추가

<plugin>
  <groupId>com.mysema.maven</groupId>
  <artifactId>apt-maven-plugin</artifactId>
      <version>1.1.3</version>
       <executions>
         <execution>
           <goals>
               <goal>process</goal>
           </goals>
            <configuration>
                 <outputDirectory>target/generated-sources/java</outputDirectory>
                 <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                  <options>
                      <querydsl.entityAccessors>true</querydsl.entityAccessors>
                  </options>
              </configuration>
           </execution>
        </executions>
</plugin>

JPAAnnotationProcessor은 @Entity 어노테이션으로 정의된 엔티티 클래스를 찾아서 쿼리 타입 생성

이후 maven lifecycle -> compile 단계 클릭해 빌드 수행

그러면 Q 도메인 클래스가 생성된 것을 볼 수 있다

QueryDSL은 지금까지 작성했었던 엔티티 클래스QDomain이라는 쿼리 타입의 클래스를 자체적으로 생성해서 메타데이터로 사용
-> 이를 통해 SQL과 같은 쿼리를 생성하여 제공

generated-sources를 Mark as Sources로 하여 IDE에서 소스 파일로 인식할 수 있게 해주어야 함


기본적인 QueryDSL 사용

	@PersistenceContext
    EntityManager entityManager;

    @Test
    void queryDslTest(){
        JPAQuery<Product> query = new JPAQuery(entityManager);
        QProduct qProduct = QProduct.product;

        List<Product> productList = query
                .from(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();

        for(Product product : productList){
            System.out.println("-----------------");
            System.out.println();
            System.out.println("Product Number : " + product.getNumber());
            System.out.println("Product Name : " + product.getName());
            System.out.println("Product Price :" + product.getPrice());
            System.out.println("Product Stock :" + product.getStock());
            System.out.println();
            System.out.println("-------------------");
        }
    }

위 코드는 QueryDSL에 의해 생성된 Q도메인 클래스를 활용하는 코드

Q도메인 클래스와 대응되는 테스트 클래스가 없으므로 엔티티 클래스에 대응되는 리포지토리의 테스트 클래스에 포함해도 무관

QueryDSL을 사용하기 위해선 JPAQuery 객체를 사용
-> EntityManager를 활용하여 생성

생성된 JPAQuery는 9~13번 줄 같이 빌더 형식으로 쿼리 작성

List로 반환 받기 위해서는 fetch() 메서드를 사용해야 함

반환 메서드 종류

  • List< T > fetch() : 조회 결과를 리스트로 반환
  • T fetchOne : 단 한건의 조회 결과 반환
  • T fetchFirst() : 여러 건의 조회 결과중 1건을 반환
  • Long fetchCount() : 조회 결과의 개수를 반환
  • QueryResult< T > fetchResults() : 조회 결과 리스트와 개수를 포함한 QueryResults 반환

QueryDSL을 사용하기 위해 JPAQueryFactory를 사용하는 경우도 있음

JPAQueryFactory를 활용한 QueryDSL 테스트 코드

@Test
void queryDslTest2(){
    JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
     QProduct qProduct = QProduct.product;
        
      List<Product> productList = jpaQueryFactory.selectFrom(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();
        
     for(Product product : productList){
            System.out.println("-----------------");
            System.out.println();
            System.out.println("Product Number : " + product.getNumber());
            System.out.println("Product Name : " + product.getName());
            System.out.println("Product Price :" + product.getPrice());
            System.out.println("Product Stock :" + product.getStock());
            System.out.println();
            System.out.println("-------------------");
        }
    }

JPAQuery를 사용했을 때와 달리 JPAQueryFactory에서는 select 절부터 가능

일부만 조회하고 싶다면 selectFrom()이 아닌 select()와 From() 메서드 구분해서 사용하자

@Test
    void queryDslTest3(){
        JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
        QProduct qProduct = QProduct.product;

        List<String> productList = jpaQueryFactory
                .select(qProduct.name)
                .from(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();
        
        for(String product : productList){
            System.out.println("-------------------");
            System.out.println("Product Name : " + product);
            System.out.println("------------------");
        }
        
        List<Tuple> tupleList = jpaQueryFactory
                .select(qProduct.name, qProduct.price)
                .from(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();
        
        for(Tuple product : tupleList){
            System.out.println("----------");
            System.out.println("Product Name :" + product.get(qProduct.name));
            System.out.println("Product Name :" + product.get(qProduct.price));
            System.out.println("----------");
        }
    }

productList는 select 대상이 하나인 경우

TupleList처럼 select 대상이 여러개인 경우 쉼표로 구분해서 작성하면 되고, 리턴 타입을 List< tuple > 로 작성


실제 비즈니스 로직에 QueryDSL 활용

컨피그 클래스 생성

QueryDSLConfig.java

@Configuration
public class QueryDSLConfiguration {
    
    @PersistenceContext
    EntityManager entityManager;
    
    @Bean
    public JPAQueryFactory jpaQueryFactory(){
        return new JPAQueryFactory(entityManager);
    }
}

위와 같이 JPAQueryFactory 객체를 @Bean 객체로 등록시 앞선 방법처럼 JPAQueryFactory를 초기화 하지 않고 스프링 컨테이너에서 가져다 쓸 수 있음

ProductRepositoryTest

	@Autowired
    JPAQueryFactory jpaQueryFactory;
    
    @Test
    void queryDslTest4(){
        QProduct qProduct = QProduct.product;
        
        List<String> productList = jpaQueryFactory
                .select(qProduct.name)
                .from(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();
        
        for(String product : productList){
            System.out.println("-----------------");
            System.out.println("Product Name : " + product);
            System.out.println("-----------------");
        }
    }
Hibernate: 
    select
        product0_.name as col_0_0_ 
    from
        product product0_ 
    where
        product0_.name=? 
    order by
        product0_.price asc
-----------------
Product Name : 펜
-----------------
-----------------
Product Name : 펜
-----------------
-----------------
Product Name : 펜
-----------------
profile
Journey for Backend Developer
post-custom-banner

0개의 댓글