일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- delayed message plugin
- Spring
- GitHub Actions
- java
- 이펙티브자바
- springboot
- JPQL
- 30기
- 객체지향 쿼리 언어
- 도메인 주도 개발 시작하기
- cicd
- 한국대학생it경영학회
- 교육기획팀원
- rabbitmq-delayed-message-exchange
- scheduling messages with rabbitmq
- Domain Driven Design
- ddd
- kusitms
- reactive operaton
- 자바 ORM 표준 JPA 프로그래밍
- 큐시즘
- RESTClient
- 최범균
- 자동처리
- 영속성
- 밋업프로젝트
- Spring Batch
- JPA
- 교육기획팀
- jdbc
- Today
- Total
코딩은 마라톤
[자바 ORM 표준 JPA 프로그래밍] 10장. Criteria 쿼리 본문
회사에서 ElasticSearch 조회할 때 CriteriaQuery 만들어봤는데 JPA에도 Criteria 쿼리가 있다니.. 신기방기
Criteria
JPQL을 자바 코드로 작성할 수 있게 도와주는 빌더 클래스 API
코드로 JPQL을 작성함으로써 컴파일 단계에서 문법 오류를 잡을 수 있고 동적 쿼리를 안전하게 생성 가능
코드가 복잡하고 장황해서 직관적으로 이해하기 힘든 단점
기초
//JPQL: select m from Member m
CriteriaBuilder cb = em.getCriteriaBuilder(); //Criteria 쿼리 빌더 O
//Criteria 생성, 반환 타입 지정
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class); //FROM 절
cq.select(m); //SELECT 절
TypedQuery<Member> query = em.createQuery(cq);
List<Member> members = query.getResultList();
- Criteria 쿼리를 작성하기 위해 빌더를 생성한다. (EntityManager를 통해 얻음)
- 빌더에서 Criteria 쿼리를 생성한다. 이때 반환 타입 지정한다.
- From 절을 생성한다. 위 코드의 m은 Criteria에서 사용하는 별칭으로, m을 조회의 시작점이라 해서 "Root"라고 한다.
- select 절을 생성한다.
상기 코드에 검색 조건과 정렬 조건 또한 추가할 수 있다.
// 검색 조건
Predicate usernameEqual = cb.equal(m.get("username"), "검색값");
// 정렬 조건
javax.persistence.criteria.Order ageDesc = cb.desc(m.get("age"));
// 쿼리 생성
cq.select(m)
.where(usernameEqual)
.orderBy(ageDesc);
- 쿼리 루트는 조회의 시작점이다. (Root<> ~ = cq.from(~));
- Criteria에서 사용되는 특별한 별칭이다. (상기 Member m)
- 별칭은 엔티티에만 부여할 수 있다.
생성
public interface CriteriaBuilder {
CriteriaQuery<Object> createQuery (); //조회값 반환 타입: Object
//조회값반환타입:엔티티,임베디드타입,기타
<T> CriteriaQuery<T> createQuery(Class<T> resultclass);
...
}
createQuery 실행 시 파라미터로 쿼리 결과에 대한 반환 타입을 지정할 수 있어
만약 위에서 createQuery(Member.class)를 통해 쿼리를 생성했다면, em.createQuery(cq)에선 반환 타입을 지정하지 않아도 된다.
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
List<Member> resultList = em.createQuery(cq).getResultList();
혹은 파라미터 없이 createQuery()를 사용하여 Object를 반환하기도 한다.
반환 타입이 둘 이상일 경우 Object[]를 사용하는 것도 편리하다.
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
List<Object[]> resultList = em.createQuery(cq).getResultList();
조회
조회는 select() 메서드를 통해 할 수 있다.
조회 대상을 한 건 혹은 여러 건 지정할 수 있다.
- 조회 대상 : 1건
- cq.select(m);
- 조회 대상 : 여러 건
- cq.multiselect(m.get("username"), m.get("age"));
- 여러건 지정은 cb.array를 사용할 수도 있다.
cq.select(cb.array(m.get("username"), m.get("age")));
DISTINCT
distince는 select, multiselect 다음에 distinct(true)를 사용한다.
cq.multiselect(m.get("username"), m.get("age")).distinct(true);
NEW, construct()
JPQL에서 select new 생성자() 구문은 cb.construct(클래스타입, ...)을 사용한다.
cq.select(cb.construct(MemberDTO.class, m.get("username"), m.get("age")));
튜플
Criteria는 Map과 비슷한 튜플이라는 특별한 반환 객체를 제공한다.
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
// CriteriaQuery<Tuple> cq = cb.createQuery(Tuple.class);
// createTupleQuery()를 사용하거나 createQuery(Tuple.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);
}
Map에서 Key에 해당하는 별칭을 설정해주고, Value를 별칭을 통해 얻을 수 있다.
따라서 별칭은 필수로 할당해야 한다.
cq.select(cb.tuple(...));
또한 cq.multiselect() 대신 위와 같이 사용해도 동일한 결과를 반환한다.
집합
- GROUP BY : cq.groupBy(m.get("team").get("name")) 와 같이 사용한다.
- HAVING : cq.having(cb.gt(minAge, 10)) 와 같이 사용한다. 여기서 gt는 min(m.age) > 10과 같다. (greater than)
정렬
- 정렬은 CriterBuilder를 사용한다.
- cb.desc(...), cb.asc(...) 를 사용한다.
cq.select(m)
.where(ageGt)
.orderBy(cb.desc(m.get("age")));
조인
조인은 join() 메소드와 JoinType ENUM 클래스를 사용한다.
/* JPQL
select m, t from Member m
inner join m.team t
where t.name = ’팀A’
*/
Root<Member> m = cq.from(Member.class);
Join<Member, Team> t = m.join("team", JoinType.INNER);
cq.multiselect(m, t)
.where(cb.equal(t.get("name"), "팀A"));
- 조인 타입을 생략하면 내부 조인을 사용한다.
- 외부 조인은 JoinType.LEFT로 설정한다. (RIGHT는 하이버네이트나 구현체에 따라 없을 수 있다.)
- FETCH JOIN은 fetch(조인 대상, JoinType)를 사용한다.
- m.fetch("team", JoinType.LEFT);
cq.select(m);
- m.fetch("team", JoinType.LEFT);
서브 쿼리
/* JPQL:
select m from Member m
where m.age >=
(select AVG(m2.age) from Member m2)
*/
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);
단순한 쿼리는 mainQuery.subQuery(...)로 생성한다.
만약 서브 쿼리에서 메인 쿼리의 정보를 사용한다면 메인 쿼리에서 사용한 별칭을 사용한다.
/* JPQL
select m from Member m
where exists
(select t from m.team t where t.name = '팀A')
*/
Root<Member> m = mainQuery.from(Member.class);
//서브 쿼리 생성
Subquery<Team> subQuery = mainQuery.subquery(Team.class);
Root<Member> subM = subQuery.correlate(m); // mainQuery의 별칭을 가져온다.
Join<Member, Team> t = subM.join("team")
...
IN, CASE
- IN : CriteriaBuilder의 in(...) 메소드 사용
- where(cb.in(...))
.value(...)
.value(...)
...
- where(cb.in(...))
- CASE : CriteriaBuilder의 selectCase()와 when(<조건 절>, <then 절>).otherwise(else 절)을 사용한다.
파라미터 정의
/* JPQL
select m from Member m
where m.username = :usernameParam
*/
cq.select(m)
.where(cb.equal(m.get("username"), cb.parameter(String.class, "usernameParam")));
List<Member> resultList = em.createQuery(cq)
.setParameter("usernameParam", "멤버") // 파라미터 바인딩
.getResultList();
- cb.parameter(파라미터 타입, 파라미터 이름) 으로 파라미터 정의
- setParameter(파라미터 이름, 값) 으로 파라미터 사용할 값 바인딩
'Backend > JPA' 카테고리의 다른 글
[JPA] 엔티티 저장 후 조회, 동등성 문제 및 해설 (@Transactional) (2) | 2024.12.31 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 10장. JPQL (1) (1) | 2024.06.16 |
[자바 ORM 표준 JPA 프로그래밍] 9장. 값 타입 (0) | 2024.03.17 |
[자바 ORM 표준 JPA 프로그래밍] 8장. 프록시와 연관관계 관리 (0) | 2024.02.25 |
[자바 ORM 표준 JPA 프로그래밍] 7장. 고급 매핑 (0) | 2024.02.22 |