"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다."
www.inflearn.com/course/ORM-JPA-Basic#
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그
www.inflearn.com
-실무 에서 굉장히 중요한 개념이다.-
페치 조인(fetch join)
- SQL 조인의 종류가 아니라, JPQL 에서 성능 최적화를 위해 제공하는 기능이다.
- 연관된 Entity 나 컬렉션을 SQL 한 번에 함께 조회하는 기능으로, join fetch 명령어를 사용한다.
- 페치 조인 :: = [LEFT[OUTER] | INNER] JOIN FETCH 조인 경로
예시 :
SQL 한 번에 회원을 조회하면서 연관된 팀도 함께 조회하고 싶다면 어떻게 해야 할까?
- JPQL : select m from Member m join fetch m.team
- SQL : SELECT M.*, T.* FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID = T.ID
* SQL 을 보면 회원 뿐만 아니라 팀(T.*) 도 함께 SELECT 된다.
* 분명히 JPQL 에서는 select 프로젝션에 Member 객체 m 만 작성하였는데, 페치 조인 기능으로 인해 실제 수행되는 SQL 에서는 Member 데이터 뿐만 아니라, Team 데이터 까지 같이 조회를 하게 된다.
페치 조인 기능을 잘 활용하면 쿼리로 내가 원하는 대로 어떤 객체 그래프를 한 번에 조회할 것이라는 것을 명시적으로 동적인 타이밍에 정할 수 있게 된다.
아래와 같은 구조를 설계해보자.
위의 설계를 보면 inner 조인 이기 대문에 팀 데이터가 null 값인 회원4 는 조인 결과로 나타나지 않는다.
(Left 조인이라면 나오겠지만)
예제의 단순화를 위해 직접 작성하는 코드에는 팀C 와 회원4 는 제외한다.
일단 페치 조인 기능의 명확한 확인을 위해 우선 페치 조인 없이 아래와 같은 코드를 작성하여 실행해보자.
- JpaMain.java
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query = "select m From Member m";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member member : result){
System.out.println("member = " + member.getUsername() + ", " + member.getTeam().getName());
// 페치 조인 기능을 활용하지 않고 Member 객체와 연관관계를 맺고 있는 Team 객체 데이터를 호출하면 어떻게 될까?
}
- Hibernate SQL
Hibernate:
/* select
m
From
Member m */ select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.TEAM_ID as team_id5_0_,
member0_.type as type3_0_,
member0_.username as username4_0_
from
Member member0_
Hibernate:
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
member = 회원1, 팀A /* SQL */
member = 회원2, 팀A /* 1차 캐시 */
/*팀 A 데이터가 사용됨 으로서 팀A 에 대한 데이터가 SQL 로 검색되었다.*/
Hibernate:
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
member = 회원3, 팀B /* SQL */
/*팀 B 데이터가 사용됨 으로서 팀B 에 대한 데이터가 SQL 로 검색되었다.*/
위의 Hibernate SQL 을 보면 페치 조인 기능을 활용하지 않은 경우 각 팀 데이터를 호출해서 사용할 때 따로 SQL 쿼리를 생성하여 실행하는 것을 볼 수 있다.
왜 그런걸까?
Member - Team 클래스 간 연관관계 에서 Member 객체에서 Team 객체 데이터를 호출하여 사용하는 경우 필요치 않은 데이터에 대한 검색을 줄이기 위해 지연 로딩 방식으로 연관관계를 설정해놓고 있다.
- Member.java
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
그렇기 때문에 처음 JPQL 쿼리로 Member 객체가 select 되었을 때 지연 로딩 설정으로 인해 프록시 객체로 생성되어 있던 Team 객체 데이터가 for 문을 통한 출력문에서 사용되었을 때, 그제서야 해당 팀에 대한 데이터를 검색하는 SQL 쿼리를 추가로 생성하여 실행시킨 것이다.
즉, 페치 조인을 사용하지 않은 위의 코드는 회원 데이터를 검색하는데 있어 N + 1 문제를 야기 시킨다는 것을 알 수 있다.
여기서 만약 회원의 숫자가 100 명 단위로 많아진다면 문제의 심각성이 더욱 크게 느껴질 것이다.
팀이 다른 회원 데이터가 나올 때마다 매번 SQL 문을 새로 생성하여 호출해줘야 하니 정말 비효율적이라고 할 수 있다.
그렇다면 JPQL 에서 페치 조인 기능을 이용하여 Team 객체 데이터를 호출하게 되면 어떻게 될까?
- JpaMain.java
//String query = "select m From Member m";
String query = "select m from Member m join fetch m.team"; // join 하면서 한번에 fetch 로 데이터 들을 가져와라.
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member member : result){
System.out.println("member = " + member.getUsername() + ", " + member.getTeam().getName());
// 여기서 호출되는 Team 객체 데이터는 프록시 객체가 아니라 영속성 컨텍스트에 실제 데이터 들이 모두 올라와 있는 상태이다.
}
- Hibernate SQL
Hibernate:
/* select
m
from
Member m
join
fetch m.team */ select
member0_.id as id1_0_0_,
team1_.id as id1_3_1_,
member0_.age as age2_0_0_,
member0_.TEAM_ID as team_id5_0_0_,
member0_.type as type3_0_0_,
member0_.username as username4_0_0_,
team1_.name as name2_3_1_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.id
member = 회원1, 팀A
member = 회원2, 팀A
member = 회원3, 팀B
/*단 한번의 SQL 실행으로 서로 팀이 다른 Member 데이터까지 모두 검색되었다.*/
위의 Hibernate SQL 을 보면 join fetch 명령어를 통해 페치 조인 기능을 사용한 결과, 단 한 번의 SQL 쿼리 만으로 각 Member 와 연관된 Team 데이터까지 모두 가져온 것을 알 수 있다.
다음번 글에서는 컬렉션 페치 조인과 조심해야 할 점에 대해 알아보자.
'JPA' 카테고리의 다른 글
JPQL - JPQL 페치 조인(3) (0) | 2020.11.28 |
---|---|
JPQL - JPQL 페치 조인(2) (0) | 2020.11.28 |
JPQL - 경로 표현식 (0) | 2020.11.28 |
JPQL - JPQL 기본(10) (0) | 2020.11.24 |
JPQL - JPQL 기본(9) (0) | 2020.11.24 |