개요
연관관계가 있는 엔티티를 조인 - 기본 조인
/**
* **INNER JOIN**
* 예) 팀 A 에 소속된 모든 회원 조회
*/
@Test
void join() {
//when
List<Member> result = jpaQueryFactory
.selectFrom(member)
.join(member.team, team) // join(조인 대상, 별칭으로 사용할 Q타입 지정)
.where(team.name.eq("teamA"))
.fetch();
//then
assertThat(result)
.extracting("userName")
.containsExactly("member1", "member2");
// Console Log
// Member(id=3, userName=member1, age=10)
// Member(id=4, userName=member2, age=20)
}
/**
* **LEFT OUTER JOIN**
* 예) 회원과 팀을 조인하면서, 팀 이름이 teamA 인 팀만 조인, 회원은 모두 조회
* JPQL: SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'teamA'
* SQL : SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID = t.id and t.name = 'teamA'
*/
@Test
void join_on_filtering() {
//given
//when
List<Tuple> result = jpaQueryFactory
.select(member, team)
.from(member)
.leftJoin(member.team, team).on(team.name.eq("teamA"))
.fetch();
//then
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
// Console Log
// tuple = [Member(id=3, userName=member1, age=10), Team(id=1, name=teamA)]
// tuple = [Member(id=4, userName=member2, age=20), Team(id=1, name=teamA)]
// tuple = [Member(id=5, userName=member3, age=30), null]
// tuple = [Member(id=6, userName=member4, age=40), null]
}
@PersistenceUnit
EntityManagerFactory emf;
@Test
void fetchJoinUse() {
//given
em.flush();
em.clear();
//when
Member findMember = jpaQueryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.userName.eq("member1"))
.fetchOne();
//then
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 적용").isTrue();
}
- 기본 조인
- join() , innerJoin() : 내부 조인(inner join)
- leftJoin() : left 외부 조인(left outer join)
- rightJoin() : rigth 외부 조인(rigth outer join)
- 성능 최적화를 위한 Fetch Join 제공
- SQL JOIN 을 활용해서 연관된 엔티티를 SQL 한 번에 조회하는 기능
- 지연로딩으로 설정 되어 있다면 조회 시 프록시 초기화 방법으로 연관관계의 엔티티를 가져온다.
- 페치 조인의 적용으로 연관 관계의 엔티티까지 즉시로딩으로 한 번에 조회할 수 있다.
- 참고
- on 절을 활용해 조인 대상을 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링 하는 것과 기능이 동일하다.
- 따라서 on 절을 활용한 조인 대상 필터링을 사용할 때, 내부조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 on 절을 사용하자.
연관관계가 없는 엔티티를 조인
/**
* **THETA JOIN (연관관계가 없는 필드로 조인)**
* 예) 회원의 이름이 팀 이름과 같은 회원 조회
* 모든 멤버과 모든 팀을 가져와 조인하고 조건(where)에 맞게 조회 (DB 가 성능 최적화)
* from 절에 여러 엔티티를 선택해서 세타 조인
* 외부 조인 불가능 -> on 절을 사용하면 외부 조인 가능
*/
@Test
void theta_join() {
//given
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
em.persist(new Member("teamC"));
//when
List<Member> result = jpaQueryFactory
.select(member)
.from(member, team)
.where(member.userName.eq(team.name))
.fetch();
//then
assertThat(result)
.extracting("userName")
.containsExactly("teamA", "teamB");
}
/**
* **LEFT OUTER JOIN (연관관계 없는 엔티티 외부 조인)**
* 예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
* JPQL: SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
* SQL: SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
*/
@Test
void join_on_no_relation() {
//given
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
em.persist(new Member("teamC"));
//when
List<Tuple> result = jpaQueryFactory
.select(member, team)
.from(member)
.leftJoin(team).on(member.userName.eq(team.name))
.fetch();
//then
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
// Console Log
// tuple = [Member(id=3, userName=member1, age=10), null]
// tuple = [Member(id=4, userName=member2, age=20), null]
// tuple = [Member(id=5, userName=member3, age=30), null]
// tuple = [Member(id=6, userName=member4, age=40), null]
// tuple = [Member(id=7, userName=teamA, age=0), Team(id=1, name=teamA)]
// tuple = [Member(id=8, userName=teamB, age=0), Team(id=2, name=teamB)]
// tuple = [Member(id=9, userName=teamC, age=0), null]
}
- 하이버네이트 5.1 부터 on 절을 사용해서 서로 관계가 없는 필드로 외부조인하는 기능 추가
- 내부조인도 가능하다.
- 주의
- leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어간다.
- 일반조인 : leftJoin(member.team, team)
- on조인 : from(member).leftJoin(team).on(Xxx)