QueryDSL은 쿼리를 문자가 아닌 코드로 작성하여 쉽고 간결하며 그 모양도 쿼리와 비슷하게 개발할 수 있는 프로젝트이다. 코드로 JPQL을 작성하므로 문법 오류를 컴파일 단계에서 잡을 수 있고 IDE 자동완성 기능의 도움을 받을 수 있다
2. 시작
- 라이브러리 추가와 쿼리용 클래스 생성
com.mysema.query.jpa.impl.JPAQuery
객체를 생성해야 하며, 사용할 쿼리 타입(Q)을 생성할 때 생성자에는 별칭을 준다.
기본 Q 생성
- 쿼리 타입(Q)은 기본 인스턴스를 보관하고 있다.
- 같은 엔티티를 조인하거나 같은 엔티티를 서브쿼리에 사용하면 같은 별칭이 사용되므로 이때는 별칭을 직접 지정해서 사용해야 한다.
QMember qMember = new QMember("m"); // 별칭 직접 지정 QMember qMember = QMember.member; // 기본 인스턴스 사용
public void queryDSL() {
EntityManager em = emf.createEntityManager();
JPAQuery query = new JPAQuery(em);
QMember qMember = new QMember("m");
List<Member> members =
query.from(qMember)
.where(qMember.name.eq("회원1"))
.orderBy(qMember.name.desc())
.list(qMember);
}
3. 검색 조건 쿼리
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 itme,name =?1 and item.price>?2
- QueryDsl의 where 절에는 and 나 or을 사용할 수 있고, 여러 검색 조건을 사용해도 된다,
- 쿼리 타입의 필드는 필요한 대부분의 메소드를 명시적으로 제공한다.
- ex )
.between()
.contains()
.startsWith()
- ex )
4. 결과 조회
쿼리 작성이 끝나고 결과 조회 메소드를 호출하면 실제 데이터베이스를 조회한다.
uniqueResult()
: 조회 결과가 한 건일 때 사용한다. 조회 결과가 없으면 null / 하나 이상이면 예외가 발생singleResult()
: 조회 결과가 하나 이상이면 처음 데이터를 반환한다.list()
: 결과가 여러 개일 때 모두 반환하며, 결과가 없으면 빈 컬렉션을 반환한다.
5. 페이징과 정렬
- 정렬은 orderby를 사용하는데 쿼리 타입이 제공하는
asc()
desc()
를 사용한다. - 페이징은 offset과 limit를 적절히 조합해서 사용한다.
query.from(item)
.where(item.price.gt(20000))
.orderBy(item.price.desc(), item.stockQuantity.asc())
.offset(10).limit(20)
.list(item);
- 실제 페이징 처리를 하려면 검색된 전체 데이터 수를 알아야 해서 list 대신
listReults()
를 사용한다.
6. 그룹
- groupBy와 having 사용
query.from(item)
.groupBy(ite.price)
.having(item.price.gt(1000))
.list(item);
7. 조인
- 내부조인, 외부조인, 페치조인 사용 가능
join(조인 대상, 별칭으로 사용할 쿼리 타입)
// 기본 조인
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.cont.gt(2))
.list(order);
// 페치 조인
query.from(order)
.innerJoin(order.member, member).fetch()
.leftJoin(order.orderItems, orderItem).fetch()
.list(order);
// 세타 조인
query.from(order, member)
.where(order.member.eq(member))
.list(order);
8. 서브쿼리
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);
//여러 건
query.from(item)
.where(item.in(
new JPASubQuery().from(itemSub)
.where(item.name.eq(itemSub.name))
.list(itemSub)
))
.list(item);
9. 프로젝션 결과 반환
- select절에 조회 대상을 지정하는 것을 프로젝션이라고 한다.
프로젝션 대상이 하나
- 해당 타입으로 반환
QItem item = QItem.item;
List<String> result = query.from(item).list(item.name);
여러 컬럼 반환과 튜플
- QueryDSL은 Map과 비슷한 Tuple 자료형을 지원한다.
QItem item = QItem.item;
List<Tuple> result = query.from(item).list(item.name, list.price);
// 같은 의미
List<Tuple> result = query.from(item).list(new QTuple(item.name, list.price));
// tuple.get()으로 조회
빈 생성
- 쿼리 결과를 특정 객체로 받고 싶으면 QueryDSL의 빈 생성 기능을 사용할 수 있다.
public class ItemDTO {
private String username;
private int price;
// 생성자
// getter, setter
}
- 프로퍼티 접근 (setter)
Projections.bean()
메소드는 setter를 사용해서 값을 채워 준다.QItem item = QItem.item; List<ItemDTO> result = query.from(item).list( Projections.bean(ItemDTO.class, item.name.as("username"), item.price));
- 필드 직접 접근
Projections.fields()
는 필드에 직접 접근QItem item = QItem.item; List<ItemDTO> result = query.from(item).list( Projections.fields(ItemDTO.class, item.name.as("username"), item.price));
- 생성자 사용
Projections.constructor()
는 생성자를 사용QItem item = QItem.item; List<ItemDTO> result = query.from(item).list( Projections.constructor(ItemDTO.class, item.name.as("username"), item.price));
10. 수정, 삭제 배치 쿼리
- QueryDSL도 JPQL 배치 쿼리와 같이 영속성 컨텍스트를 무시하고 데이터베이스를 직접 쿼리한다.
- 수정 배치 쿼리 :
com.mysema.query.jpa.impl.JPAUpdateClause
- 삭제 배치 쿼리 :
com.mysema.query.jpa.impl.JPADeleteClause
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();
// 삭제 배치 쿼리
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.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);
12. 메소드 위임
- 메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건을 직접 정의할 수 있다.
//검색 조건 정의
public class ItemExpression {
@QueryDelegate(Item.class)
public static BooleanExpression isExpensive(QItem item, Integer price) {
return item.price.gt(price);
}
}
//쿼리 타입에 생성된 결과
public class QItem extends EntityPathBase<Item> {
...
public BooleanExpression isExpensive(Integer price) {
return ItemExpression.isExpensive(this, price);
}
}
'자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 13장 - 웹 애플리케이션과 영속성 관리 (0) | 2023.07.26 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 12장 - 스프링 데이터 JPA (0) | 2023.07.26 |
[자바 ORM 표준 JPA 프로그래밍] 10.2 - JPQL (0) | 2023.07.22 |
[자바 ORM 표준 JPA 프로그래밍] 9장 - 값 타입 (0) | 2023.07.22 |
[자바 ORM 표준 JPA 프로그래밍] 8장 - 프록시와 연관관계 관리 (0) | 2023.06.13 |