본문 바로가기

SpringFramework/JPA

JPA - Criteria

Criteria는 JPQL을 생성하는 빌더 클래스다. Criteria는 문자가 아닌 query.select(arg).where(..) 이런 모양으로 프로그래밍 코드로 JPQL을 작성할 수 있는 특징이 있다. 문자열 기반 쿼리는 컴파일시점에서는 오류가 나지 않지만, Criteria는 코드로 JPQL을 작성하기때문에 컴파일 시점에 오류를 잡아낼 수 있다.

하지만, 장점이 많은 만큼 복잡하고 장황한 문제가 있다. 그래서 사용하기 불편하고, 복잡한 코드때문에 가독성이 떨어지는 단점이 있다.

 

아래의 Criteria 사용 기본예제를 살펴보자. Spring-Data-JPA의 EntityManager를 사용한다.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("Criteria_Start");
EntityManager em = emf.createEntityManager();

// Criteria 사용 준비
CriteriaBuilder criteria = em.getCriteriaBuilder();
CriteriaQuery<Member> query = criteria.createQuery(Member.class);

// 루트 클래스 (조회를 시작할 클래스)
Root<Member> member = query.from(Member.class);

// 쿼리 생성
CriteriaQuery<Member> members = query.select(member).where(criteria.equal(member.get("username"), "xggames"));
List<Member> resultList = em.createQuery(members).getResultList();

- Criteria API는 javax.persistence.criteria 패키지에 있다.

- Criteria 쿼리를 생성하려면 먼저 CriteriaBuilder를 얻어야 한다. 이 빌더는 EntityManager, EntityManagerFactory에서 얻을 수 있다.

- Criteria 쿼리빌더에서 CriteriaQuery를 생성하고, 리턴타입을 지정할 수 있다.

쿼리 생성

CriteriaBuilder.createQuery() : Criteria 쿼리 생성. 파라미터에는 반환받을 값(객체)를 지정한다. 파라미터가 없으면, Object가 반환값이다.

// EntityManager.. 생성

CriteriaBuilder cb = em.getCriteriaBuilder();

// 반환타입 지정
CriteriaQuery<Member> cq1 = cb.createQuery(Member.class); // 반환타입 Member객체
List<Member> resultList1 = em.createQuery(cq1).getResultList();

// 반환타입 미지정
CriteriaQuery<Member> cq2 = cb.createQuery();
List<Object> resultList2 = em.createQuery(cq2).getResultList();

// 반환타입이 둘이상
CriteriaQuery<Member> cq3 = cb.createQuery(Object[].class);
List<Object[]> resultList3 = em.createQuery(cq3).getResultList();

쿼리 메소드

- 조회

    - 조회 대상을 한건 지정: cq.select(m)

    - 조회 대상을 여러건 지정

    => q.multiselect(m.get("username"), m.get("age"))

    => q.select(cb.array(m.get("username"), m.get("age")))

 

- DISTINCT

 

: select, multiselect 다음에 distinct(true)를 사용한다.

    => cq.select(m).distinct(true)

 

- construct() (=NEW)

: JPQL의 select new 생성자() 구문을 construct() 메소드로 사용한다.

    => cq.select(cb.construct(MemberDTO.class, m.get("username"), m.get("age")));

 

- 튜플

: Criteria는 Map과 비슷한 튜플이라는 특별한 반환 객체를 제공

// EntityManager.. 생성

CriteriaBuilder cb = em.getCriteriaBuilder();

CriteriaQuery<Tuple> cq = cb.createTupleQuery(); // TupleQuery 생성
// CriteriaQuery<Tuple> cq = cb.createQuery(Tuple.class); // 위와 같음

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);
}

// - cq.multiselect()와 cq.select(cb.tuple()) 모두 사용가능
// 물론, 엔티티도 조회가능하다.
cq.select(cb.tuple(
    m.alias("m"), // 회원 엔티티
    m.get("age").alias("age")
));


TypedQuery<Tuple> query = em.createQuery(cq);
List<Tuple> resultList = query.getResultList();
for (Tuple tuple : resultList) {
    Member member = tuple.get("m", Member.class);
}

- 튜플은 이름 기반이므로 순서 기반의 Object[]보다 안전함.

 

- 집합

    - groupBy()

        => cq.groupBy(m.get("team").get("name")); // GROUP BY m.team.name

    - having()

        => having(cb.gt(cb.max(m.<Integer>get("age")), 10)) // HAVING min(m.age) > 10

 

- 정렬

    - orderBy()

        => orderBy(cb.desc(m.get("age"))); // ORDER BY m.age DESC

 

- 조인

: join() 메소드와 JoinType 클래스를 사용

public enum JoinType {
    INNER, // 내부 조인
    LEFT, // LEFT 외부 조인
    RIGHT // RIGHT 외부 조인
    // JPA 구현체나 데이터베이스에 따라 지원하지 않을 수도 있다.
}

// join() 예제

