select m.username -> 상태 필드
from Member m
join m.team t -> 단일 값 연관 필드
join m.orders o -> 컬렉션 값 연관 필드
where t.name ='팀A'
경로 표현식 용어 정리
경로 표현식 특징
select m.team.name from Member m; //team에서 경로탐색이 더 가능하다(name)
select m.username from Team t join t.members m;
묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어려우므로 명시적 조인을 사용하는것을 권장
엔티티 페치 조인
//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;
팀이 있는 회원을 조회하고 싶을 때 fetch join을 사용하면 내부적으로 inner join을 사용한다.
팀이 없는 회원은 누락된다.
페치 조인 사용 코드
String jpql = "select m from Member m";
List<Member> members = em.createQuery(jpql, Member.class)
.getResultList();
for (Member member : members) {
System.out.println("username = " + member.getUsername() + ", " +
"teamName = " + member.getTeam().name());
//회원1, 팀A(SQL)
//회원2, 팀A(1차 캐시)
//회원3, 팀B(SQL)
//회원 100명 -> N + 1
}
: 최초 jpql을 통해 Member를 조회해 올때 Team의 정보는 Proxy객체로 가지고 있다. (실제론 없다는 의미)
그렇기에 실제로 getTeam().getName()을 통해 팀의 정보를 조회하려고 할 때 SQL을 수행한다.
주석 내용대로 한번 가져온 Team의 정보는 1차 캐시에 올라가 있기 때문에 더 조회할 필요는 없지만,
회원을 N명 조회하게 되었을때 최대 N + 1 번 Team 조회 쿼리가 수행 될 수 있다.
String jpql = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(jpql, Member.class)
.getResultList();
for (Member member : members) {
//페치 조인으로 회원과 팀을 함께 조회해서 지연 로딩X
System.out.println("username = " + member.getUsername() + ", " +
"teamName = " + member.getTeam().name());
}
페치조인은 조회 당시에 실제 엔티티가 담긴다. 그렇기 때문에 지연로딩 없이 바로 사용이 가능하다.
컬렉션 페치 조인
//JPQL
select t from Team t join fetch t.members where t.name = '팀A';
//SQL
select t.*, m.* from team t, inner join member m on t.id = m.team_id
where t.name = '팀A';
이를 수행하면 Team은 하나지만 Member가 1개 이상일 수 있다.
팀A는 1개이지만 그에 해당하는 멤버는 회원1과 회원2로 두개이기 때문에, 조회 결과는
위 표처럼 2개의 row가 된다.
팀은 하나이기에 같은 주소값을 가진 결과가 두개가 나오고 팀A의 입장에서는 회원1, 회원2를 가진다.
String jpql = "select t from Team t join fetch t.members where t.name = '팀A'"
List<Team> teams = em.createQuery(jpql, Team.class).getResultList();
for (Team team : teams) {
System.out.println("teamname = " + team.getName() + ", team = " + team);
for (Member member : team.getMembers()) {
//페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println("->username = " + member.getUsername()+ ", member = " + member);
}
}
페치 조인과 DISTINCT
일대다(1:N) 관계에서는 join fetch 결과가 뻥튀기 될 수 있다.
JPQL의 DISTINCT로 해결가능
반대로 다대일(N:1)은 뻥튀기 되지 않는다.
페치 조인과 일반 조인의 차이
일반 조인 실행시 연관된 엔티티를 함께 조회하지 않음.
//JPQL
select from Team t join t.members m where t.name = '팀A';
//SQL
select t.* from Team t inner join member m on t.id = m.team_id
where t.name = '팀A';
해당 쿼리 수행시 일반 조인은 연관된 엔티티를 먼저 조회하지 않기 때문에 프록시 객체 반환.
실제로 해당 엔티티를 사용할 때 실제 값을 조회한다.
반면, 페치 조인은 조회시 연관관계도 같이 조회(즉시 로딩)
페치 조인은 객체 그래프를 SQL 한 번에 조회하는 개념
페치 조인의 특징과 한계
페치 조인 대상에는 별칭을 줄 수 없다
둘 이상의 컬렉션은 페치 조인 할 수 없다.
String query = "select t from Team t join fetch t.members, t.orders"
//불가능
String query = "select m from Member m join fetch m.team t";
public class Team{
...
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members;
...
}
String query = "select t from Team t";
hibernate.default_batch_fetch_size=100
페치 조인 - 정리
모든 것을 페치 조인으로 해결할 수는 없다.
페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.
여러 테이블을 조인해서 엔티티가 아닌 전혀 다른 결과를 내야 하면, 페치 조인 보다는 일반 조인을 사용하고
필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적
출처:https://catsbi.oopy.io/137e5070-40d5-4799-a283-a747fb7a0f2d#b3974d25-fa6d-4da0-adb4-356f018fbebd