기본 문법과 쿼리 API
파라미터 바인딩
프로젝션
SELECT 절에 조회할 대상을 지정하는 것
엔티티 프로젝션
임베디드 프로젝션
여러 값 조회
NEW 명령어
TypedQuery<UserDTO> query = em.createQuery("SELECT new UserDTO(m.username, m.age)
FROM Member m", UserDTO.class);
//SELECT 다음 NEW 명령어 사용 시 반환받을 클래스를 지정 할 수 있어서
// 이 클래스의 생성자에 JPQL 조회 결과를 넘겨 줄 수 있다.
페이징 API
집합
정렬(ORDER BY)
JPQL 조인
내부 조인
String query = "SELECT m FROM Member m JOIN m.team t WHERE t.name = :teamName";
//JPQL 조인을 SQL 조인 처럼 사용 시 문법 오류 발생
//ex) FROM Member m JOIN Team t -> 문법 오류
외부 조인
SELECT m ,t
FROM Member m LEFT JOIN m.team t on t.name = 'A'
컬렉션 조인
페치 조인
경로 표현식
서브 쿼리
...
동적 쿼리
정적 쿼리
JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API !.. (어려워서 QueryDSL 에 밀리고 있는 상태..)
맛만 봐보자!
Criteria API 는 javax.persistence.criteria 패키지에 존재
//JPQL : select m from Member m
CriteriaBuilder cb = em.getCriteriaBuilder(); // Criteria 쿼리 빌더
//Criteria 생성, 반환 타입 지정
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class); //FROM 절
cq.select(m); //SELECT 절
TypedQuery<Member> query = em.createQuery(cq);
List<Member> members = query.getResultList();
위 예제 처럼 Criteria 문법만 보고서 어떠한 JPQL이 나올지 예측하기가 QueryDSL 에 비해 까다롭고 직관적이지 않다..! 나같아도 Criteria 보단 QueryDSL 을 선호..할 수 밖에 없을 거같다.
Criteria 의 가장 큰 단점인 너무 복잡하고 어렵다는 걸 대체 할 수 있도록 쿼리를 문자가 아닌 코드로 작성해도 쉽고 간결하며 그 모양도 쿼리와 비슷하게 개발 할 수 있는 프로젝트
시작
public void queryDSL() {
EntityManager em = emf.createEntityManager();
JPAQuery query = new JPAQuery(em);
QMember qMember = new QMember("m"); //생성 되는 JPQL 의 별칭이 m 이다.
List<Member> members =
query.from(qMember)
.where(qMember.name.eq("회원1"))
.orderBy(qMember.name.desc())
.list(qMember);
}
기본 Q 생성
쿼리 타입은 아래와 같이 기본 인스턴스를 보관 하고 있음, 하지만 같은 엔티티를 조인 하거나 같은 엔티티를 서브쿼리에 사용하면 같은 별칭이 사용되므로 이 때는 별칭을 직접 지정하여 사용 해야 함
public class QMember extends EntityPathBase<Member> {
public static final QMember member = new QMember("member1");
...
}
//쿼리 타입 사용
QMember qMember = new QMember("m"); //직접 지정
QMember qMember = QMember.member; // 기본 인스턴스 사용
검색 조건 쿼리
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); // 조회 할 프로젝션 지정
------
select item
from Item item
where item.name = ?1 and item.price > ?2 //위치 기준 파라미터 바인딩
QueryDSL 의 where 절에는 and 나 or 사용 가능
.where(item.name.eq("좋은상품") , item.price.gt(20000)) // 이때는 and 연산이 됨
item.price.between(10000,20000); // 10000~20000 사이
item.name.contains("상품1"); // 상품1 이라는 이름을 포함한 상품
item.name.startswith("고급"); // 이름이 고급으로 시작 하는 상품
결과 조회
페이징 & 정렬
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);
---
QueryModifiers queryModifiers = new QueryModifiers(20L, 10L); //limit, offset
List<Item> list =
query.from(item)
.restrict(queryModifiers)
.list(item);
---
//실제 페이징 처리 시 검색된 전체 데이터 수를 알아야 한다. 이 때 listResults() 사용
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(); // 조회된 데이터
//listResults() 사용 시 전체 데이터 조회를 위한 count 쿼리를 한번 더 실행 한다.
// 그리고 SearchResults 를 반환하는데 이 객체에서 전체 데이터 수를 조회할 수 있음
그룹
query.from(item)
.groupBy(item.price)
.having(item.price.gt(10000)) //조건
.list(item);
조인
innerJoin, leftJoin, rightJoin, fullJoin 사용 및 JPQL 의 성능최적화를 위한 fetch 조인도 사용할 수 있다.
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.count.gt(2))
.list(order);
---
//fetch 조인
query.from(order)
.innerJoin(order.member, member).fetch()
.leftJoin(order.orderItems, orderItem).fetch()
.list(order);
---
//from 절에 여러 조건 사용
qQOrder 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.in(
new JPASubQuery().from(itemSub)
.where(item.name.eq(itemSub.name))
.list(itemSub)
))
.list(item);
프로젝션 결과 반환
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);
for(Tuple tuple : result) {
System.out.println("name = " + tuple.get(item.name));
System.out.println("price = " + tuple.get(item.price));
}
빈 생성 (com.mysema.query.types.Projections 사용)
특정 객체(ex. DTO) 로 받고 싶을 때 사용
프로퍼티 접근
필드 직접 접근
생성자 사용
QItem item = QItem.item;
List<ItemDTO> result = query.from(item).list(
projections.bean(ItemDTO.class, item.name.as("username"), item.price); //Setter 사용
query.from(item).list(
projections.fileds(ItemDTO.class, item.name.as("username"), item.price); //필드 직접 접근
query.from(item).list(
projections.constructor(ItemDTO.class, item.name, item.price) // 생성자 사용
----
DISTINCT
-> query.distinct().from(item)... 으로 사용
수정,삭제,배치 쿼리
JPQL 배치 처럼 영속성 컨텍스트를 무시하고! 데이터베이스를 직접 쿼리한다.
//수정 배치 쿼리
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();
//수정 시 JPAUpdateClause 사용
//삭제 시 JPADeleteClause 사용
동적 쿼리 : com.mysema.query.BooleanBuilder 사용 시 특정 조건에 따른 동적 쿼리 생성
메소드 위임 : @QueryDelegate(쿼리타입,필요한 파라미터..) 사용 시 쿼리 타입에 검색 조건을 직접 정의 가능
public class ItemExpression {
@QueryDelegate(Item.class)
public static BooleanExpression isExpensive(QItem item, Integer price) {
return item.price.gt(price);
}
}