- 참조: 자바 ORM 표준 JPA 프로그래밍
- 서브 쿼리
Criteria 로도 서브쿼리를 작성할 수 있다. 메인의 테이블과 연관된 서브쿼리와 메인의 테이블이 연관되지 않은 간단한 서브쿼리를 알아보도록 한다.
- 간단한 서브쿼리
메인 테이블과 아무런 연관이 없는 간단한 서브쿼리부터 알아보도록 하자. 평균나이보다 나이가 많은 Member 엔티티만 조회해보자. JPQL 로 표현하면 "select m from Member m where m.age >= (select AVG(m2.age) from Member m2)" 과 같이 될 것이다.
데이터는 앞의 글에서 사용했던 아래 데이터를 이용한다.
Member member1 = new Member();
member1.setName("Name#1");
member1.setAge(10);
member1.setAddress(
new Address("City#1", "Street#1", "Zipcode#1"));
em.persist(member1);
Member member2 = new Member();
member2.setName("Name#2");
member2.setAge(13);
member2.setAddress(
new Address("City#1", "Street#2", "Zipcode#2"));
em.persist(member2);
Member member3 = new Member();
member3.setName("Name#3");
member3.setAge(32);
member3.setAddress(
new Address("City#2", "Street#3", "Zipcode#3"));
em.persist(member3);
Member member4 = new Member();
member4.setName("Name#4");
member4.setAge(22);
member4.setAddress(
new Address("City#2", "Street#4", "Zipcode#4"));
em.persist(member4);
서브쿼리를 이용한 코드를 살펴보자.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class);
//Subquery
Subquery<Double> ageSubQuery = mainQuery.subquery(Double.class);
Root<Member> m2 = ageSubQuery.from(Member.class);
ageSubQuery.select(cb.avg(m2.get("age")));
//Main query
Root<Member> m = mainQuery.from(Member.class);
mainQuery.select(m);
mainQuery.where(cb.ge(m.<Integer>get("age"), ageSubQuery));
List<Member> members = em.createQuery(mainQuery)
.getResultList();
members.forEach(member -> {
System.out.println("Member ID: " + member.getId() +
", age: " + member.getAge());
});
그냥 읽으면 복잡한 것 같지만 서브 쿼리와 메인 쿼리로 나누어서 구조를 생각해보면 파악이 용이하다. 우선 메인 쿼리의 CriteriaQuery(mainQuery) 를 생성한다.
그 후 mainQuery 에서 subquery 메소드로 서브 쿼리를 생성한다. 그 후부터는 from 메소드로 Root 를 얻고 select, where 등을 순차적으로 사용하면 된다. 특별한 조건없이 평균 나이만 구하면 되므로 select 메소드만 이용한다.
서브쿼리를 생성했으면 메인 쿼리를 작성한다. from 메소드로 부터 Root 를 얻고 select where 등을 작성한다. 이때 SubQuery 는 Expression 을 확장하므로 CriteriaBuilder 의 비교 표현식들의 인자가 될 수 있다.(ge 메소드의 2 번째 인자)
Hibernate:
/* select
generatedAlias0
from
Member as generatedAlias0
where
generatedAlias0.age>=(
select
avg(generatedAlias1.age)
from
Member as generatedAlias1
) */ 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_.age as age7_6_,
member0_.name as name8_6_
from
member member0_
where
member0_.age>=(
select
avg(cast(member1_.age as double))
from
member member1_
)
Member ID: 3, age: 32
Member ID: 4, age: 22
생성된 SQL 을 살펴보면 우리가 의도했던대로 변환된것을 알 수 있다.
- 상호 연관 서브쿼리
이번에는 서브 쿼리에서 메인 쿼리의 정보를 이용해본다. 주문이 한 건도 없는 Member들, JPQL 로는 select m from Member m where 0 = (select count(o) from m.orders o) 에 해당하는 엔티티들을 조회한다.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class);
Root<Member> m = mainQuery.from(Member.class);
//Sub Query
Subquery<Long> subQuery = mainQuery.subquery(Long.class);
Root<Member> subM = subQuery.correlate(m);
Join<Member, Order> o = subM.join("orders");
subQuery.select(cb.count(o));
//Main Query
mainQuery.select(m)
.where(cb.equal(subQuery, 0));
List<Member> members = em.createQuery(mainQuery)
.getResultList();
members.forEach(member -> {
System.out.println("Member ID: " + member.getId() +
", age: " + member.getAge());
});
사실 예제의 요구사항은 Join 을 이용해도 되지만 서브 쿼리 사용이 주 목적이므로 문법에 초점을 맞추길 바란다.
서브 쿼리에서 메인 쿼리 정보를 사용하려면 correlate 메소드를 이용해서 별칭을 얻어야 한다. 그 후 join 메소드로 주문 목록 orders 를 얻었다. 주문 건수를 구해야 하므로 서브 쿼리의 select 절에서 CriteriaBuilder 의 count 메소드를 이용했다.
메인 쿼리에서는 where 절에 equal 메소드를 이용한다. subQuery 의 count 결과는 Long 형으로 반환되는데 이 결과가 0 인것을 조회하면 주문 목록이 한 건도 없는 Member 엔티티를 조회할 수 있다.
'Framework and Tool > JPA' 카테고리의 다른 글
JPA - 객체지향 쿼리 언어 - Criteria 메타 모델 API (0) | 2021.08.13 |
---|---|
JPA - 객체지향 쿼리 언어 - Criteria 파라미터, 네이티브 함수 (0) | 2021.08.12 |
JPA - 객체지향 쿼리 언어 - Criteria 집합, 정렬, 조인 (0) | 2021.08.09 |
JPA - 객체지향 쿼리 언어 - Criteria 조회 (0) | 2021.08.08 |
JPA - 객체지향 쿼리 언어 - Criteria 쿼리 생성 (0) | 2021.08.05 |
댓글