<aside> ❗
SELECT m.username -> 상태필드
FROM Member m
JOIN m.team t -> 단일 값 연관 필드
JOIN m.orders o -> 컬렉션 값 연관필드
WHERE t.name = '팀A'
</aside>
<aside> ❗
3가지 경로 표현식 존재 - 상태필드, 단일 값 연관 필드, 컬렉션 값 연관 필드
→ 내부적으로 동작하는 방식이 달라 결과가 달라짐
상태필드
- 단순히 값을 저장하기 위한 필드
→ 상태 필드로 객체 그래프 탐색
연관필드
- 연관관계를 위한 필드
단일 값 연관 필드
: @ManyToOne, @OneToOne 대상이 엔티티 // m.tead
타겟 대상이 엔티티일 때
컬렉션 값 연관 필드
: @OneToMany, @ManyToMany 대상이 컬렉션 // m.orders
타겟 대상이 컬렉션일 때
</aside><aside> ❗
상태 필드
- 경로 탐색의 끝, 탐색 X// m.username은 상태 필드
// 경로 탐색의 끝
String query = "SELECT m.username FROM Member m";
단일 값 연관 필드
- 묵시적 내부 조인(INNER JOIN) 발생
, 탐색 O
→ JOIN 쿼리가 실행 됨// 단일 값 연관 필드
// 객체 그래프 탐색 가능
String query = "SELECT m.team FROM Member m";
**// Member와 Team을 조회해서 Team 정보를 가져온다.**
// 상태필드가 되어 경로 탐색 끝
String query = "SELECT m.team.name FROM Member m";
컬렉션 값 연관 필드
- 묵시적 내부 조인 발생
, 탐색 X// t.members 이후 탐색 불가
// t.members.size는 가능
String query = "SELECT t.members FROM Team t";
// 명시적 조인을 사용해야 함
// 명시적 조인을 사용하면 별칭을 얻을 수 있다.
**// 별칭을 가지고 탐색 가능**
String query = "SELECT m FROM Team t JOIN t.members m";
**** 묵시적 내부 조인이 발생하게 쿼리를 작성하면 안 된다.
****
→ 성능에 영향을 주고 쿼리 튜닝하기도 어렵다. (운영하기 어려움)
→ JPQL과 SQL을 최대한 맞춰서 짠다.
→ 실무에서는 묵시적 조인을 절대 사용하지 말고 **명시적 조인을 사용해라
→ 쿼리 튜닝하기도 쉬움
**
</aside>
<aside> ❗
<aside> ❗
<aside> ❗
명시적 조인
- JOIN 키워드 직접 사용
외부조인을 사용하고 싶으면 명시적 조인을 사용하면 된다.
묵시적 조인
- 경로 표현식에 묵시적으로 SQL 조인 발생 (내부 조인만 가능)
<aside> ❗
명시적 조인을 통해 별칭을 얻어야 탐색 가능
묵시적 조인으로 인해 SQL의 FROM(JOIN) 절에 영향을 줌
→ 묵시적 조인으로 인해 FROM절에 영향을 주게 된다. 이 때,
JPA가 묵시적 조인을 작성하므로 예측하기 어려워진다. 그래서 성능 최적화가 어려울 수 있다.
</aside><aside> ❗
가급적 묵시적 조인 대신에 **명시적 조인 사용**
조인이 일어나는 상황을 한눈에 파악하기 어려움
</aside><aside> ❗
성능 최적화
를 위해 제공하는 기능연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회
**하는 기능
→ 쿼리 두 번 나가는 것을 하나로 조회<aside> ❗
원하는 객체 그래프를 한 번에 조회할 것인지 명시적으로 동적인 타이밍에 정할 수 있음
회원을 조회하면서 연관된 팀도 함께 조회
(SQL 한 번에)[JPQL]
// m만 조회했는데, m과 t의 데이터를 다 가져온다.
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
// 최악의 경우 N + 1 번 쿼리를 보냄
// 회원들 조회 (DB에서 회원들 정보 조회) 1
// 회원1 (DB에서 팀 A조회)
// 회원2 (1차 캐시에서 팀 A조회) 2
// 회원3 (DB에서 팀 B조회) 3
"SELECT m FROM Member m";
[N+1]
// Member를 조회하는데, 연관된 팀을 한 번에 조인해서 가져옴
String jpql = "SELECT m FROM Member m JOIN FETCH m.team";
List<Member> members = em.createQuery(jpql, Member.class)
.getResultList();
// 패치 조인을 사용하면 지연로딩을 걸어도 프록시 클래스가 아님
// **영속성 컨텍스트에 실제 엔티티가 올라가 있음 (프록시 X), 즉 실제 데이터가 다 들어가 있음**
for(Member member : members){
System.out.println("username = " + member.getUsername() + ", " +
"testName = " + member.getTeam().name());
}
/*
select
m1_0.MEMBER_ID,
m1_0.city,
m1_0.street,
m1_0.zipcode,
m1_0.age,
t1_0.TEAM_ID,
t1_0.name,
m1_0.type,
m1_0.username
from
Member m1_0
join
Team t1_0
on t1_0.TEAM_ID=m1_0.TEAM_ID
*/
/*
username = 회원1, teamname = 팀A
username = 회원2, teamname = 팀A
username = 회원3, teamname = 팀B
*/
[참고]
지연로딩을 설정해도 Fetch 조인이 항상 우선
</aside>