fetch 조인
페치 조인은 JPQL에서 성능 최적화를 위해 제공하는 기능으로 sql에서 지원하는 조인 종류가 아니다.
연관된 엔티티나 컬렉션을 sql 한번에 함께 조회하는 기능으로 사실상 즉시로딩의 기능과 같기 때문에 도메인 설계 단계에서 대부분 지연로딩을 사용하는 실무에서 자주 사용한다.
→ 도메인 설계시 대부분의 연관관계를 fetchType = Lazy 로 설정 후(비즈니스 로직에 따라 다름) 조회 쿼리시 'fetch join' 사용
→ n+1 문제 해결
*예시
[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
→ 회원을 조회하면서 연관된 팀도 함께 조회가능 (sql 한번 발생)
-컬렉션 페치 조인 (oneToMany 관계)
[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';
결과를 반복문으로 돌리면 2번 반복 → 레코드 2개 발생
for(Team team : result){
System.out.println(team);
}
--> 출력 (똑같은 엔티티 두번 출력)
Team(Id : 1, name : '팀A', memberList[{id:1, name:'회원1'},{id:2, name:'회원2'}]
Team(Id : 1, name : '팀A', memberList[{id:1, name:'회원1'},{id:2, name:'회원2'}]
똑같은 엔티티 두번 출력 → 해결 방법 DISTINCT 사용!
SQL : sql에서의 distinct는 중복된 결과를 제거하는 명령
JQPL : 하지만 jqpl에서는 2가지 기능을 제공
- SQL에 DISTINCT를 추가
- 애플리케이션에서 엔티티 중복 제거
레코드 결과가 완벽히 동일하지 않기 때문에 단순히 sql에 distinct를 추가한다고해서 중복이 해결되지는 않는다.
→ 때문에 JPQL에서는 추가로 애플리케이션에서 같은 식별자를 가진 Team 엔티티를 제거한다.
for( Team team : result){
System.out.println(team);
}
--> 출력 (중복 엔티티를 제거하여 출력)
Team(Id : 1, name : '팀A', memberList[{id:1, name:'회원1'},{id:2, name:'회원2'}]
- fetch 조인의 특징과 한계
1. 페지 조인 대상에는 가급적 별칭을 사용하지 말자.
→ 별칭을 주어 where, on 절에서 필터링하여 컬렉션을 가져오면 후에 값이 바뀔 시 정합성에 문제 발생, 엔티티 그래프 사상에 어긋남.
2. 둘 이상의 컬렉션은 페치 조인을 할 수 없다.
3. 컬렉션 페치 조인의 경우 페이징 API를 사용할 수 없다.
→ 일대일, 다대일(xToOne) 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능하지만 컬렉션 페치 조인의 경우 결과값이 중복되어 레코드들을 반환하기 때문에 페이징을 할 수 없다.
-해결방법
1) from 절을 바꾸어 단일 페치 조인을 사용
2) @BatchSize 사용 (1. 어노테이션, 2. 글로벌 - properties)
3) 일반 조인을 사용하여 필요한 데이터들만 조회해서 DTO로 반환 → repository단에서 DTO 결과를 받음
4. DISTINCT 사용은? → 페이징 처리 안됨 - jpa의 distinct는 같은 식별자를 가진 엔티티를 한개로 출력해주지만 이 작업은 데이터베이스에서 받은 후 JPA단에서 처리된다. 하지만 페이징 처리는 데이터베이스단에서 처리 후 JPA로 데이터를 넘겨주기 때문에 JPA의 distinct가 아닌 순수 SQL disticnt 로 처리한 후 페이징처리를 하기 때문에 뻥튀기가 된 데이터를 토대로 페이징처리를 하게되어 문제가 발생한다.