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 사용방법
- 우선 com.mysema.query.jpa.impl.JPAQuery 객체 생성.
- EntityManager를 인자로 넘겨준다.
- 사용할 쿼리 타입(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 조인도 사용할 수 있다.
기본 문법
- 첫 번째 파라미터에 조인 대상을 지정.
- 두 번째 파라미터에 별칭으로 사용할 쿼리 타입을 지정.
기본적인 조인
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);