ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링] QueryDsl 정리
    스프링 2022. 12. 26. 21:56
    728x90
    김영한님의 자바 ORM 표준 JPA 프로그래밍 - queryDsl 정리 부분입니다

    간단한 특징

    1. 오픈소스 프로젝트다

    2. 쿼리를 문자가 아닌 코드로 작성해도 쉽고 간결하며  그 모양도 쿼리와 비슷하게 개발할 수 있다.

     

    1. 라이브러리 추가 필요

    // 1. pom.xml 추가
    <dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
        <veresion>3.6.3</veresion>
    </dependency>
    
    <dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId> // 쿼리 타입(Q) 생성시 필요한 라이브러리
        <veresion>3.6.3</veresion>
        <scope>provided</scope>
    </dependency>
    
    // 2. 쿼리 타입 생성용 플로그인을 pom.xml에 추가
    <build>
        <plugins>
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <veresion>1.1.3</veresion>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

     

    2. queryDsl 시작하는 방법

    1. com.mysema.query.jpa.impl.JPAQuery 객체를 생성해야 하는데 이때 엔티티 매니저를 생성자에 넘겨준다

    2. 사용할 쿼리 타입(Q)을 생성하는데 생성자에는 별칭을 주면 된다. ( 이 별칭을 JPQL에서 별칭으로 사용 )

     

    1) 기본 Q 생성

    쿼리 타입(Q)는 사용하기 편하기 위해서 기본 인스턴스를 보관한다

    // 예시 ( Import static으로 더 간결하게 표현 가능 )
    import static jpabook.jpashop.domain.QMember.member; // 기본 인스턴스

     

    3. 검색 조건 쿼리

    where 절에는 and나 or 을 사용할 수 있다. 또한 여러 검색 조건을 사용해도 되며, and 연산이 된다

    JPAQuery query = new JPAQuery(em);
    QItem item = QItem.item;
    List<Item> list = query.from(item)
            .where(item.name.eq("좋은 상품").and(item.price.gt(20000)))
            .list(item); // list는 조회할 프로젝션 지정
            
    // 여러 검색 조건 사용 -> and로 이용됨
    .where(item.name.eq("좋은 상품"), item.price.gt(20000))

     

    4. 결과 조회

    쿼리 작성이 끝나고 결과 조회 메소드를 호출하면 실제 디비를 조회한다

    // 1. uniqueResult()
    -> 조회 결과가 한 건일때 사용. 없으면 null, 한건 이상이면 예외 발생
    
    // 2. singleResult()
    -> uniqueResult() 랑 같은데, 한건 이상이면 처음 데이터 반환
    
    // 3. list()
    -> 결과가 하나 이상일때 사용, 결과가 없으면 빈 컬렉션 반환

     

    5. 페이징과 정렬

    정렬은 orderBy 사용 -> asc(), desc() 

    페이징은 offset, limit 사용 -> 대신해서 restrict() 사용 가능

    SearchResults<Item> result = query.from(item) 
                      .where(item.price.gt(10000))
                      .offset(10).limit(20)
                      .limitResults(item);
                      
    long total = result.getTotal(); //검색된 전체 데이터수
    long limit = result.getLimit();
    long offset = result.getOffset();
    List<Item> res = result.getResults(); // 조회된 데이터 ( 전체 데이터 수 체크 가능 )
    
    // restrict() 사용방법
    QueryModifiers queryModifiers = new QueryModifiers(20L, 10L); // limit, offset
    List<Item> list = query.from(item)
                            .restrict(queryModifiers)
                            .list(item);

     

    6. 그룹

    groupBy, having(그룹화된 결과 제한) 사용

    query.from(item)
         .groupBy(item.price)
         .having(item.price.gt(1000))
         .list(item);

     

    7.조인

    innerJoin, leftJoin, rightJoin, fullJoin

    -> on과 성능 최적화를 위한 fetch 조인도 가능하다

    -> 조인의 첫 번째 파라미터에 조인 대상을 두 번째 파라미터에는 별칭으로 사용할 쿼리 타입을 지정하면 된다.

    //기본 조인
    QOrder order = QOrder.order;
    QMember member = QMember.member;
    QOrderItem orderItem = QOrderItem.orderItem;
    
    query.from(order)
         .join(order.member, member)
         .leftJoin(order.orderItems, orderItem)
         .list(order);
         
    // on 사용
    query.from(order)
         .leftJoin(order.orderItems, orderItem)
         .on(orderItem.count.gt(2))
         .list(order);
         
    // fetch 조인 사용
    query.from(order)
         .innerJoin(order.member, member).fetch()
         .leftJoin(order.orderItems, orderItem).fetch()
         .list(order);
         
    // from 절에 여로 조건 사용
    query.from(order, member)
         .where(order.member.eq(member))
         .list(order);

     

    8. 서브 쿼리

    서브 쿼리의 결과가 하나면 unique(), 여러 건이면 list() 을 사용한다

    QItem item = QItem.item;
    QItem itemSub = new QItem("itemSub");
    
    query.from(item)
         .where(item.in(
             new JPASubQuery().from(itemSub)
                 .where(item.name.eq(itemSub.name))
                 .list(itemSub)
         ))
         .list(item);

     

    9. 프로젝션과 결과 반환

    1) 여러 컬럼 반환과 튜플

    프로젝션 대상으로 여러 필드를 선택하면 QueryDSL은 기본으로 com.mysema.query.Tuple이라는 Map과 비슷한 내부 타입을 사용한다. 또한, 조회 결과는 tuple.get() 메소드에 조회한 쿼리 타입을 지정하면 된다.

    QItem item = QItem.item;
    
    // 한개
    List<String> res = query.from(item).list(item.name);
    
    // 여러개
    List<Tuple> res = query.from(item).list(item.name, item.price);

     

    2) 빈 생성

    // 프로퍼티 접근
    QItem Item = QItem.iten;
    List<ItemDTO> res = query.from(item).list(
            Projections.bean(ItemDTO.class, item.name.as("username"), item.price));
    
    // 필드 직접 접근
    QItem Item = QItem.iten;
    List<ItemDTO> res = query.from(item).list(
            Projections.fields(ItemDTO.class, item.name.as("username"), item.price));
    
    // 생성자 접근
    QItem Item = QItem.iten;
    List<ItemDTO> res = query.from(item).list(
            Projections.constructir(ItemDTO.class, item.name, item.price));

     

    3. DISTINCT 사용법

    query.distinct().from(item)...

     

    10. 수정, 삭제 배치 쿼리

    JPQL 배치 쿼리와 같이 영속성 컨텍스트를 무시하고 디비를 직접 쿼리한다는 점에 유의하기

    // 수정
    QItem item = QItem.item;
    JPAUpdateClause updateClause = new JPAUpdateClause(em, item);
    long count = updateClause.where(item.name.eq("jpa책"))
                     .set(item.price, item.price.add(100))
                     .execute();
                     
    // 삭제
    QItem item = QItem.item;
    JPADeleteClause deleteClause = new JPADeleteClause(em, item);
    long count = deleteClause.where(item.name.eq("jpa책"))
                     .execute();

     

    11. 동적 쿼리

    com.mysema.query.BooleanBuilder 사용하면 특정 조건에 따른 동적 쿼리를 생성가능

    SearchParam param = new SearchParam();
    param.setName("시골개발자");
    param.setPrice(10000);
    
    QItem item = QItem.item;
    
    BooleanBuilder builder = new BooleanBuilder();
    
    if(StringUtils.hasNext(param.getName())) {
        builder.and(item.name.contains(param.getName()));
    }
    if(param.getPrice() != null) {
        builder.and(item.price.gt(param.getPrice()));
    }
    List<Item> result = query.from(item)
         .where(builder)
         .list(item);

     

    12. 메소드 위임

    @QueryDelegate 이 기능을 통해서 쿼리 타입에 검색 조건을 직접 정의할 수 있다.

     

     

    13. 스프링 데이터 JPA + QueryDSL 통합하기

    방법 아래와 같이 2가지가 있다

    1. org.springframework.data.querydsl.QueryDslPredicateExecutor

    2. org.springframework.data.querydsl.QueryDslRepositorySupport 

     

    1) QueryDslPredicateExecutor 상속받기

    // 예제
    public interface ItemRepository {
        extends JpaRepository<Item, Long>, QueryDslPredicateExecutor<Item> { ...}
    }
    
    // 위를 사용한 예제
    QItem item = QItem.item;
    Iterable<Item> res = itemRepository.findAll(
        item.name.contains("장난감").and(item.price.between(10000,20000))
    );

    이런식으로 사용할 수 있다

    하지만, join, fetch 를 사용할 수 없다는 한계가 있어서 비추

     

     

    1) QueryDslRepositorySupport  상속받기

    queryDsl 의 모든 기능을 사용하려면, JPAQuery 객체를 직접 생성해서 사용하면 된다

    // 예제 -> 주문 내역 검색 기능
    // 스프링 데이터 JPA가 제공하는 공통 인터페이스는 직접 구현이 힘들기에 사용자 정의 레포지를 만듦
    public interface CustomOrderRepository {
        public List<Order> search(OrderSearch orderSearch);
    }
    
    // queryDslRepositorySupport 상속받는 예제
    public class OrderRepositoryImpl extends QueryDslRepositorySupport
               implements CustomOrderRepository {
        
        public OrderRepositoryImpl() {
            super(Order.class);
        }
        
        @Override
        public List<Order> search(OrderSearch orderSearch) {
            QOrder order = QOrder.order;
            QMember member = QMember.member;
            
            JPQLQuery query = from(order);
            
            if(StringUtils.hasText(orderSearch.getMemberName())) {
                query.leftJoin(order.membr, member) 
                    .where(member.name.contains(orderSearch.getMemberName()));
            }
            
            if(orderSearch.getOrderStatus() != null) {
                query.where(order.status.eq(orderSearch.getOrderStatus())) ;
            }
            
            return query.list(order);
            
        }
               
    }

     

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

    [스프링] 트랜잭션과 락,2차캐리  (0) 2022.12.28
    [스프링] OSIV  (2) 2022.12.28
    [스프링] 스프링 데이터 JPA  (0) 2022.12.28
    [스프링] 객체지향 쿼리 심화  (0) 2022.12.26
    [스프링] JPQL 정리  (0) 2022.12.26
Designed by Tistory.