티스토리 뷰

Books

[JPA] QueryDSL 정리

Aaron 2020. 12. 22. 21:08
반응형


| QueryDSL



- 쿼리언어를 코드로 작성할 수 있도록 해주는 오픈소스 프로젝트

- 데이터 조회 기능이 특화


Documentation ko-KR ver.



|| 설정



ㅇ 라이브러리 추가 및 환경설정 (pom.xml)

- querydsl-jpa : QueryDSL JPA 라이브러리

1
2
3
4
5
<dependency>
  <groupId>com.querydsl</groupId>
  <artifactId>querydsl-jpa</artifactId>
  <version>${querydsl.version}</version>
</dependency>
cs


- querydsl-apt : 쿼리 타입 생성 시 필요한 라이브러리

1
2
3
4
5
6
<dependency>
  <groupId>com.querydsl</groupId>
  <artifactId>querydsl-apt</artifactId>
  <version>${querydsl.version}</version>
  <scope>provided</scope>
</dependency>
cs


1
2
3
4
5
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.6.1</version>
</dependency>
cs


- 엔티티를 기반으로 쿼리 타입이라는 쿼리용 클래스를 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<project>
  <build>
  <plugins>
    ...
    <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>
          </configuration>
        </execution>
      </executions>
    </plugin>
    ...
  </plugins>
  </build>
</project>
cs



|| 시작



QueryDSL을 사용하기 위해 com.mysema.query.jpa.impl.JPAQuery 객체를 생성해야 함


1
2
3
4
5
6
7
8
9
10
11
12
public void queryDSL() {
 
    EntityManager em = emf.createEntityManager();
 
    JPAQuery query = new JPAQuery(em);
    QMember qMember = new QMember("m"); // 생성되는 JPAL의 별칭
    List<Member> members = 
        query.from(qMember)
             .where(qMamber.name.eq("회원1"))
             .orderBy(qMember.name.desc())
             .list(qMember);
}
cs




|| 검색 조건 쿼리



- QueryDSL where 절에는 and 나 or 사용 가능


1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void queryDSL() {
 
    EntityManager em = emf.createEntityManager();
 
    JPAQuery query = new JPAQuery(em);
    QItem item = QItem.item;
    List<Item> list = query.from(item)
                           .where(item.name.eq("좋은상품").and(item.price.gt(20000))
                            // .where(item.name.eq("좋은상품"), item.price.gt(20000))
                            // .where(item.price.between(10000, 20000))
                            // .where(item.name.contains("상품1")
                            // .where(item.name.startWith("고급")
                           .list(item); // 조회할 프로젝션
}
cs


=> 생성 JPQL

1
2
3
select item
from Item item
where item.name = ?1 and item.price > ?2
cs



|| 결과 조회



* uniqueResult() : 조회 결과가 한 건일 때 사용,

 조회 결과가 없으면 null 반환,

     결과가 하나 이상이면 com.querydsl.core.NonUniqueResultException 예외 발생

* singleResult() : uniqueResult()와 같지만 결과가 하나 이상이면 처음 데이터 반환

* list() : 결과가 하나 이상일 때 사용,

    결과가 없으면 빈 컬렉션 반환



|| 페이징과 정렬



1.

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

페이징은 offset과 limit를 조합

1
2
3
4
5
6
7
QItem item = QItem.item;
 
query.from(item)
     .where(item.price.gt(20000))
     .orderBy(item.price.desc(), item.stockQuantity.asc())
     .offset(10).limit(20)
     .list(item);
cs


2.

com.querydsl.core.QueryModifiers 파라미터 사용

1
2
3
4
QueryModifiers queryModifiers = new QueryModifiers(20L, 10L); // Limit, offset
List<Item> list = query.from(item)
                       .restrict(queryModifiers)
                       .list(item);
cs


* 실제 페이징 처리를 위해 검색된 전체 데이터 수를 알아야 함.

  이때는 list() 대신 listResults() 사용

1
2
3
4
5
6
7
8
9
SearchResults<Item> result = query.from(item)
                                  .where(item.price.gt(10000))
                                  .offset(10).limit(20)
                                  .listResults(item);
 
long total = result.getTotal();    // 검색된 전체 데이터 수
long limit = result.getLimit();
long offset = result.getOffset();
List<Item> results = result.getResults();    // 조회된 데이터
cs



|| 그룹



groupBy 와 having 사용

1
2
3
4
query.from(item)
     .groupBy(item.price)
     .having(item.price.gt(1000))
     .list(item);
cs



|| 조인



첫 번째 파라미터에 조인 대상을 지정하고,

두 번째 파라미터에 별칭으로 사용할 쿼리 타입을 지정

join(조인 대상, 별칭으로 사용할 쿼리 타입)


ㅇ 기본 조인

1
2
3
4
5
6
7
8
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);
cs


ㅇ on을 사용한 조인

1
2
3
4
query.from(order)
     .leftJoin(order.orderItems, orderItem)
     .on(orderItem.count.gt(2))
     .list(order);
cs


ㅇ fetch join

1
2
3
4
query.from(order)
     .innerJoin(order.member, member).fetch()
     .leftJoin(order.orderItems, orderItem).fetch()
     .list(order);