Root<Member> m = cq.from(Member.class);
// JoinType 생략시, default JoinType.INNER
Join<Member, Team> t = m.join("team", JoinType.INNER); // 내부 조인

// FETCH JOIN
Join<Member, Team> t = m.fetch("team", JoinType.LEFT);


cq.multiselect(m, t).where(cb.equal(t.get("name"), "xggames_team"));

 

- 서브쿼리

// --- 기본 서브 쿼리

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class);

// 서브쿼리 생성
Subquery<Double> subQuery = mainQuery.subquery(Double.class);

Root<Member> m2 = subQuery.from(Member.class);
subQuery.select(cb.avg(m2.<Integer>get("age")));

// 메인쿼리 생성
Root<Member> m = mainQuery.from(Member.class);
mainQuery.select(m).where(cb.ge(m.<Integer>get("age"), subQuery));

List<Member> resultList = em.createQuery(mainQuery).getResultList();
/*
 SELECT m
 FROM Member m
 WHERE m.age >= (SELECT AVG(m2.age) FROM Member m2)
*/
// --------------------------------------------------------------
// --- 상호 관련 서브 쿼리
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class);

// 서브쿼리에서 사용되는 메인 쿼리의 m
Root<Member> m = mainQuery.from(Member.class);

// 서브쿼리 생성
Subquery<Team> subQuery = mainQuery.subquery(Team.class);
Root<Member subM = subQuery.correlate(m); // 메인 쿼리의 별칭을 가져옴

Join<Member, Team> t = subM.join("team");
subQuery.select(t).where(cb.equal(t.get("name"), "xggames_team"));

// 메인쿼리 생성
mainQuery.select(m).where(cb.exists(subQuery));

List<Member> resultList = em.createQuery(mainQuery).getResultList();
/*
 SELECT m
 FROM Member m
 WHERE EXISTS (SELECT t FROM m.team t WHERE t.name = 'xggames_team')
*/

 

- IN절

    => cq.select(m).where(cb.in(m.get("username")).value("회원1").value("회원2"));

 

- CASE

: selectCase(), when(), otherwise() 메소드를 사용하여 구현

// ...
cq.multiselect(
    m.get("username"),
    cb.selectCase()
        .when(cb.ge(m.<Integer>get("age"), 60), 600)
        .when(cb.le(m.<Integer>get("age"), 15), 500)
        .otherwise(1000)
);
/*
 SELECT
     m.username,
     CASE
         WHEN m.age >= 60 THEN 600
         WHEN m.age <= 15 THEN 500
         ELSE 1000
     END
 FROM
     Member m
*/

 

- 파라미터

: JPQL처럼 파라미터 정의할 수 있다. parameter() 메소드를 사용한다.

    => cq.select(m).where(cb.equal(m.get("username"), cb.parameter(String.class, "usernameParam")));

    List<Member> resultList = em.createQuery(cq).setParameter("usernameParam", "xggames").getResultList();

Criteria 함수 정리

- 조건 함수

함수명 JPQL
and() AND
or() OR
not() NOT
equal(), notEqual() =, <>
lt(), lessThen() <
le(), LessThanOrEqualTo() <=
gt(), greaterThan() >
ge(), greaterThanOrEqualTo() >=
between() BETWEEN
like(), notLike() LIKE, NOT LIKE
isTrue(), isFalse() IS TRUE, IS FALSE
in(), not(in()) IN, NOT IN
exists(), not(exists()) EXISTS, NOT EXISTS
isNull(), isNotNull() IS NULL, IS NOT NULL
isEmpty(), isNotEmpty() IS EMPTY, IS NOT EMPTY
isMember(), isNotMember() MEMBER OF, NOT MEMBER OF

- 스칼라와 기타 함수

함수명 JPQL
sum() +
neg(), diff() -
prod() *
quot() /
all() ALL
any() ANY
some() SOME
abs() ABS
sqrt() SQRT
mod() MOD
size() SIZE
length() LENGTH
locate() LOCATE
concat() CONCAT
upper() UPPER
lower() LOWER
substring() SUBSTRING()
trim() TRIM
currentDate() CURRENT_DATE
currentTime() CURRENT_TIME
currentTimestamp() CURRENT_TIMESTAMP

- 집합 함수, 분기 함수

함수명 JPQL
avg() AVG
max(), greatest() MAX
min(), least() MIN
sum(), sumAsLong(), sumAsDouble() SUM
count() COUNT
countDistinct() COUNT DISTINCT
nullif() NULLIF
coalesce() COALESCE
selectCase() CASE

참고자료

- 서적 - 자바 ORM 표준 JPA 프로그래밍 제 10장 - 김영한 지음

'SpringFramework > JPA' 카테고리의 다른 글

JPA - 스프링 데이터 JPA  (0) 2021.11.23
JPA - QueryDSL  (0) 2021.11.19
JPA - 객체지향 쿼리 언어, JPQL  (0) 2021.11.17
JPA - 값타입  (0) 2021.11.16
JPA - 연관관계 고급매핑  (0) 2021.11.15