JPQL - Criteria 기초 (조회, 집합, 정렬)

2024. 5. 28. 16:39JPA

반응형

 

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