본문 바로가기
Framework and Tool/JPA

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

by ocwokocw 2021. 8. 11.

- 참조: 자바 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 엔티티를 조회할 수 있다.

댓글