JPA - 객체지향 쿼리 언어 - Criteria
- 참조: 자바 ORM 표준 JPA 프로그래밍
- Criteria
Criteria 는 JPQL 의 작성을 도와주는 빌더 클래스이다. 문자열로 JPQL 을 작성하면 런타임이 되어야 문법 오류를 알 수 있지만 Criteria 는 자바 코드 기반이기 때문에 안전하게 JPQL 을 작성할 수 있다.
하지만 코드가 복잡해서 직관적으로 이해하기 힘들다는 의견도 있다. 대부분의 내용이 JPQL 과 중복되므로 사용법위주로 살펴보자.
- Criteria 기초
Member member1 = new Member();
member1.setName("Name#1");
em.persist(member1);
Member member2 = new Member();
member2.setName("Name#2");
em.persist(member2);
테스트 데이터로 위와 같이 2 명의 회원을 나타내는 Member 엔티티를 영속화시킨다. 그 후 단순하게 Member 엔티티를 조회하는 Criteria 코드를 작성해보자.
//Criteria query builder
CriteriaBuilder cb = em.getCriteriaBuilder();
//Criteria 생성, 반환 타입 지정
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class); //From clause
cq.select(m); //Select clause
TypedQuery<Member> query = em.createQuery(cq);
List<Member> members = query.getResultList();
members.forEach(member -> {
System.out.println("member's name: " + member.getName());
});
위의 자바 코드는 단순하게 Criteria 를 이용하여 Member 엔티티를 모두 조회하는 코드이다. 순서는 아래와 같다.
- 우선 CriteriaBuilder 를 얻어서 Criteria 를 사용할 준비를 해야 한다.
- 그 후 빌더로 부터 CriteriaQuery 를 얻는다. 이때 반환형도 설정할 수 있다.
- FROM 절을 생성한다. 반환한 m 값은 Criteria 에서 사용하는 별칭이다.
- SELECT 절을 생성한다.
위의 과정을 거치고 나면 JPQL 사용하는 법과 똑같다. createQuery 로 부터 TypedQuery 를 얻어서 결과를 얻는다. (이전에 사용했었던 JPQL 예제들은 TypedQuery 형을 반환받는 과정을 생략하고 많이 사용하였다.)
주석을 친절하게 추가한데에는 이유가 있다. 주석을 한번 없앤 후 코드를 파악해보길 바란다. 이전에 살펴본 JPQL 이나 초반에 간략하게 알아본 QueryDSL 보다 상당히 복잡하다.
Hibernate:
/* select
generatedAlias0
from
Member as generatedAlias0 */ select
member0_.member_id as member_i1_6_,
member0_.insert_datetime as insert_d2_6_,
member0_.update_datetime as update_d3_6_,
member0_.city as city4_6_,
member0_.street as street5_6_,
member0_.zipcode as zipcode6_6_,
member0_.name as name7_6_
from
member member0_
member's name: Name#1
member's name: Name#2
위의 코드를 실행하면 변환되는 SQL 에는 특별한점은 없다. WHERE 절과 ORDER BY 절도 추가해서 사용해보자.
//Criteria query builder
CriteriaBuilder cb = em.getCriteriaBuilder();
//Criteria 생성, 반환 타입 지정
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class); //From clause
//Where clause, m.name = 'Name#2'
Predicate usernameEqual = cb.equal(m.get("name"), "Name#2");
//Order by clause, order by id desc
Order idDesc = cb.desc(m.get("id"));
//Select clause
cq.select(m)
.where(usernameEqual)
.orderBy(idDesc);
List<Member> members = em.createQuery(cq)
.getResultList();
members.forEach(member -> {
System.out.println("member's name: " + member.getName());
});
From 절 Root<Member> 이후에 Where, Order by 절을 생성해주었다. m.get("name") 은 JPQL 로 m.name 이라는 의미이다. 또 cb.desc(m.get("id")) 는 JPQL 로 m.id desc 라고 볼 수 있다.
이렇게 완성한 Predicate 와 Order 를 where 와 orderBy 에 넣어서 완성해준다. Root는 조회의 시작점이다. CriteriaQuery 로Root(from) 를 얻어주고 QueryBuilder 로 Predicate(where) -> Order(order by) 를 생성한다. From 절을 생성하고 Where 와 Order by 를 정의했으면 Criteria Query 로 조회절을 생성한다.
ID 가 2 이상이면서 이름을 역순으로 정렬하는 JPQL 을 Criteria 로 생성해보자. 우선 올바른 결과값 확인을 위해 Member 를 더 추가해준다.
//추가
Member member3 = new Member();
member3.setName("Name#3");
em.persist(member3);
Member member4 = new Member();
member4.setName("Name#4");
em.persist(member4);
이제 Criteria 를 아래와 같이 수정해준다. Root 부터 작성했다. cb(CriteriaBuilder) 는 equal 뿐만 아니라 여러가지 비교 표현 메소드가 있다. 2 이상이므로 greaterThanEqualTo 메소드를 사용해준다. 요구사항이 이름의 역순이니 name 에 대한 desc 로 Order 를 생성한다.
참고로 idPathInteger 변수를 따로 두지 않고 cb.greaterThanOrEqualTo(m.<Integer>get("id"), 2); 와 같이 한번에 표현할 수도 있다. m 의 제네릭이 Integer 가 아니라 m.get 의 반환형 Generic 이 Integer 이기 때문에 m.<Integer>get("id") 라고 표현해주어야 한다.
Root<Member> m = cq.from(Member.class); //From clause
//Where clause
Path<Integer> idPathInteger = m.get("id");
Predicate userIdGreaterEqualThan = cb.greaterThanOrEqualTo(idPathInteger, 2);
//Order by clause
Order nameDesc = cb.desc(m.get("name"));
//Select clause
cq.select(m)
.where(userIdGreaterEqualThan)
.orderBy(nameDesc);
List<Member> members = em.createQuery(cq)
.getResultList();
members.forEach(member -> {
System.out.println("member's name: " + member.getName());
});
위의 코드로 생성된 SQL 은 아래와 같다.
Hibernate:
/* select
generatedAlias0
from
Member as generatedAlias0
where
generatedAlias0.id>=2L
order by
generatedAlias0.name desc */ select
member0_.member_id as member_i1_6_,
member0_.insert_datetime as insert_d2_6_,
member0_.update_datetime as update_d3_6_,
member0_.city as city4_6_,
member0_.street as street5_6_,
member0_.zipcode as zipcode6_6_,
member0_.name as name7_6_
from
member member0_
where
member0_.member_id>=2
order by
member0_.name desc
member's name: Name#4
member's name: Name#3
member's name: Name#2