본문 바로가기
Framework and Tool/JPA

JPA - 객체지향 쿼리 언어 - JPQL 서브쿼리, 조건식

by ocwokocw 2021. 8. 3.

- 참조: 자바 ORM 표준 JPA 프로그래밍

- JPQL: https://docs.oracle.com/html/E13946_04/ejb3_langref.html

- 서브 쿼리

JPQL 도 SQL 처럼 서브 쿼리를 지원한다. 다만 SQL 처럼 SELECT, FROM, WHERE, HAVING 절에 쓸 수 있는것은 아니고 WHERE 와 HAVING 절에서만 쓸 수 있다.

 

하이버네이트 HQL 은 SELECT 절의 서브쿼리를 허용하며 일부 JPA 구현체는 FROM 절의 서브쿼리도 지원하므로 자신이 사용할 구현체의 스펙을 잘 알아보고 사용하도록 하자.

 

SQL 의 기본적인 문법은 다루지 않는다. JPQL 의 특성을 이용한 문법을 사용해보자.

 

Member member = new Member();
member.setName("User#1");

Address address1 = new Address("City#1", "Street#1", "Zipcode#1");

member.setAddress(address1);
em.persist(member);

Member member2 = new Member();
member2.setName("User#2");

Address address2 = new Address("City#2", "Street#2", "Zipcode#2");

member2.setAddress(address2);
em.persist(member2);

Order order1 = new Order();
order1.setMember(member);
order1.setStatus(OrderStatus.ORDER);

Delivery delivery1 = new Delivery();

delivery1.setAddress(address1);
order1.setDelivery(delivery1);

em.persist(order1);

Order order2 = new Order();
order2.setMember(member);
order2.setStatus(OrderStatus.ORDER);

Delivery delivery2 = new Delivery();

delivery2.setAddress(address1);
order2.setDelivery(delivery2);

em.persist(order2);

 

위의 코드는 User#1 회원은 2 건의 주문을 User#2 회원은 주문을 하지 않은 상황을 표현한 코드이다. 이런 상황에서 주문을 1 건 이상한 회원을 조회하는 JPQL 을 작성해보자.

 

String jpql = "select m "
		+ "from Member m "
		+ "where (select count(o) from m.orders o where o.member = m) > 0";

List<Member> members = em.createQuery(jpql, Member.class)
	.getResultList();


members.forEach(member -> {
	System.out.println("Member name: " + member.getName());
});

 

우리가 아는 SQL 와 비슷하게 작성되었다. SQL 로 작성한다면 o.member = m 과 같이 엔티티 비교가 아니라 회원의 ID 키를 이용하였을 것이다. 또한 count 도 count(1) 과 같이 값이 아닌 엔티티의 alias o 에 대해 count(o) 로 표현하였다. 

m.orders 가 컬렉션임을 이용하면 더 간단하게도 작성할 수 있다.

 

tring jpql = "select m "
		+ "from Member m "
		+ "where m.orders.size > 0";

List<Member> members = em.createQuery(jpql, Member.class)
	.getResultList();


members.forEach(member -> {
	System.out.println("Member name: " + member.getName());
});

 

복잡한 서브쿼리 대신 m.orders.size 를 이용하여 주문건이 있는 회원조건을 표현하였다.  

 

Hibernate: 
    /* select
        m 
    from
        Member m 
    where
        (
            select
                count(o) 
            from
                m.orders o 
            where
                o.member = m
        ) > 0 */ select
            member0_.member_id as member_i1_4_,
            member0_.insert_datetime as insert_d2_4_,
            member0_.update_datetime as update_d3_4_,
            member0_.city as city4_4_,
            member0_.street as street5_4_,
            member0_.zipcode as zipcode6_4_,
            member0_.name as name7_4_ 
        from
            member member0_ 
        where
            (
                select
                    count(orders1_.order_id) 
                from
                    orders orders1_ 
                where
                    member0_.member_id=orders1_.member_id 
                    and orders1_.member_id=member0_.member_id
            )>0
Member name: User#1

...................................................

Hibernate: 
    /* select
        m 
    from
        Member m 
    where
        m.orders.size > 0 */ select
            member0_.member_id as member_i1_4_,
            member0_.insert_datetime as insert_d2_4_,
            member0_.update_datetime as update_d3_4_,
            member0_.city as city4_4_,
            member0_.street as street5_4_,
            member0_.zipcode as zipcode6_4_,
            member0_.name as name7_4_ 
        from
            member member0_ 
        where
            (
                select
                    count(orders1_.member_id) 
                from
                    orders orders1_ 
                where
                    member0_.member_id=orders1_.member_id
            )>0
Member name: User#1

 

위의 2 예제를 수행해보면 SQL 문법이 다르긴 하지만 유의미한 차이가 있을만큼 아주 큰 차이는 없다.


- 서브 쿼리 함수

JPQL 의 서브 쿼리는 [NOT] EXISTS, {ALL | ANY | SOME}, [NOT] IN 와 같은 연산자를 사용할 수 있다. 서브 쿼리에서 사용하는 함수들은 SQL 에서 사용하는 함수들과 큰 차이는 없다. EXISTS 에 대한 예제만 간단하게 살펴보자.

 

String jpql = "select m "
		+ "from Member m "
		+ "where exists (select o "
		+ "				from m.orders o "
		+ "				where o.delivery.address.city = 'City#1')";

List<Member> members = em.createQuery(jpql, Member.class)
	.getResultList();

members.forEach(member -> {
	System.out.println("Member name: " + member.getName());
});

 

언급한 서브쿼리 함수들은 설명보다는 필요할 때 마다 써보면서 익히면 된다.


- 조건식

JPQL 도 SQL 처럼 많은 조건식을 지원한다. JPQL 에서 사용하는 특이한 조건식만 살펴보고 넘어가보도록 하자. 

  • Enum: 패키지명을 포함한 전체 이름을 사용해야 한다. ex) jpabook.MemberType.Admin
  • 엔티티 타입: 엔티티의 타입을 표현할 수 있다. ex) TYPE(m) = Member
  • 컬렉션 식: 컬렉션 식에서만 사용하는 기능 ex) m.orders is not empty

이 외에도 CASE WHEN, 문자열 함수, 논리 연산 등 많은 연산을 지원한다. JPQL 을 자세히 알고 싶은 사람은 https://docs.oracle.com/html/E13946_04/ejb3_langref.html 를 참조하자.

댓글