JPA
[JPA] 26. Fetch 조인 - 기본
inuma
2021. 2. 11. 17:41
프로젝트 git url | github.com/rtef23/JpaStudy |
필요 | 1. docker 2. docker-compose |
프로젝트 정보 | mysql : 5.7 - docker image java : 1.8 |
Fetch Join
- SQL 조인 종류가 아니다
- JPQL에서 성능 최적화를 위해 제공하는 기능
- 연관된 엔티티나 컬렉션을 쿼리 한번에 함께 조회하는 기능(즉시 로딩과 유사)
- join fetch 명령어 사용
- ::= [INNER | LEFT (OUTER) | RIGHT (OUTER)] JOIN FETCH {조인 경로}
//[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
List<Team> teams = Arrays.asList(
new Team("team-name1"),
new Team("team-name2"),
new Team("team-name3")
);
teams.forEach(entityManager::persist);
IntStream.rangeClosed(0, 9)
.forEach(
(number) -> {
Member member = new Member("name-" + number, number);
Team selectedTeam = teams.get((int) (Math.random() * teams.size()));
member.changeTeam(selectedTeam);
entityManager.persist(member);
});
entityManager.flush();
entityManager.clear();
String jpqlQuery = "select m from Member m";
List<Member> results = entityManager.createQuery(jpqlQuery, Member.class).getResultList();
for(Member member : results){
System.out.println(member.getUsername() + ",\n\t" + member.getTeam().getName());
}
- Member 엔티티의 갯수만큼 Team을 조회하는 쿼리가 실행되게 된다.
(N + 1 문제 발생)
(영속성 컨텍스트에서 관리하고 있는 Team의 경우, 영속성 컨텍스트에서 관리하는 Team 엔티티를 반환한다.)
String jpqlQuery = "select m from Member m join fetch m.team";
List<Member> results = entityManager.createQuery(jpqlQuery, Member.class).getResultList();
for(Member member : results){
System.out.println(member.getUsername() + ",\n\t" + member.getTeam().getName());
}
- 최초에 Member를 조회할 때부터 Team을 같이 조회하기 때문에 Team을 조회하는 추가 쿼리가 발생하지 않는다.
(이 시점에서 Team은 프록시 객체가 아닌 실제 엔티티가 들어가게 된다.)
컬렉션 페치 조인
//[JPQL]
SELECT t
FROM Team t
JOIN FETCH t.members
//[SQL]
SELECT t.*, m.*
FROM Team t
INNER JOIN Member m
ON t.id = m.team_id
List<Team> teams = entityManager.createQuery("select t from Team t join fetch t.members", Team.class).getResultList();
teams.forEach((team) -> {
System.out("team name : " + team.getName() + "\tsize : " + team.getMembers().size());
});
/*
//일대다의 관계에서, team-1에 속한 회원이 2명이고, team-2에 속한 회원이 1명의 경우 result
| member_id | team_id |
========================
| member-1 | team-1 |
| member-2 | team-1 |
| member-3 | team-2 |
//Java 출력
//이 때, team-1인 엔티티가 2개의 row로 출력이 되는데 2개의 엔티티는 동일한 엔티티 객체이다.(Member 엔티티도 동일하다.)
team name : team-1 size : 2
team name : team-1 size : 2
team name : team-2 size : 1
*/
페치 조인과 DISTINCT
- JPQL의 DISTINCT는 2가지 역할을 수행한다.
- SQL에 DISTINCT 키워드 추가
- 애플리케이션에서 중복 제거
//[JPQL]
SELECT DISTINCT t
FROM Team t
JOIN FETCH t.members
//[SQL]
SELECT distinct
, t...
, m...
FROM Team t
INNER JOIN Member m
ON t.id = m.team_id
/*
SQL에서의 distinct는 row가 완전히 동일한 경우에만 줄여지게 된다.
| team_id | member_id |
=======================
| team-1 | member-1 |
| team-1 | member-2 |
| team-2 | member-3 |
이를 해결하기 위해서 JPA에서는 같은 식별자를 가진 엔티티를 제거하는 추가 작업을 수행한다.
[Team(id : team-1, members : [Member(id : member-1), Member(id : member-2)]),
Team(id : team-2, members : [Member(id : member-3)])]
*/
일반 조인과 페치 조인의 차이
- 일반조인의 경우, 실행시 연관된 엔티티를 조회하지 않는다.
- 페치조인을 사용하는 경우에만, 연관된 엔티티도 함께 조회한다.
- 객체 그래프를 쿼리 한번에 조회하는 개념
//일반 조인 예시
//[JPQL]
SELECT t
FROM Team t
INNER JOIN t.members
//[SQL]
SELECT t.*
FROM Team t
INNER JOIN Member m
ON t.id = m.team_id
//페치 조인 예시
//[JPQL]
SELECT t
FROM Team t
JOIN FETCH t.members
//[SQL]
SELECT t.*, m.* //연관된 엔티티 조회
FROM Team t
INNER JOIN Member m
ON t.id = m.team_id