본문 바로가기
Framework and Tool/JPA

JPA - 객체지향 쿼리 언어 - JPQL 경로 표현식

by ocwokocw 2021. 8. 2.

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

- 경로 표현식

경로 표현식이라는것은 쉽게 얘기해 . 을 찍어 객체 그래프를 탐색하는것을 말한다.

 

String jpql = "select m.name, o, a "
		+ "from Member m "
		+ "inner join m.orders o "
		+ "inner join m.address a "
		+ "	where m.name = :username";

List<Object[]> members = em.createQuery(jpql)
	.setParameter("username", "User#1")
	.getResultList();

 

위와 같은 JPQL 이 있다고 할 때 m.name, m.orders, m.address 모두 경로표현식을 이용한 것이라고 말할 수 있다. 경로 표현식은 종류에 따라 아래와 같이 나눈다.

  • 상태 필드: 단순히 값을 저장하기 위한 필드(ex - m.name)
  • 연관필드 - 단일 값: @ManyToOne, @OneToOne 대상이 엔티티 or 임베디드 타입 (ex - m.address)
  • 연관필드 - 컬렉션 값: @ManyToMany, @OneToMany 대상 컬렉션 (ex - m.orders)

JPQL 경로탐색에는 특징이 있다. 연관필드의 단일 값은 묵시적으로 내부조인이 일어나며 계속해서 탐색이 가능하다. 하지만 컬렉션 값은 묵시적으로 내부조인이 일어나며 계속탐색이 불가능하다. 탐색을 하려면 FROM 절에서 alias 를 얻어야 탐색할 수 있다.


- 단일 값 연관 경로 탐색

String jpql = "select o.member "
		+ "from Order o ";

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

members.forEach(System.out::println);

 

위의 JPQL 을 살펴보면 Join 을 사용하지 않았다. 하지만 Order 가 참조하고 있는 Member 엔티티를 조회하기 때문에 묵시적인 Join 이 일어난다.

 

Hibernate: 
    /* select
        o.member 
    from
        
    Order o  */ select
        member1_.member_id as member_i1_4_,
        member1_.insert_datetime as insert_d2_4_,
        member1_.update_datetime as update_d3_4_,
        member1_.city as city4_4_,
        member1_.street as street5_4_,
        member1_.zipcode as zipcode6_4_,
        member1_.name as name7_4_ from
            orders order0_ 
        inner join
            member member1_ 
                on order0_.member_id=member1_.member_id

 

그래서 위와 같이 ORDERS 와 MEMBER 테이블의 외래키로 조인 SQL 이 수행된다.


- 컬렉션 값 연관 경로 탐색

JPQL 을 사용하면서 실수를 많이 하는 부분이 컬렉션 값에서 경로를 탐색하는것이라고 할 수 있다. "회원이 주문한 Order 의 ID 들을 전부 조회해야 한다." 라는 요구사항이 있을 때 아래와 같이 JPQL 을 작성할 수 있다.

 

String jpql = "select m.orders.id "
		+ "from Member m ";

List<Object[]> orderIds = em.createQuery(jpql)
	.getResultList();

 

위의 JPQL 은 실행하면 오류가 난다. JPQL 은 컬렉션 값의 경로 탐색을 허용하지 않기 때문이다. 만약 컬렉션 에서 계속 경로를 탐색하고 싶다면 Join 을 이용하여 alias 를 준 다음 참조를 해야 한다.

 

String jpql = "select o.id "
		+ "from Member m "
		+ "  inner join m.orders o";

em.createQuery(jpql, Long.class)
	.getResultList()
	.forEach(System.out::println);

 

위의 코드를 실행하면 정상적으로 SQL 로 변환되어수행되고, Order 의 ID. 도 알 수 있다.

 

Hibernate: 
    /* select
        o.id 
    from
        Member m   
    inner join
        m.orders o */ select
            orders1_.order_id as col_0_0_ 
        from
            member member0_ 
        inner join
            orders orders1_ 
                on member0_.member_id=orders1_.member_id
2
4

- 경로 표현식에 의한 조인

경로 탐색을 하면 의도여부와 상관없이 묵시적으로 Join 이 일어날 수 있다는것을 인지해야 한다. 그리고 Join 은 당연히 SQL 의 성능에 영향을 미친다.

 

묵시적으로 발생하는 Join 은 항상 INNER JOIN 이 발생한다. 만약 회원과 회원의 주문 목록을 조회하는 JPQL 이 있다고 가정하자. 물론 실제로 사용할 때에는 Member 의 엔티티를 조회한 후 Member 의 주문 목록을 가져오겠지만 예시를 위한것이니 참조하도록 하자.

 

String jpql = "select m, m.orders from Member m ";

List<Object[]> orders = em.createQuery(jpql)
	.getResultList();

System.out.println("orders's size: " + orders.size());

 

회원은 아직 주문을 한 건도 하지 않았다고 할 때 회원정보는 가져오고 주문 목록은 빈값으로 가져오고 싶을 때 위와 같이 묵시적 Join 을 사용하면 결과를 얻어올 수 없다. ORDERS 테이블과 INNER JOIN 을 수행하기 때문이다.

 

Hibernate: 
    /* select
        m,
        m.orders 
    from
        Member m  */ select
            member0_.member_id as member_i1_4_0_,
            orders1_.order_id as order_id1_6_1_,
            member0_.insert_datetime as insert_d2_4_0_,
            member0_.update_datetime as update_d3_4_0_,
            member0_.city as city4_4_0_,
            member0_.street as street5_4_0_,
            member0_.zipcode as zipcode6_4_0_,
            member0_.name as name7_4_0_,
            orders1_.insert_datetime as insert_d2_6_1_,
            orders1_.update_datetime as update_d3_6_1_,
            orders1_.delivery_id as delivery6_6_1_,
            orders1_.member_id as member_i7_6_1_,
            orders1_.order_date as order_da4_6_1_,
            orders1_.status as status5_6_1_ 
        from
            member member0_ 
        inner join
            orders orders1_ 
                on member0_.member_id=orders1_.member_id
orders's size: 0

 

묵시적 조인은 위와 같은 상황이 벌어질뿐더러 다른 사람이 JPQL을 볼때도 헷갈릴 수 있으므로 명시적으로 조인을 기술해주는것이 좋다.

댓글