cs


ㅇ 세타 조인

1
2
3
4
5
6
QOrder order = QOrder.order;
QMember member = QMember.member;
 
query.from(order, member)
     .where(order.member.eq(member))
     .list(order);
cs



|| 서브 쿼리



JPASubQuery를 생성하여 사용

결과가 하나면 unique(), 여러 건이면 list() 사용

1
2
3
4
5
6
7
8
QItem item = QItem.item;
QItem itemSub = new QItem("itemSub");
 
query.from(item)
    .where(item.price.eq(
        new JPASubQuery().from(itemSub).unique(itemSub.price.max())
    ))
    .list(item)
cs


ㅇ 서브 쿼리가 여러 건일 경우

1
2
3
4
5
6
7
8
9
10
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);
cs



|| 프로젝션과 결과 반환



select 절에 조회 대상을 지정하는 것을 프로젝션


||| 프로젝션 대상이 하나



1
2
3
4
5
6
QItem itme = QItem.item;
List<String> result = query.from(item).list(item.name);
 
for(String name : result) {
    System.out.println(name);
}
cs


||| 여러 컬럼 반환과 튜플



프로젝션 대상으로 여러 필드 선택 시 com.querydsl.core.Tuple 이라는 Map과 비슷한 내부 타입 사용

조회 결과는 tuple.get() 메소드에 조회한 쿼리 타입을 지정

1
2
3
4
5
6
7
8
9
QItem item = QItem.item;
 
List<Tuple> result = query.from(item).list(item.name, item.price);
// List<Tuple> result = query.from(item).list(new QTuple(item.name, item.price));
 
for (Tuple tuple : result) {
    System.out.println(tuple.get(item.name));
    System.out.println(tuple.get(item.price));
}
cs


||| 빈 생성


쿼리 결과를 엔티티가 아닌 특정 객체로 받고 싶을 경우 빈 생성 기능 사용

QueryDSL의 객체를 생성하는 방법

- 프로퍼티 접근

- 필드 직접 접근

- 생성자 사용


com.querydsl.core.types.Projections 사용

쿼리 결과와 매핑할 프로퍼티 이름이 다를 경우 as(별칭) 사용


ㅇ Setter 사용

1
2
3
QItem item = QItem.item;
List<ItemDTO> result = query.from(item).list(
    Projections.bean(ItemDTO.class, item.name.as("username"), item.price));
cs


ㅇ 필드 직접 접근

1
2
3
QItem item = QItem.item;
List<ItemDTO> result = query.from(item).list(
    Projections.fields(ItemDTO.class, item.name.as("username"), item.price));
cs


ㅇ 생성자 사용

1
2
3
4
QItem item = QItem.item;
List<ItemDTO> result = query.from(item).list(
    Projections.constructor(ItemDTO.class, item.name, item.price)
);
cs


||| DISTINCT


1
query.distinct().from(item) ...
cs




|| 수정, 삭제 배치 쿼리



JPQL 배치 쿼리와 같이 영속성 컨텍스트를 무시하고 데이터베이스를 직접 쿼리


ㅇ 수정 배치 쿼리

- 수정된 엔티티의 개수 리턴

1
2
3
4
5
QItem item = QItem.item;
JPAUpdateClause updateClause = new JPAUpdateClause(em, item);
long count = updateClause.where(item.name.eq("test 입니다?"))
                         .set(item.price, item.price.add(100))
                         .excute();
cs


ㅇ 삭제 배치 쿼리

1
2
3
4
QItem item = QItem.item;
JPADeleteClause deleteClause = new JPADeleteClause(em, item);
long count = deleteClause.where(item.name.eq("test 입니다?"))
                         .excute();
cs




|| 동적 쿼리



com.querydsl.core.BooleanBuilder 를 사용하여 특정 조건에 따른 동적 쿼리 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 상품 이름과 가격 유무에 따른 동적 쿼리
 
SearchParam param = new SearchParam();
param.setName("test 입니다?");
param.setPrice(10000);
 
QItem item = QItem.item;
 
BooleanBuilder builder = new BooleanBuilder();
if(StringUtils.hasText(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);
cs



|| 메소드 위임



메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건 정의 가능


ㅇ 검색 조건 정의

1. 정적 메소드 만들기

2. @QueryDelegate Annotaion 속성으로 이 기능을 적용할 엔티티 지정

3. 첫 번째 파라미터에는 대상 엔티티의 쿼리 타입을 지정

1
2
3
4
5
6
7
public class ItemExpression {
    
    @QueryDelegate(Item.class)
    public static BooleanExpression isExpensive(QItem, Integer price) {
        return item.price.gt(price);
    }
}
cs


ㅇ 메소드 위임 기능 사용

1
query.from(item).where(item.isExpensive(30000)).list(item);
cs



+ 필요 시 자바 기본 내장 타입에도 메소드 위임 기능 사용 가능

1
2
3
4
@QueryDelegate(String.class)
public static BooleanExpression isHello(StringPath stringPath) {
    return stringPath.startWith("Hello");
}
cs




출처 : 자바 ORM 표준 JPA 프로그래밍

반응형
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday