본문 바로가기
Framework and Tool/JPA

JPA - 객체지향 쿼리 언어 - Criteria

by ocwokocw 2021. 8. 4.

- 참조: 자바 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 엔티티를 모두 조회하는 코드이다. 순서는 아래와 같다.

  1. 우선 CriteriaBuilder 를 얻어서 Criteria 를 사용할 준비를 해야 한다. 
  2. 그 후 빌더로 부터 CriteriaQuery 를 얻는다. 이때 반환형도 설정할 수 있다.
  3. FROM 절을 생성한다. 반환한 m 값은 Criteria 에서 사용하는 별칭이다.
  4. 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

댓글