fetch join
매우 중요
실무에서 엄청 많이 씀
SQL 조인 종류가 아니고,
JPQL에서 성능 최적화를 위해 제공하는 기능.
연관된 엔티티나 컬렉션을 SQL로 한번에 한방 쿼리로 조회하는 기능.
join fetch 조인할대상
select m from Member m join fetch m.team t
이렇게 쓴다.
그냥 join 뒤에 fetch 추가 시킨거다.
이렇게 하면 쿼리가 어떻게 나가냐면,
Hibernate:
/* select
m
from
Member m
join
fetch m.team t */ select
m1_0.id,
m1_0.age,
t1_0.id,
t1_0.name,
m1_0.type,
m1_0.username
from
Member m1_0
join
Team t1_0
on t1_0.id=m1_0.team_id
이렇게 가져온다.
보면 t1_0.id 하면서 team도 다 가져온다.
select m from Member m join m.team t
그냥 join은
Hibernate:
/* select
m
from
Member m
join
m.team t */ select
m1_0.id,
m1_0.age,
m1_0.team_id,
m1_0.type,
m1_0.username
from
Member m1_0
join
Team t1_0
on t1_0.id=m1_0.team_id
Hibernate:
select
t1_0.id,
t1_0.name
from
Team t1_0
where
t1_0.id=?
저렇게 우선 member만 가지고 왔다가,
그 다음 팀을 따로 가져온다.
join fetch는
m.* t.* 이렇게 가져온다고 보면 된다.
이 케이스
Team team = new Team();
team.setName("teamA");
em.persist(team);
Team team2 = new Team();
team2.setName("teamB");
em.persist(team2);
Member member =new Member();
member.setUsername("관리자");
member.setAge(10);
member.changeTeam(team);
member.setType(MemberType.ADMIN);
em.persist(member);
Member member1 =new Member();
member1.setUsername("유저1");
member1.setAge(10);
member1.changeTeam(team);
member1.setType(MemberType.USER);
em.persist(member1);
Member member2 =new Member();
member2.setUsername("유저2");
member2.setAge(14);
member2.changeTeam(team2);
member2.setType(MemberType.USER);
em.persist(member2);
Member member3 =new Member();
member3.setUsername("유저2");
member3.setAge(17);
member3.setType(MemberType.USER);
em.persist(member3);
em.flush();
em.clear();
List<Member> result1 = em.createQuery("select m from Member m join fetch m.team t", Member.class)
.getResultList();
for (Member s : result1) {
System.out.println("s = " + s.getTeam().getName());
}
Hibernate:
/* select
m
from
Member m
join
fetch m.team t */ select
m1_0.id,
m1_0.age,
t1_0.id,
t1_0.name,
m1_0.type,
m1_0.username
from
Member m1_0
join
Team t1_0
on t1_0.id=m1_0.team_id
조회쿼리 이거 하나만 날라감.
참고로 이 때 team 객체는 프록시가 아님. 이미 t1_0.id.... 등등 하면서 다 가져와서 프록시로 할 필요가 없음.
저기서 만약
select m from Member m join m.team t
fetch를 뺀다면
Hibernate:
/* select
m
from
Member m
join
m.team t */ select
m1_0.id,
m1_0.age,
m1_0.team_id,
m1_0.type,
m1_0.username
from
Member m1_0
join
Team t1_0
on t1_0.id=m1_0.team_id
Hibernate:
select
t1_0.id,
t1_0.name
from
Team t1_0
where
t1_0.id=?
Hibernate:
select
t1_0.id,
t1_0.name
from
Team t1_0
where
t1_0.id=?
쿼리가 3개 나감.
3개 뿐이 아닐거임.
만약 팀 종류가 10종류면 저 team만 가져오는 select 쿼리가 10개 나갈거임.
여기까지가 @ManyToOne 패치 조인이고,
다음 컬렉션 패치 조인
일대다 관계
select t from Team t join fetch t.members
이렇게 하면
Hibernate:
/* select
t
from
Team t
join
fetch t.members */ select
t1_0.id,
m1_0.team_id,
m1_0.id,
m1_0.age,
m1_0.type,
m1_0.username,
t1_0.name
from
Team t1_0
join
Member m1_0
on t1_0.id=m1_0.team_id
이렇게 나감.
select t.*, m.* from Team t inner join member on t.id = member.team_id
이거랑 같음.
일대다 조인은 문제가 있을 수 있음.
team에서 팀A인 멤버만 join해서 가져오는데,
팀A인 member가 2개 있으면 db는 2 row를 두개 다 줌
result.get(0).getName = "팀A"
result.get(1).getName = "팀A"
같이 갯수를 맞춰야 하기 때문인 듯.. db는 결국 나온 row 수 만큼 돌려줘야 하니.. 또 그렇게 얻고 싶은 사람이 있을 수도.. 비효율 적이지만
뭐 그렇다고 메모리 상에서도 팀A 객체를 2개 생성하는게 아니라,
영속성 컨텍스트에 등록되어 공유됨.
맨왼쪽이 DB가 준 거 인듯.
여튼 일대다 조인은 이렇게 데이터가 뻥튀기 되는 문제가 있을 수 있음.
이 중복을 제거하는 방법이 있긴 있음.
distinct
근데 SQL의 distinct만으로는 모든 중복을 제거할 수 없다.
SQL의 distinct는 완전히 똑같아야 distinct가 적용된다.
select distinct t from Team t join fetch t.members
이렇게 할 경우 team 자체에 distinct를 하는 거라 다 같아야 한다.
그래서 JPQL의 distinct는 2가지 기능을 제공한다.
1. SQL에 distinct 추가
2. 애플리케이션에서 엔티티 중복 제거
그러니까 일단 distinct로 해서 값을 DB로부터 받아오고,
그럼에도 똑같은 엔티티가 있으면 제거해 줌.
근데.. 하이버네이트 6 버전부터는 패치되서 distinct 명령을 사용하지 않아도 알아서 중복 제거가 됨.
select t from Team t join fetch t.members
그냥 이렇게 해도 데이터 뻥튀기가 되지 않음.
만약 하이버네이트 6 미만이라면
select distinct t from Team t join fetch t.members
이렇게 하면 됨.
하이버네이트6부터는 반환되는 항목의 중복항목은 이제 항상 하이버네이트에 의해 필터링 된다고 함.
여튼 이렇게 같은 식별자를 가진 엔티티를 제거한다고 한다.
패치 조인 일반조인 차이
패치조인 : 연관된 엔티티까지 select 문에 포함해서 다 퍼올림.
JPQL은 결과를 반환할 때 연관관계를 고려 안함. 그냥 Select에 있었던거 다 가져오는 거임.
사실 상 즉시로딩
한방쿼리로 싹 묶어서 가져오는 가져온다. 그 후 알아서 엔티티로 변환
조인 : 그냥 SQL문에 join 추가시켜주는거고, select한 것만 가져옴.
나중에 join 시킨 거 값 가져와서 쓸 때 그때 지연로딩 발동해서 또 select 쿼리문 날려서 가져옴.
'JPA > JPA 기본' 카테고리의 다른 글
48-2. 패치 조인의 한계 (0) | 2023.11.02 |
---|---|
48. 페치 조인 한계 (0) | 2023.11.01 |
46. 경로 표현식 (0) | 2023.11.01 |
45. JPQL 함수 (0) | 2023.10.31 |
44. 조건식. case 등등 (0) | 2023.10.31 |