내가 공부한 것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼 겸 상세히 기록하고 얕은 부분들은 가볍게 포스팅하겠습니다.
8. 기본 Join
조인의 기본 문법은 첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭(alias)으로 사용할 Q 타입을 지정하면 된다.
join(조인 대상, 별칭으로 사용할 Q타입)
테스트 코드를 통해서 Join을 알아보자.
@DisplayName("팀 A에 소속된 모든 회원 찾기")
@Test
public void join_test() {
List<Member> result = queryFactory
.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(result).extracting("username")
.containsExactly("member1", "member2");
}
위와 같이 join절에 join 대상과 별칭을 넘겨주면 된다.
참고로 별칭에 있는 team은 QTeam.team이다. static import 되어있는 것 이다.
생성된 SQL문은 다음과 같다.
select
member0_.member_id as member_i1_1_,
member0_.age as age2_1_,
member0_.team_id as team_id4_1_,
member0_.username as username3_1_
from member member0_
inner join team team1_
on member0_.team_id=team1_.team_id
where team1_.name=?
정상적으로 inner join이 진행되었다.
- join() , innerJoin() : 내부 조인(inner join)
- leftJoin() : left 외부 조인(left outer join)
- rightJoin() : rigth 외부 조인(rigth outer join)
- JPQL의 on과 성능 최적화를 위한 fetch 조인 제공
또한 연관관계가 없는 Entity 간에 join 또한 가능하다. 이를 세타조인이라 부른다.
(다만 이름이 왜 세타조인 인지 모르겠다? 내가 알고있는 세타조인과 의미가 다르다.
다음 기능을 사실상 카티션곱 을 한후 where로 걸러낸것 아닌가?)
우선 이름이 teamA, teamB, teamC 인 회원 3명을 추가 등록하자. (이름이 약간 억지가...)
/**
* 회원의 이름이 팀 이름과 같은 회원을 조회
*/
@Test
public void theta_join() {
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
em.persist(new Member("teamC"));
List<Member> result = queryFactory
.select(member)
.from(member, team)
.where(member.username.eq(team.name))
.fetch();
assertThat(result).extracting("username")
.containsExactly("teamA", "teamB");
}
from 절에 여러 엔티티를 선택해서 세타 조인이 가능하다. (원래 SQL에서도 from 절에 여러 테이블을 명시해주면 카티션곱을 해준다)
또한 연관관계가 없기 때문에 from(member, member.team) 이 아니라, from(member, team) 이다!
여기까지 하면 member 와 team 간의 카티션곱이 완료되었다. 이후 where 절을 통해 필터링을 하게 된다.
참고로, 외부조인(left join, right join)은 불가능하다. 다음에 설명할 조인on을 사용하면 외부조인이 가능해진다.
9. 조인 - on절
보통 on절은 다음 2가지 기능으로 사용한다.
1. 조인 대상 필터링
2. 연관관계 없는 엔티티 외부 조인
9-1) 조인 대상 필터링
/**
* 회원과 팀을 조인하되, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
* JPQL : select m, t from Member m join m.team t on t.name = 'teamA'
*/
@Test
public void join_on_filtering_test() {
List<Tuple> tuples = queryFactory
.select(member, team)
.from(member)
.leftJoin(member.team, team).on(team.name.eq("teamA"))
.fetch();
for (Tuple tuple : tuples) {
System.out.println("tuple = " + tuple);
}
}
실행되는 SQL은 다음과 같다.
select
member0_.member_id as member_i1_1_0_,
team1_.team_id as team_id1_2_1_,
member0_.age as age2_1_0_,
member0_.team_id as team_id4_1_0_,
member0_.username as username3_1_0_,
team1_.name as name2_2_1_
from member member0_
left outer join team team1_
on member0_.team_id=team1_.team_id and (team1_.name=?)
쿼리를 보면 마지막에 and로 우리의 조건인 name 이 나오게 되었다.
and 앞의 id 비교는 leftJoin(member.team , ...) 덕분에 나오게 되는 부분이다.
실행 결과는 다음과 같다.
기대했던 결과대로 나오는것을 확인할 수 있다.
on 절을 활용해 조인 대상을(여기서는 team) 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링 하는 것과 기능이 동일하다.
.join(member.team, team)
.on(team.name.eq("teamA"))
위 아래가 동일한 결과를
.join(member.team, team)
.where(team.name.eq("teamA"))
따라서 on 절을 활용한 조인 대상 필터링을 사용할 때, inner join 이면 익숙한 where 절로 해결하고, outer join인 경우에만 on절을 활용하자.
9-2) 연관관계가 없는 Entity 간의 Outer Join
하이버네이트 5.1부터 on 을 사용해서 서로 연관관계가 없는 필드로 외부 조인하는 기능이 추가되었다. 물론 내부 조인도 가능하다.
/**
* 연관관계가 없는 엔티티 끼리 외부 조인
* 회원의 이름이 팀 이름과 같은 대상을 outer join
*/
@Test
public void join_on_no_relation() {
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
em.persist(new Member("teamC"));
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(team).on(member.username.eq(team.name))
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
잘보면 기존의 join과 다른점이 있다. 바로 leftJoin()의 인자로 하나만 전달하고 있다는 점 이다.
- 일반조인: from(member).leftJoin(member.team, team)
- on조인: from(member).leftJoin(team).on(xxx)
실행 결과는 다음과 같다.
10. Fetch Join
우선 fetch join을 사용하지 않는 코드부터 살펴보자.
@PersistenceUnit
EntityManagerFactory emf;
@Test
public void no_fetch_join() {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1"))
.fetchOne();
// loading 된 Entity인지 확인하기
boolean isLoaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(isLoaded).isFalse();
}
LAZY 로딩으로 걸려있는 Team같은 경우 아직 초기화가 되지 않았다.
실행되는 SQL도 딱 member부분만 select 해오고 있다.
따라서 assert문에서 false가 나왔다.
이번에는 Fetch Join을 사용하는 코드를 살펴보자.
@Test
public void fetch_join() {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin() // Fetch Join 사용!!
.where(member.username.eq("member1"))
.fetchOne();
// loading 된 Entity인지 확인하기
boolean isLoaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(isLoaded).isTrue();
}
join() 뒤에 fetchJoin()만 명시해주면 된다.
이번에는 team에 대한 정보까지 한번에 조회해오는 것을 확인할수 있다.
'BackEnd > JPA' 카테고리의 다른 글
[JPA] QueryDSL 중급문법 - 1 (0) | 2022.05.14 |
---|---|
[JPA] QueryDSL 기본문법 - 4 (0) | 2022.05.14 |
[JPA] QueryDSL 기본문법 - 2 (0) | 2022.05.13 |
[JPA] QueryDSL 기본문법 - 1 (0) | 2022.05.13 |
[JPA] 나머지 기능들 (0) | 2022.05.07 |
댓글