관련 내용
<Spring Data JPA와 Querydsl 완전히 이해하기>
- SpringDataJPA 이해와 사용법
- Querydsl 이해와 사용법
- (현재글)Querydsl JOIN((INNER, LEFT, RIGHT, THETA, FETCH) 사용법
Querydsl 의 JOIN (INNER, LEFT, RIGHT, THETA, FETCH) 사용법
개요 목적
이 글에서는 Querydsl의 JOIN 기능을 설명한다.
각 조인들(INNER, LEFT 등)의 의미와, 사용 방법 그리고 사용 결과를 보면서 Querydsl의 JOIN 사용법을 익혀보자.
다양한 JOIN 기능을 알아보기 위한 DB 기본 세팅
JOIN을 설명할 때 코드 예시를 든다.
해당 예시에서 사용하는 데이터를 아래 그림에서 확인할 수 있다.
QueryDsl INNER JOIN
먼저 QueryDsl에서 innerJoin이란, 연관 관계가 있는 두 엔티티의 교집합을 구하는 것이다.
inner join의 코드 작성 방법과 어떤 쿼리를 날리는 지 확인해보자.
@Test
void innerJoin() {
List<Tuple> result = queryFactory
.select(member,team)
.from(member)
//innerjoin() 이렇게 써도 된다.
.join(member.team, team)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
//결과 멤버 중에서 teamId
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), Team(id=2, name=teamB)]
tuple = [Member(id=6, username=member4, age=40), Team(id=2, name=teamB)]
JPQL 쿼리와 날라간 SQL 쿼리 확인하기
//JPQL
select member1, team from Member member1 inner join member1.team as team
//SQL 쿼리
select
member0_.member_id,
team1_.team_id,
member0_.age,
member0_.team_id,
member0_.username,
team1_.name
from
member member0_
//inner join이 실행 된다.
inner join
//FK로 연관관계가 있는 즉 member의 team_id와 team의 PK가 같은 교집합을 가져온다.
team team1_ on member0_.team_id=team1_.team_id
on절 함께 사용하기
조인 대상을 필터링 할 수 있다.
외부조인이 아니라 내부조인(inner join)을 사용하면,where 절에서 필터링 하는 것과 기능이 동일하다. 따라서 on 절을 활용한 조인 대상 필터링을 사용할 때,내부 조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자.
ON절은 WHERE절 보다 순서 상으로 더 빠르고, outer join을 사용했을 때 확실히 확인이 가능하다. ON절의 조건으로 조인 되는 결과가 달라지고 where 조건 할 대상이 달라지기 때문이다.
@Test
void innerJoinOn() {
List<Tuple> result = queryFactory
.select(member,team)
.from(member)
//innerjoin() 이렇게 써도 된다.
//join(조인 대상, 별칭으로 사용할 Q타입)
.join(member.team, team)
.on(team.name.eq("teamA"))
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
//결과
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)]
//SQL 쿼리
select
member0_.member_id,
team1_.team_id,
member0_.age,
member0_.team_id,
member0_.username,
team1_.name
from
member member0_
inner join
team team1_
on member0_.team_id=team1_.team_id
//on절에 and 조건을 붙여서 조인할 대상을 한번 필터링 한다.
//LEFT RIGHT JOIN에서도 동일하게 작동한다.
and (
team1_.name=?
)
//결과
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)]
QueryDsl LEFT JOIN
left join이란, 왼쪽 테이블 즉, from(member)에 있는 테이블의 모든 데이터를 포함한다. team FK가 없더라도 데이터를 받아온다.
left join의 코드 작성 방법과 어떤 쿼리를 날리는 지 확인해보자.
@Test
void leftJoin() {
List<Tuple> result = queryFactory
.select(member,team)
//주 조회 테이블 member가 left 테이블이 된다.
.from(member)
//leftjoin()을 사용한다. 같이 값을 가져올 대상 team right 테이블이다.
.leftJoin(member.team, team)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
//결과
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), Team(id=2, name=teamB)]
tuple = [Member(id=6, username=member4, age=40), Team(id=2, name=teamB)]
//이너 조인과 다르게 팀과 접점이 없어도 모든 member 데이터를 들고온다.
tuple = [Member(id=7, username=member5, age=50), null]
tuple = [Member(id=8, username=member6, age=60), null]
JPQL 쿼리와 날라간 SQL 쿼리 확인하기
//JPQL
select member1, team from Member member1 left join member1.team as team
//SQL 쿼리
select
member0_.member_id,
team1_.team_id,
member0_.age,
member0_.team_id,
member0_.username,
team1_.name
from
member member0_
//left outer join이 날라간다. on에는 연관관계를 찾는 FK=PK 조건을 건다.
left outer join
team team1_
on member0_.team_id=team1_.team_id
QueryDsl RIGHT JOIN
right join이란, 오른 쪽 테이블 같이 값을 가져올 조인 대상 테이블(team)의 모든 데이터를 포함한다.
right join의 코드 작성 방법과 어떤 쿼리를 날리는 지 확인해보자.
@Test
void rightJoin() {
List<Tuple> result = queryFactory
.selectDistinct(member,team)
.from(member)
//rightJoin 사용
.rightJoin(member.team, team)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
//결과
//left 테이블 값이 없는 team데이터도 전부 가져온다.
tuple = [null, Team(id=9, name=teamC)]
tuple = [null, Team(id=10, name=teamD)]
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), Team(id=2, name=teamB)]
tuple = [Member(id=6, username=member4, age=40), Team(id=2, name=teamB)]
JPQL 쿼리와 날라간 SQL 쿼리 확인하기
//JPQL
select member1, team from Member member1 rightjoin member1.team as team
//SQL 쿼리
select
member0_.member_id,
team1_.team_id,
member0_.age,
member0_.team_id,
member0_.username,
team1_.name
from
member member0_
//right outer join이 날라간다. on에는 연관관계를 찾는 FK=PK 조건을 건다.
right outer join
team team1_
on member0_.team_id=team1_.team_id
QueryDsl THETA JOIN - 막 조인
연관 관계가 없는 필드로 조인하는 방법이다.
기본적인 방법 코드
@Test
public void theta_join2() throws Exception {
//테스트를 위해 데이터 추가
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
List<Member> result = queryFactory
.select(member)
//from 절에 조인할 team을 넣는다.
.from(member, team)
//wherer 절에
.where(member.username.eq(team.name))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("teamA", "teamB");
}
//결과
tuple = [Member(id=11, username=teamA, age=0), Team(id=1, name=teamA)]
tuple = [Member(id=12, username=teamB, age=0), Team(id=2, name=teamB)]
이 방법은 레프트 라이트 아우터 조인을 하지 못한다. 조인 on절을 사용하면 세타조인인데도 아우터 조인을 할 수 있다. 그 방법에 대해서 알아보자.
연관관계 없는 엔티티 외부 조인 - on절 사용
@Test
public void join_on_no_relation() throws Exception {
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
//leftJoin(member.team, team) 이렇게가 아니라 아래처럼
//작석하고 on에 세타 조인할 조건을 붙여주면된다.
.leftJoin(team).on(member.username.eq(team.name))
.fetch();
for (Tuple tuple : result) {
System.out.println("t=" + tuple);
}
}
//결과
//아우터 조인 left테이블 모든 데이터 가져왔다.
t=[Member(id=3, username=member1, age=10), null]
t=[Member(id=4, username=member2, age=20), null]
t=[Member(id=5, username=member3, age=30), null]
t=[Member(id=6, username=member4, age=40), null]
t=[Member(id=7, username=member5, age=50), null]
t=[Member(id=8, username=member6, age=60), null]
t=[Member(id=11, username=teamA, age=0), Team(id=1, name=teamA)]
t=[Member(id=12, username=teamB, age=0), Team(id=2, name=teamB)]
FULL JOIN
왼쪽 오른쪽 데이터를 모두 가져오는 풀 조인은 지원하지 않는다.
Querydsl은 JPQL 스펙까지만 지원한다. JPQL 스펙이 Full outer join을 지원하지 않는다.
이런 경우 네이티브 쿼리를 사용해야 한다.
조인 - 페치 조인
페치 조인은 SQL에서 제공하는 기능은 아니다. SQL조인을 활용해서 연관된 엔티티를 SQL 한번에 조회하는 기능이다. 주로 성능 최적화에 사용하는 방법이다.
JPQL의 페치조인에 대한 자세한 이해는 아래 블로그 글에서 확인할 수 있다.
JPA JPQL, fetch join 중심으로 이해하기
코드를 보면서 페치 조인을 하는 방법을 알아보자.
페치 조인 미적용
@PersistenceUnit
EntityManagerFactory emf;
@Test
public void fetchJoinNo() throws Exception {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1"))
.fetchOne();
//페치 조인 확인하는 법
boolean loaded =
emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 미적용").isFalse();
}
페치 조인 적용
@Test
public void fetchJoinUse() throws Exception {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
//join 뒤에다가 fetchjoin()을 붙여준다.
.join(member.team, team).fetchJoin()
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded =
emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 적용").isTrue();
}
//JPQL
select member1 from Member member1 inner join
fetch member1.team as team where member1.username = member1
//SQL
//innerjoin을 통해서 team 정보까지 다 가져오는 것을 확인할 수 있다.
select
member0_.member_id,
team1_.team_id,
member0_.age,
member0_.team_id,
member0_.username,
team1_.name
from
member member0_
inner join
team team1_
on member0_.team_id=team1_.team_id
where
member0_.username=?
leftJoin() 등 조인 기능 뒤에 fetchJoin() 도 가능하다.
'Web Sever 개발과 CS 기초 > 스프링' 카테고리의 다른 글
Spring - MySQL과 Querydsl 통계 쿼리 처리(group by, Expressions) (0) | 2023.05.01 |
---|---|
Querydsl를 사용한 동적 쿼리 만들기 (1) | 2023.04.29 |
Querydsl 이해와 사용법 (0) | 2023.04.28 |
Spring Data JPA 이해와 사용법 (0) | 2023.04.28 |
JDBC 코드를 JPA 기술을 사용하여 변경하기(배달 서비스 음식 등록) (0) | 2023.04.28 |