Criteria 쿼리는 JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API이다. Criteria를 사용하면 문자가 아닌 코드로 JPQL을 작성하므로 문법 오류를 컴파일 단계에서 잡을 수 있고 문자 기반의 JPQL보다 동적 쿼리를 안전하게 생성할 수 있는 장점이 있다.
Criteria 기초
Criteria API는 javax.persistence.criteria 패키지에 있다.
아래는 단순한 Criteria 쿼리이다.
//JPQL: select m from Member m
CriteriaBuilder cb = em.getCriteriaBuilder(); // 1. Criteria 쿼리 빌더
// 2. Criteria 생성, 반환 타입 지정
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class); // 3. FROM 절
cq.select(m); // 4. SELECT 절
TypedQuery<Member> query = em.createQuery(cq);
List<Member> members = query.getResultList();
1. Criteria 쿼리를 생성하려면 먼저 CriteriaBuilder를 얻어야 한다. CriteriaBuilder는 EntityManager나 EntityManagerFactory에서 얻을 수 있다.
2. Criteria 쿼리 빌더에서 CriteriaQuery를 생성한다. 이때 반환타입을 지정할 수 있다.
3. FROM 절을 생성한다. 반환된 값 m은 Criteria에서 사용하는 특별한 별칭이다. m을 조회의 시작점이라는 의미로 쿼리 Root라 한다.
4. SELECT 절을 생성한다.
위의 쿼리에 검색 조건과 정렬 조건을 추가해보겠다.
//JPQL
//select m from Member m
//where m.username=“회원1”
//order by m.age desc
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
// 1. 검색 조건 정의
Predicate usernameEqual = cb.equal(m.get(“username”), “회원1”);
// 2. 정렬 조건 정의
javax.persistence.criteria.Order ageDesc = cb.desc(m.get(“age”));
// 3. 쿼리 생성
cq.select(m)
.where(usernameEqual) // WHERE 절 생성
.orderBy(ageDesc); // ORDER BY 절 생성
TypedQuery<Member> query = em.createQuery(cq);
List<Member> members = query.getResultList();
1. 검색조건을 정의한 부분을 보면 m.get(“username”) 이 표현은 jpql에서 m.username이라는 표현과 같다.
2. 정렬 조건을 정의하는 코드인 cb.desc(m.get(“age”))는 jpql에서 m.age.desc 과 같은 표현이다.
3. 만들어둔 조건을 where, orderBy 절에 넣어서 원하는 쿼리를 생성한다.
다른 예제로 10살을 초과하는 회원을 조회하고 나이를 역순으로 정렬해본다.
//select from Member m
//where m.age > 10 order by m.age desc
Root<Member> m = cq.from(Member.class);
//타입 정보 필요
Predicate ageGt = cb.greaterThan(m.<Integer>get("age"), 10);
cq.select(m);
cq.where(ageGt);
cq.orderBy(cb.desc(m.get("age")));
m.<Integer>get("age")에서 제네릭으로 타입 정보를 주었다. m.get("age")에서는 "age"의 타입 정보를 알지 못한다. 따라서 제네릭으로 반환 타입 정보를 명시해야한다.(보통 String 같은 문자타입은 지정하지 않아도 된다.) 참고로 greaterThan() 대신에 gt()를 사용해도 된다.
조회
//조회 대상 한 건
cq.select(m) //JPQL: select m
//조회 대상 여러 건
cq.multiselect(m.get("username"), m.get("age")); //JPQL: select m.username, m.age
cq.select( cb.array(m.get("username"), m.get("age")) );
DISTINCT
//JPQL: select distinct m.username, m.age from Member m
cq.multiselect(m.get("username"), m.get("age")).distinct(true);
cq.select(cb.array(m.get("username"), m.get("age"))).distinct(true);
NEW, construct()
//JPQL: select new jpa.domain.MemberDTO(m.username, m.age) from Member m
CriteriaQuery<MemberDTO> cq = cb.createQuery(MemberDTO.class);
Root<Member> m = cq.from(Member.class);
cq.select(cb.construct(MemberDTO.class, m.get("username"), m.get("age")));
TypedQuery<MemberDTO> query = em.createQuery(cq);
List<MemberDTO> resultList = query.getResultList();
튜플
Criteria는 Map과 비슷한 튜플이라는 특별한 반환 객체를 제공한다.
//JPQL: select m.username, m.age from Member m
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Member> m = cq.from(Member.class);
cq.multiselect(
m.get("username").alias("username"), //튜플에서 사용할 튜플 별칭
m.get("age").alias("age")
);
TypedQuery<Tuple> query = em.createQuery(cq);
List<Tuple> resultList = query.getResultList();
for(Tuple tuple : resultList){
//튜플 별칭으로 조회
String username = tuple.get("username", String.class);
Integer age = tuple.get("age", Integer.class);
}
집합 - GROUP BY, HAVING / 정렬 - ORDER BY
팀별로 가장 나이 어린 사람이 10살을 초과하는 팀의 나이가 가장 많은 사람과 가장 적은 사람을 구해본다.
/*
JPQL:
select m.team.name, max(m.age), min(m.age)
from Member m
group by m.team.name
having min(m.age) > 10
order by m.age desc
*/
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
Root<Member> m = cq.from(Member.class);
Expression maxAge = cb.max(m.<Integer>get("age"));
Expression minAge = cb.min(m.<Integer>get("age"));
cq.multiselect(m.get("team").get("name"), maxAge, minAge)
.groupBy(m.get("team").get("name")) //GROUP BY
.having(cb.gt(minAge, 10)) //HAVING
.orderBy(cb.desc(m.get("age"))); // ORDER BY
TypedQuery<Object[]> query = em.createQuery(cq);
List<Object[]> resultList = query.getResultList();
cf)
- Expression: 쿼리에서 값을 표현하는 데 사용된다. 주로 SELECT 절이나 조건식의 일부분으로 사용된다.
- Predicate: 쿼리 조건을 나타내는 데 사용된다. 주로 WHERE 절에서 조건을 정의하는 데 사용된다.
'JPA' 카테고리의 다른 글
JPQL - Named 쿼리 - 정적 쿼리 (0) | 2024.05.14 |
---|---|
JPQL - CASE 식 (0) | 2024.03.25 |
JPQL - 서브 쿼리 (2) | 2023.10.23 |
JPQL - 경로 표현식 (4) | 2023.10.23 |
JPQL - 페치 조인(Fetch Join) (0) | 2023.10.12 |