10.4 QueryDSL

  • Criteria의 가장 큰 단점은 너무 코드가 복잡하고 어렵다.
  • 어떤 JPQL이 생성될지 파악하기 쉽지 않다.

특징

  • 쉽고 간결하고 모양도 쿼리와 비슷하게 개발할 수 있는 프로젝트
  • JPA, JDO, JDBC, Lucene, Hibernate, Search, 몽고DB, 자바컬렉션 등 지원
  • 데이타를 조회하는데 특화

공식 가이드
http://www.querydsl.com/static/querydsl/3.6.3/reference/ko-KR/html_single/

10.4.1 QueryDSL 설정

필요 라이브러리

// pom.xml

<!-- QueryDSL -->
<dependency>
    <groupId>com.mysema.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>3.6.3</version>
</dependency>

<dependency>
    <groupId>com.mysema.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>3.6.3</version>
</dependency>
파일명 설명
querydsl-jpa QueryDSL JPA 라이브러리
querydsl-apt 쿼리 타입(Q)를 생성할 때 필요한 라이브러리

환경설정

QueryDSL을 사용하려면 Criteria 메타 모델처럼 엔티티를 기반으로 쿼리 타입이라는 쿼리용 클래스를 생성해야 한다.

// pom.xml - plugin
<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.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
        </execution>
    </executions>
</plugin>

쿼리타입 클래스 생성

실행하면 Q로 시작하는 쿼리 타입들이 생성된다.

mvn compile

결과

10.4.2 시작

public static void queryDSL(EntityManager entityManager) {

    JPAQuery query = new JPAQuery(entityManager);
    QMember qMember = new QMember("m");     // 생성되는 JPQL의 별칭이 m
    List<Member> members = query
            .from(qMember)
            .where(qMember.username.eq("철수"))
            .orderBy(qMember.username.desc())
            .list(qMember);

    for (Member member : members) {
        System.out.println("Member : " + member.getMemberId() + ", " + member.getUsername());
    }
}

QueryDSL 사용방법

  1. 우선 com.mysema.query.jpa.impl.JPAQuery 객체 생성.
  2. EntityManager를 인자로 넘겨준다.
  3. 사용할 쿼리 타입(Q)를 생성하는데 생성자에는 별칭을 준다.

JPQL과 비교

select m from Member m
where m.name = ?1
order by m.name desc

List<Member> members = query
.from(qMember)
.where(qMember.username.eq("철수"))
.orderBy(qMember.username.desc())
.list(qMember);

기본 Q 생성

쿼리 타입(Q)은 사용하기 편리하도록 기본 인스턴스를 보관하고 있다.
엔티티를 조인하거나 같은 엔티티를 서브쿼리로 사요하면 별칭을 직접 지정해서 사용.

쿼리 타입 사용

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

10.4.3 검색 조건 쿼리

QueryDSL의 기본 쿼리 기능

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

실행된 JPQL

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

QueryDSL의 where절에는 and 나 or를 사용할 수 있다.
여러 검색 조건을 사용해도 된다. and 연산 적용.

.where(item.name.eq("좋은상품"), item.price.gt(20000))

기본 조건절

item.price.between(1000, 2000);     // 가격이 10000원~20000원 상품

item.name.contains("상품1");        // 상품1 이름을 포함하는 상품

item.name.startsWith("고급");       // like 검색

IDE 자동완성 지원이 됨으로 쉽게 원하는 메소드를 찾을 수 있음.

10.4.4 결과 조회

보통 uniqueResult()나 list() 사용.
파라미터로 프로젝션 대상을 넘겨준다.

메소드 설명
uniqueResult() 조회 결과가 한 건 일때 사용, 없으면 null, 하나 이상은 예외
singleResult() uniqueResult()와 같지만 하나 이상이면 첫번째 반환
list() 결과가 하나 이상일 경우, 없으면 빈 컬렉션

10.4.5 페이징과 정렬

 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)
기능 메소드
정렬 orderBy 사용, asc(), desc().
페이징 offset, limit

페이징과 QueryModifiers 사용

QueryModifiers queryModifiers = new QueryModifiers(20L, 0L);
        List<Member> members =
                query.from(qMember)
                        .restrict(queryModifiers)
                        .list(qMember);

실제 페이징 처리

전체 데이터 수를 알아야 한다. list() 대신 listResults() 사용

QueryModifiers queryModifiers = new QueryModifiers(20L, 0L);
SearchResults<Member> result = query.from(qMember)
        .restrict(queryModifiers)
        .listResults(qMember);

long total = result.getTotal();     // 검색된 전체 데이터 수
long limit = result.getLimit();
long offset = result.getOffset();
List<Member> members = result.getResults();

System.out.println("total : " + total);
System.out.println("limit : " + limit);
System.out.println("offset : " + offset);

for (Member member : members) {
    System.out.println("Member : " + member.getMemberId() + ", " + member.getUsername());
}

listResults()를 사용하면 전체 데이터 조회를 위한 count 쿼리를 한번 더 실행.

10.4.7 조인

  • innerJoin(join), leftJoin, rightJoin, fullJoin을 사용할 수 있다.
  • JPQL의 on 사용할 수 있다.
  • 성능 최적화를 위한 fetch 조인도 사용할 수 있다.

기본 문법

  1. 첫 번째 파라미터에 조인 대상을 지정.
  2. 두 번째 파라미터에 별칭으로 사용할 쿼리 타입을 지정.

기본적인 조인

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

페치 조인 사용

query.from(order)
    .innerJoin(order.member, member).fetch()
    .leftJoin(order.orderItem, orderItem).fetch()
    .list(order)

세타 조인

QOrder order = QOrder.order;
QMember member = QMember.member;

query.from(order, member)
    .where(order.member.eq(member))
    .list(order)

서브쿼리

com.mysema.query.jpa.JPASubQuery를 생성해서 사용.
결과가 하나면 unique, 여러 건이면 list()를 사용

결과가 단 건

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

결과가 여러 건

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

10.4.9 프로젝션과 결과 반환

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

프로젝션 대상이 하나

해당 타입으로 반환

QItem item = QItem.item;
List<String> result = query.from(item).list(item.name);

for (String name : result) {
    System.out.println("name = " + name);
}

여러 컬럼 반환과 튜플

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("name = " + tuple.get(item.name));
    System.out.println("price = " + tuple.get(item.price));
}

10.4.10 수정, 삭제 배치 쿼리 지원

단, JPQL 배치 쿼리와 같이 영속성 컨텍스트 무시하고 데이타베이스에 직접 쿼리

JPAUpdateClause, JPADeleteClause

10.4.11 동적 쿼리

com.mysema.query.BooleanBuilder 사용하여 동적 쿼리 생성.

SearchParam param = new SearchParam();
param.setName("시골개발자");
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);

results matching ""

    No results matching ""