쿼리를 문자 아닌 코드로 작성하고, 쉽고 간결하며 그 모양도 쿼리와 비슷하게 개발할 수 있는 프로젝트
<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: QuerDSL JPA 라이브러리
querydsl-apt: 쿼리 타입을 생성할 때 필요한 라이브러리
in pom.xml
<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.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
메이븐 컴파일하면 target/generated-sources/java/ 폴더에 메타모델(Q) 클래스 파일들이 생성된다.
EntityManager em = emf.createEntityManager();
// 쿼리타입 생성 생성자의 파라미터는 별칭이다
JPAQuery query = new JPAQuery(em);
List<Member> members = query.from(member)
.where(member.name.eq("회원1"))
.orderBy(member.name.desc())
.list(member);
쿼리타입 (Q)은 사용하기 편리하도록 메타 모델 클래스 안에 스태틱 인스턴스를 보관하고 있다.
public class QMember extends EntityPathBase<Member> {
public static final QMember member = new QMember("member1");
따라서
QMember qMember= new jpabook.queryDSL.QMember("m");// 직접 별칭 생성
QMember qMember1 = jpabook.queryDSL.QMember.member; // 기본 인스턴스 사용
두개 중 하나를 이용해 쿼리타입으로 사용하면 된다.
QItem item = QItem.item;
List<Item> items = query.from(item)
.where(item.name.eq("상품A").and(item.price.gt(20000)).list(item);
->
select item from Item item where item.name =?1 and item.price > ?2
쿼리타입 필드는 필요한 대부분의 메소드를 명시적으로 제공한다.
item.price.between(100,150);
item.name.contains("상품A"); // like '%상품A%'
item.name.startsWith("고급"); // lite '고급%'
쿼리 작성 후 결과 조회 메서드 호출 시 실제 데베를 조회한다.
1. uniqueResult() : 조회 결과가 한 건 일때 사용하고, 없으면 null 을 반환, 한 건 이상이면 예외발생
2. singleResult(): uniqueResult와 동일하지만 하나 이상의 데이터가 검출되면 첫번째 데이터를 반환한다.
3. list() : 결과가 하나 이상일때 사용하며, 없을시 빈 배열을 반환한다.
QItem item = QItem.item;
query.from(item)
.orderBy(item.price.desc(), item.stockQuantity.asc())
.offset(10).limit(20)
.list(item)
페이징은 restrict() 메서드에 QueryModifiers 를 파라미터로 사용할 수 있다.
QueryModifiers qm = new QueryModifier(20L,10L); // limit, offset
List<Item> items = query.from(item).restrict(qm).list(list);
실제 페이징을 하려면 전체 데이터 수를 알아야한다. 이때는 list()대신 listResults() 를 사용한다.
listResults() 카운트 쿼리를 한번 더 사용한다.
SearchResults<Item> results = query.from(item)
.where(item.price.gt(1000))
.offset(10).limit(20)
.listResults(item);
long total = results.getTotal();
long limit = results.getLimit();
long offset = results.getOffset();
List<Item> items =results.getResults();
groupBy() 를 사용하고 그룹된 결과를 제한하려면 having()을 사용한다.
query.from(item)
.groupBy(item.price)
.having(item.price.gt(10000))
.list(item);
조인은 innerJoin(join), 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, ordreItem)
.list(order);
// 조인 on 사용
query.from(order)
.leftJoin(order.orderItems, orderItem)
.on(orderItem.count.gt(2))
.list(order);
//fetch join
query.from(order)
.innerJoin(order.member, member).fetch()
.leftJoin(order.orderItems,orderItem).fetch()
.list(order)
//from 절에 여러 조건 사용
QOrder order = QOrder.order;
QMember member =QMember.member;
query.from(order,member)
.where(order.member.eq(member))
.list(order);
서브쿼리의 결과가 하나면 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);
QItem item = QItem.item;
List<String> result = query.from(item).list(item.name);
프로젝션 대상으로 여러 필드를 선택하면 QueryDSL 은 ㄱ본적으로 Tuple이라는 Map 과 비슷한 내부 타입을 사용한다.
결과는 Tuple.get() 메소드에 조회한 쿼리 타입을 지정하면 됨.
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) {
soutv("name =" + tuple.get(item.name));
soutv("price = " + tuple.get(item.price));
}
쿼리 결과가 엔티티가 아닌 특정 객체로 받고 싶으면 빈 생성 기능을 사용함.
QueryDSL 의 객체를 생성하는 다양한 방법 제공
1. 프로퍼티 접근
2. 필드 직접 접근
3. 생상저 접근
public class ItemTDO{
String username;
int price;
// 기본생성자
// 모든 필드 생성자
// 개터 셋터
}
QItem item = QItem.item;
List<ItemDTO> result = query.from(item).list(
Projections.bean(ItemDTO.class, item.name.as("username"), item.price));
QItem item = QItem.item;
List<ItemDTO> result = query.from(item).list(
Projections.fields(ItemDTO.class, item.name.as("username"), item.price));
QItem item = QItem.item;
List<ItemDTO> result = query.from(item).list(
Projections.constructor(ItemDTO.class, item.name, item.price));
quert.distinct.from(item)...
QueryDSL 도 수정, 삭제 같은 배치 쿼리를 사용한다.
JPQL 배치 쿼리와 같이 영속성 컨텍스트를 무시하고 데베에 직접 쿼리를 날린다.
// 수정,
QItem item= QItem.item;
JPAUpdateClause updateClause = new JPAUpdateClause(em,item);
long count = updateClause.where(item.name.eq("책A"))
.set(item.price, item.price.add(100))
.excute();
책A의 가격을 100원 더한다.
// 삭제,
QItem item= QItem.item;
JPADeleteClause deleteClause = new JPADeleteClause(em,itme);
long count = deleteClause.where(item.name.eq("책A")).excute();
SearchParam param = new SearchParam();
param.setName("bookA");
param.setPrice(1000);
QItem item = QItem.item;
BooleanBuilder buildere = 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)
.lite(list);
쿼리 타입에 검색 조건을 직접 정의할 수 있다.
// 검색 조건 정의
import com.mysema.query.annotations.QueryDelegate;
import com.mysema.query.types.expr.BooleanExpression;
public class ItemExpression {
@QueryDelegate(Item.class) // 엔티티가 아니라 자바 기장 내장 타입도 가능하다.
public static BooleanExpression isExpensive(QItem item , Integer price) {
return item.price.gt(price);
}
}
클래스 내에 정적 메소드를 만들고 @QueryDelegate 애노테이션에 속성으로 이 기능을 적용할 엔티티를 지정한다.
정적 메서드의 첫 번째 파라미터에는 대상 엔티티의 쿼리타입을 지정하고 나머지는 필요한 파라미터를 정의함. 후 메이븐을 컴파일하면 아래의 메서드가 쿼라타입에 추가된다
public class QItem extends EntityPathBase<Item> {
...
public com.mysema.query.types.expr.BooleanExpression isExpensive(Integer price) {
return ItemExpression.isExpensive(this, price);
}
이후 쿼리타입에 정의한 기능이 추가된 것을 확인 할 수 있고 , 사용할 수 있다.
query.from(item).where(item.isExpensive(30000)).list(item);