본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
JPA

JPQL - 경로 표현식

by 방구석 대학생 2020. 11. 28.

"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다."

www.inflearn.com/course/ORM-JPA-Basic#

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그

www.inflearn.com

 

 

경로 표현식

 - . (점) 을 찍어 객체 그래프를 탐색하는 것이다.

예시 :

select m.username -> 상태 필드

        from Member m

               join m.team t -> 단일 값 연관 필드

               join m.orders o -> 컬렉션 값 연관 필드

where t.name = '팀A'

 

상태 필드로 가냐, 단일 값 연관 필드로 가냐, 컬렉션 값 연관 필드로 가냐에 따라서 내부적으로 동작하는 방식이 달라지기 때문에 결과가 달라지게 된다.

위의 3가지를 반드시 구분해서 이해해야 한다.

- 상태 필드(state field) : 단순히 값을 저장하기 위한 필드

- 연관 필드(association field) : 연관관계를 위한 필드

   -> 단일 값 연관 필드 : @ManyToOne, @OneToOne 과 같이 대상이 Entity 인 경우(예 : m.team)

   -> 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany 과 같이 대상이 컬렉션 인 경우(m.orders)

 

* 경로 표현식의 특징

- 상태 필드 : 경로 탐색의 끝으로, 더 이상 탐색을 하지 못한다.

- 단일 값 연관 경로 : 묵시적 내부 조인(inner join) 이 발생하며, 탐색이 가능하다.

아래의 코드를 한 번 살펴보자.

- JpaMain.java

String query = "select m.team From Member m";
// m.team.name 과 같은 탐색이 가능하다.

List<Team> result = em.createQuery(query, Team.class)
	.getResultList();
for (Team s : result){
	System.out.println("s = " + s);
}

- Hibernate SQL

Hibernate: 
    /* select
        m.team 
    From
        Member m */ select
            team1_.id as id1_3_,
            team1_.name as name2_3_ 
        from
            Member member0_ 
        inner join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id

 

위의 Hibernate SQL 을 잘 보면 from 절에서 member 테이블과 team 테이블을 join 해서 team 데이터를 select 프로젝션에 나열한다.

객체 입장에서는 . 을 찍으면 탐색을 할 수 있지만, DB 입장에서는 join 을 활용해야 한다.

* 이렇게 발생하는 join 을 두고 묵시적 내부 조인이라고 한다.

 

편해보이지만 join 을 어느 타이밍에 쓰게 될지 명시적으로 보이질 않으므로 유지 보수 및 운영을 하기가 쉽지 않다.

나중에 가서 글을 작성하겠지만 되도록이면 묵시적으로 내부 조인이 발생하게끔 코드를 작성해서는 안된다.

실무에서는 적어도 수백개의 쿼리들이 서로 오고갈 텐데, 조인의 경우는 성능 관리에 있어서 꽤 지대한 영향을 끼치게 된다.

 

- 컬렉션 값 연관 경로 : 묵시적 내부 조인이 발생하나, 단일 값 연관 경로와는 달리 탐색을 할 수 없다.

컬렉션의 경우 내부에 들어있는 데이터가 1,2 개가 아니기 때문에 그 중에 뭘 선택해서 어떤 필드를 꺼내야 할지 가 난감해진다.

그래서 JPA 에서는 여기에 제약을 걸었다.

컬렉션 값 연관 경로는 묵시적 내부 조인을 발생하나, 더 이상의 탐색은 불가능하다.(중요한 특징)

 

아래의 코드를 살펴보자.

- JpaMain.java

// 경로 탐색 - 컬렉션 값 연관 경로(탐색 불가능)
String query = "select t.members FROM Team t";
// 여기서 members 는 Team 클래스에서 Member 클래스와 @OneToMany 연관관계로 선언된 컬렉션이다.

Collection result = em.createQuery(query, Collection.class)
	.getResultList(); // 컬렉션 반환 값 그 자체를 받아와야 한다.
for (Object o : result) {
	System.out.println("o = " + o);
}

- Hibernate SQL

Hibernate: 
    /* select
        t.members 
    FROM
        Team t */ select
            members1_.id as id1_0_,
            members1_.age as age2_0_,
            members1_.TEAM_ID as team_id5_0_,
            members1_.type as type3_0_,
            members1_.username as username4_0_ 
        from
            Team team0_ 
        inner join
            Member members1_ 
                on team0_.id=members1_.TEAM_ID

 

여기서 컬렉션에 값을 넣으면 다음과 같은 결과가 나오는 것을 확인할 수 있다.

- JpaMain.java

Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member = new Member();
member.setUsername("관리자1");
member.setTeam(team);
member.setAge(10);
em.persist(member);

Member member1 = new Member();
member1.setUsername("관리자2");
member1.setAge(20);
member1.setTeam(team);
em.persist(member1);

em.flush();
em.clear();

// 경로 탐색 - 컬렉션 값 연관 경로(탐색 불가능)
String query = "select t.members FROM Team t";
// 여기서 members 는 Team 클래스에서 Member 클래스와 @OneToMany 연관관계로 선언된 컬렉션이다.

Collection result = em.createQuery(query, Collection.class)
	.getResultList();
for (Object o : result) {
	System.out.println("o = " + o);
}

- Hibernate SQL

Hibernate: 
    /* select
        t.members 
    FROM
        Team t */ select
            members1_.id as id1_0_,
            members1_.age as age2_0_,
            members1_.TEAM_ID as team_id5_0_,
            members1_.type as type3_0_,
            members1_.username as username4_0_ 
        from
            Team team0_ 
        inner join
            Member members1_ 
                on team0_.id=members1_.TEAM_ID
o = Member{id=2, username='관리자1', age=10}
o = Member{id=3, username='관리자2', age=20}

 

그런데 만약 컬렉션 값 연관 경로를 사용하는데 데이터를 탐색하고 싶은 경우, 쿼리를 아래와 같이 FROM 절에서 명시적 내부 조인을 사용하여 별칭을 얻는 방식으로 작성하면, 해당 별칭을 통해 컬렉션 값 연관 경로에서도 탐색이 가능해진다.

String query = "select m.username from Team t join t.members m";

// 위와 같이 명시적 내부 조인으로 m 이라는 별칭을 얻으면 컬렉션 값 연관 경로에서도 필드 탐색이 가능해진다.

 

* 그런데 강사님이 추천하는 방식은 실무에서는 그냥 묵시적 내부 조인 같은거 쓰지 말고 명시적 내부 조인을 사용하는 것이다.

 

경로 탐색을 사용한 묵시적 조인 시 주의 사항

- 항상 내부 조인이 수행된다.(외부 조인이 수행되는 것이 아니다.)

- 컬렉션은 경로 탐색의 끝 이므로, 더 탐색을 하고자 한다면 명시적 조인을 통해 별칭을 얻어야 한다.

- 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만, 묵시적 조인으로 인해 SQL 의 FROM(JOIN) 절에 영향을 준다.

 

실무 조언?

- 가급적 묵시적 조인 대신 명시적 조인을 사용해라.

- 조인은 SQL 튜닝에 있어 중요한 포인트이다.

- 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다.

'JPA' 카테고리의 다른 글

JPQL - JPQL 페치 조인(2)  (0) 2020.11.28
JPQL - JPQL 페치 조인(1)  (0) 2020.11.28
JPQL - JPQL 기본(10)  (0) 2020.11.24
JPQL - JPQL 기본(9)  (0) 2020.11.24
JPQL - JPQL 기본(8)  (0) 2020.11.24