3. Criteria
Criteria 기초
- Criteria API는 javax.persistence.criteria 패키지에 있음
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
Predicate usernameEqual = cb.equal(m.get("username"), "회원1");
javax.persistence.criteria.Order ageDesc = cb.desc(m.get("age"));
cq.select(m)
.where(usernameEqual)
.orderBy(ageDesc);
List<Member> resultList = em.createQuery(cq).getResultList();
- 쿼리 루트
- Root< Member > m = cq.from(Member.class); 여기서 m이 쿼리 루트
- 쿼리 루트는 조회의 시작점
- Criteria에서 사용되는 특별한 별칭
- 별칭은 엔티티에만 부여 가능
Criteria 쿼리 생성
- CriteriaBuilder.createQuery() 메소드로 Criteria 쿼리 생성
- Criteria 쿼리 생성 시 파라미터로 쿼리 결과에 대한 반환 타입 지정 가능
- 반환 타입을 지정할 수 없거나 반환 타입이 둘 이상이면 Object(Object[])로 반환 받음
- 튜플로도 반환 받을 수 있음
조회
- 조회 대상을 한 건, 여러 건 지정
- select: 조회 대상 한 건
- multiselect: 조회 대상 여러 건
- DISTINCT
- select, multiselect 다음에 distinct(true) 사용
- NEW, construct()
- cb.construct(클래스 타입, ...)
- 튜플
- 이름 기반이므로 순서 기반의 Object[] 보다 안전
- tuple.getElements() 같은 메소드를 사용해서 현재 튜플의 별칭과 자바 타입 조회 가능
- 튜플 사용할 때는 별칭 필수
집합
정렬
- orderBy
- cb.desc(...), cb.asc(...)
조인
- join()
- JoinType 클래스
- fetch(조인대상, JoinType)
서브 쿼리
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class);
Root<Member> m = mainQuery.from(Member.class);
Subquery<Team> subQuery = mainQuery.subquery(Team.class);
Root<Member> subM = subQeury.correlate(m);
Join<Member, Team> t = sbuM.join("team");
subQuery.select(t)
.where(cb.equal(t.get("name"), "팀A"));
mainQuery.select(m)
.where(cb.exists(subQuery));
List<Member> resultList = em.createQuery(mainQuery).getResultList();
IN 식
CASE 식
- selectCase()
- when()
- otherwise()
파라미터 정의
...
cq.select(m)
.where(cb.equal(m.get("username"), cb.parameter(String.class, "usernameParam")));
List<Member> resultList = em.createQuery(cq)
.setParameter("usernameParam", "회원1")
.getResultList();
네이티브 함수 호출
- cb.function(...)
- 하이버네이트 구현체는 방언에 사용자정의 SQL 함수를 등록해야 호출할 수 있음
동적 쿼리
- 다양한 검색 조건에 따라 실행 시점에 쿼리를 생성하는 것을 동적 쿼리라 함
Integer age = 10;
String username = null;
String teamName = "팀A";
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
Join<Member, Team> t = m.join("team");
List<Predicate> criteria = new ArrayList<Predicate>();
if (age != null) criteria.add(cb.equal(m.<Integer>get("age"),
cb.parameter(Integer.class, "age")));
if (username != null) criteri.add(cb.equal(m.get("username"),
cb.paramter(String.class, "username")));
if (teamName != null) criteria.add(cb.equal(t.get("name"),
cb.paramter(String.class, "teamName")));
cq.where(cb.and(criteria.toArray(new Predicate[0])));
TypedQuery<Member> query = em.createQuery(cq);
if (age != null) query.setParameter("age", age);
if (username != null) query.setParameter("username", username);
if (teamName ! null) query.setParameter("teamName", teamName);
List<Member> resultList = query.getResultList();
Criteria 메타 모델 API
- m.get("age")에서 age는 문자인데 실수로 잘못 적어도 컴파일 시점에 에러 발견 불가
➡️ 메타 모델 API 사용
- 엔티티 -> 코드 자동 생성기 -> 메타 모델 클래스
2. QueryDSL
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>
<scope>provided</scope>
</dependency>
- querydsl-jpa: QueryDSL JPA 라이브러리
- querydsl-apt: 쿼리 타입(Q) 생성할 때 필요한 라이브러리
<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>
시작
import static jpabook.jpashop.domain.QMember.member
public void basic() {
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);
검색 조건 쿼리
- QueryDSL의 where 절에는 and나 or, between, contains, startsWith 사용 가능
결과 조회
- uniqueResult(): 조회 결과가 한 건일 때 사용, 조회 결과가 없으면 null을 반환하고 하나 이상이면 com.mysema.query.NonUniqueResultException 예외 발생
- singleResult(): uniqueResult()와 같지만 결과가 하나 이상이면 처음 데이터 반환
- list(): 결과가 하나 이상일 대 사용, 결과가 없으면 빈 컬렉션 반환
페이징과 정렬
- 정렬은 orderBy를 사용하는데 쿼리 타입(Q)이 제공하는 asc(), desc() 사용
- 페이징은 offset과 limit 조합해서 사용
- 페이징은 restrict() 메소드에 com.mysema.query.QueryModifiers를 파라미터로 사용해도 됨
- 실제 페이징 처리를 하려면 검색된 전체 데이터 수를 알아야 하기 때문에 listResults() 사용
➡️ 전체 데이터 조회를 위한 count 쿼리를 한 번 더 실행하고 SearchResults를 반환하는데 이 객체에서 전체 데이터 수를 조회할 수 있음
그룹
조인
- innerJoin, join
- leftJoin, rightJoin, fullJoin
- fetch
서브 쿼리
- com.mysema.query.jpa.JPASubQuery 생성해서 사용
- 서브 쿼리의 결과가 하나면 unique(), 여러 건이면 list() 사용
프로젝션과 결과 반환
- 프로젝션 대상으로 여러 필드 선택하면 com.mysema.query.Tuple 사용
- 쿼리 결과를 엔티티가 아닌 특정 객체로 받고 싶으면 빈 생성 기능 사용
- 프로퍼티 접근: Projections.bean()
- 필드 직접 접근: Projections.fields()
- 생성자 사용: Projections.constructor()
- distinct()
수정, 삭제 배치 쿼리
- QueryDSL도 JPQL 배치 쿼리와 같이 영속성 컨텍스트를 무시하고 데이터베이스를 직접 쿼리함
- 수정 배치 쿼리는 com.mysema.query.jpa.impl.JPAUpdateClause 사용
- 삭제 배치 쿼리는 com.mysema.query.jpa.impl.JPADeleteClause 사용
동적 쿼리
- com.mysema.query.BooleanBuilder
메소드 위임
- 메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건을 직접 정의할 수 있음
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 com.mysema.query.types.expr.BooleanExpression isExpensive(Integer price) {
return ItemExpression.isExpensive(this, price);
}
}