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 |