- 페치 조인(fetch join)
1. SQL 조인 종류가 아니다.
2. JPQL에서 성능 최적화를 위해 제공하는 기능이다.
3. 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능이다.
- 엔티티 페치 조인
1. Member 엔티티를 조회하면서 연관된 팀 Team 엔티티도 함께 조회가 가능하다. (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
- 현재 위와 같이 회원 2명이 팀 하나에 같이 소속되어 있는 상황이다.
- 회원 1,2,3과 팀 A,B를 조인하면 위와 같이 3개의 튜플이 생성된다.
- 기존의 조인을 확인해보면 Team의 경우 LAZY 즉, 지연로딩으로 설정되어 있기 떄문에 Team 조회가 필요하기 전까지 쿼리가 실행되지 않고 프록시만 생성되어 있는 상태다.
* (flush와 clear를 통해 DB에 값은 저장하였지만 영속성 컨텍스트는 비워둔 상태이다.)
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 정보를 가져올 때는 지연 로딩에 의해 member가 먼저 select 된 후 team을 select 하는 것을 볼 수 있다.
- 또한, 영속 엔티티에 없는 또 다른 team 엔티티가 조회 될 경우 조회하는 시점에 team 엔티티가 select 쿼리를 보내는 것을 확인 할 수 있다.
- fetch 조인 사용
String query = "select m From Member m join fetch m.team";
// 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());
}
- fetch join을 사용하면 호출 시점에 Team 엔티티를 조인하여 함께 조회한다.
- 한번에 3개의 튜플이 함께 조회된다.
String query = "select t From Team t";
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName() + "| members = " + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member);
}
}
- JPQL에서 Team을 조회했기 때문에 select로 team을 우선 조회한다.
for (Team team : result) {
System.out.println("team = " + team.getName() + "| members = " + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member);
}
1. 현재 팀 A,B가 영속성 컨텍스트에 저장되어 있는 상태이다.
2. for문에 의해 첫번째 팀 A에 대한 이름과 크기를 가져온다. 팀 A에는 회원 1,2 두명이 있기 때문에 List의 크기 2가 반환된다.
3. 중첩 for문을 확인해보면 Team A에 대한 member 객체를 조회하는 것이기 때문에 팀 A에 속한 member에 대한 정보만 표시한다.
1. 마찬가지로 select문이 또 실행된 이유는 현재 현재 팀 B에 속한 member에 대한 엔티티 정보가 없기 때문에 또 한 번 호출된 것이다.
- 컬렉션 페치 조인
String query = "select t From Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName() + "| members = " + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member);
}
}
- 다음은 Team 엔티티에 속한 members 컬렉션을 fetch join 하는 코드이다.
- fetch join의 경우 Team 엔티티를 조회함과 동시에 Member 엔티티도 join으로 함께 조회하기 때문에 한번에 결과값이 전부 출력되는 것을 확인 할 수 있다.
String query = "select t From Team t join t.members";
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName() + "| members = " + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member);
}
}
- 일반 조인의 경우 select가 데이터가 필요한 member에 따라 계속해서 select를 호출하는 것을 확인 할 수 있다.
- 위에서 1:N 일대다 조인의 데이터를 조회했을 때 같은 데이터가 2번 나오는 것을 확인 할 수 있다.
- JPA는 DB에서 값을 가져오기 전까지는 DB에 어떤 데이터가 들어있는지 알 수 없기 때문에 쿼리를 날려 DB가 보내주는 정보를 그대로 사용할 수 밖에 없다.
- 현재 팀 A에는 2명의 회원이 존재하기 때문에 join을 하게되면 최종적으로 두 줄의 데이터가 생성되고 해당 데이터를 DB로 부터 받는다.
- 결과적으로 위와 같이 JOIN의 결과로 팀A의 회원 2명인 두 줄의 데이터가 생성된다.
String query = "select t From Team t join fetch t.members";
System.out.println("===========================================111");
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
System.out.println("===========================================222");
for (Team team : result) {
System.out.println("===========================================333");
System.out.println("team = " + team.getName() + "| members = " + team.getMembers().size());
System.out.println("===========================================444");
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member);
}
}
1. 위의 조인한 결과로 두 줄의 데이터가 나온 것을 기반으로 첫번재 줄의 팀A를 호출 하였을 때 영속 컨텍스트에 팀A에 대한 데이터가 저장된다.
2. 두번째 줄 팀A를 조회할 때는 이미 영속 컨텍스트에 같은 주소값의 데이터가 있기 때문에 별도로 가져오지 않고, 같은 주소값을 사용하여 팀A를 조회한다.
3. for문에서 result의 값을 가져올 때 DB로부터 가져온 테이블의 행은 2개기 때문에 같은 값이더라도 2번을 호출해야 한다.
4. 결과적으로 같은 팀A라도 회원 1,2 중 어떤 값을 가져오기 위한 것인지 JPA는 알지 못하기 때문에 같은 결과 값을 두번 호출하게 된다.
출처 : 인프런 - 김영한 (실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화)
'Programming > JPA' 카테고리의 다른 글
JPA - 엔티티 직접 사용 (0) | 2023.07.19 |
---|---|
JPA - 페치 조인 (2) (0) | 2023.07.19 |
JPA - 경로 표현식 (0) | 2023.07.18 |
JPA - 기본 문법 (조건식(CASE 등등), JPQL 함수) (0) | 2023.07.18 |
JPA - 기본 문법 (조인, 서브쿼리, 타입 표현과 기타식) (0) | 2023.07.18 |