QueryDsl 에서도 JPQL과 유사하게 다양한 종류의 조인을 지원합니다. 주로 사용되는 조인 유형에는 JPQL과 마찬가지로 내부 조인, 외부 조인, 세타 조인, 크로그 조인이 있습니다.
내부 조인 (Inner Join)
내부 조인은 두 테이블에서 조건이 일치하는 행만 반환합니다. 아래 코드는 member, team 간에 일치하는 행만 추출하며 그중 teamA인 행만 조회합니다.
@Test
void 내부_조인_테스트() {
List<Member> result = queryFactory
.select(member)
.from(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(result.size()).isEqualTo(2);
}
외부 조인 (Outer Join)
외부 조인은 조건이 일치하지 않는 행도 포함하여 반환합니다. 주로 좌측 (Left Join)이 사용됩니다. 이 예시에서 member, team을 외부 조인하여 팀에 속하지 않는 멤버도 결과에 포함합니다.
@Test
void 외부_조인_테스트() {
List<Member> result = queryFactory
.select(member)
.from(member)
.leftJoin(member.team, team)
.fetch();
assertThat(result.size()).isEqualTo(5);
}
세타 조인 (Theta Join)
세타 조인은 두 엔티티 간에 연관관계가 없어도 조인할 수 있게 해주는 기능으로 QueryDsl에서는 where 절을 사용하여 세타 조인을 구현할 수 있습니다. 만약 member, team 간에 관계가 정의되어 있지 않다고 가정하고, 두 엔티티 간에 특정 조건을 기반으로 데이터를 조인하고 싶을 때 세타 조인을 사용합니다.
@Test
void 세타_조인_테스트() {
List<Member> result = queryFactory
.select(member)
.from(member, team)
.where(member.username.eq(team.name))
.fetch();
assertThat(result.size()).isEqualTo(0);
}
조인 - ON절
QueryDsl에서 on절은 sql과 유사하게 두 가지 주요 역할을 합니다. 조인 대상을 필터링하고, 연관관계 없는 엔티티 간의 조인(세타 조인)을 수행하는데 사용됩니다. 이를 통해 더 복잡하고 세밀한 조인 조건을 정의할 수 있습니다.
조인 대상 필터링 및 외부 조인
on 절을 사용하여 조인하는 대상을 특정 조건에 맞는 데이터로 제한할 수 있습니다. 이는 주로 연관관계가 있는 엔티티 간의 조인에서 조인 대상을 필터링할 때 사용됩니다.
@Test
void teamA_에_소속된_모든_멤버_조회() {
String teamName = "teamA";
List<Member> result = queryFactory
.select(member)
.from(member)
.join(member.team, team)
.where(team.name.eq(teamName))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("member1", "member2");
}
@Test
void teamA인_팀만_조회하고_회원은_모두_조회한다() {
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(member.team, team)
.on(team.name.eq("teamA"))
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
위 결과에서 보면 외부조인으로 team 데이터를 가져오고 조인되지 않은 member 데이터도 모두 가져옵니다.
연관관계 없는 엔티티 외부 조인
연관관계가 없는 엔티티 간의 조인(세타 조인)은 on 절을 사용하여 수행할 수 있으며, 이는 주로 외부 조인의 형태로 사용됩니다.
@Test
void 연관_관계가_없는_외부조인() {
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(team).on(team.name.eq(member.username))
.fetch();
}
연관관계가 없는 엔티티 간에 외부 조인을 수행할 때 where절을 사용하게 되면 Hibernate나 JPA 구현체에서 아래와 같은 예외를 발생시킬 수 있습니다. 연관관계가 없는 엔티티 간의 조인을 시도할 때, on절을 통해 명시적으로 조인 조건을 제공하지 않았기 때문입니다.
java.lang.IllegalArgumentException: org.hibernate.query.SemanticException: Entity join did not specify a predicate, please define an on clause or use an explicit cross join: SqmEntityJoin(com.study.querydsl2024.entity.Team(team))
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:138)
at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:378)
at org.hibernate.query.Query.getResultList(Query.java:119)
연관관계가 없는 두 엔티티를 외부 조인할 때는 반드시 on절을 사용하여 조인 조건을 명시적으로 지정해야 합니다. 이렇게 하면 조인을 수행할 수 있는 기준이 명확해지며, 추가적으로 where 절을 사용하여 결과를 필터링할 수 있습니다.
* 연관관계가 없는 엔티티 간의 외부 조인을 시도할 때 on 절은 필수입니다.
on
- 목적 : on절은 조인 조건을 지정하는 데 사용됩니다. 이는 주로 두 테이블 간의 조인을 위한 키(열)을 명시할 때 사용되며, 조인 대상을 더 세밀하게 제어할 수 있게 합니다.
- 연관관계 없는 엔티티의 조인 : 연관관계가 없는 엔티티 간의 조인(세타 조인이나 외부 조인 포함)을 할 때, on 절이 필수적입니다.
- 조인 대상 필터링 : on 절은 특정 조건을 만족하는 행만 조인 대상으로 만듭니다. 조인 과정에서 필터링을 적용하여 결과적으로 특정 조건에 부합하는 레코드만 조회됩니다.
- 외부 조인에서의 중요성 : 외부 조인 (left join, right join) 에서 on 절은 조인되는 테이블에서 어떤 레코드가 결과에 포함될지 결정합니다. 조건에 부합하지 않는 행은 null 값으로 채워진 필드를 결과에 포함 합니다.
where
- 목적 : where 절은 쿼리 결과에서 특정 조건을 만족하는 행을 필터링 하기 위해 사용됩니다. 조인된 결과물 전체에 대해 추가적인 조건을 적용하여 필터링 할 때 사용됩니다.
- 조인 : where 절은 조인 조건과 독립적으로 작동합니다. 조인 과정이 완료된 후에 최종적으로 결과를 필터링 합니다.
페치 조인 (Fetch Join)
QueryDsl에서 fetch join은 JPA fetch join과 같습니다. 연관된 엔티티나 컬렉션을 한번의 쿼리로 함께 조회하는 기능을 제공합니다. 페치 조인을 사용하게 되면 N+1문제를 해결할 수 있습니다. ( 자세한 내용은 "조회 전략" 부분을 참고 하시기 바랍니다.)
N+1 문제
엔티티를 조회할 때, 엔티티와 연관된 다른 엔티티를 접근하기 위해 추가적인 쿼리가 발생하는 문제를 말합니다. 예를 들어, member 엔티티를 조회하고 각 member에 속한 team에 접근할때 member 대해 team을 조회하는 별도의 쿼리가 실행되어 총 쿼리 수가 N+1이 됩니다.
@Test
void 페치_조인_미사용() {
List<Member> result = queryFactory
.select(member)
.from(member)
.join(member.team, team)
.fetch();
for (Member findMember : result) {
Team team1 = findMember.getTeam();
System.out.println("team1 = " + team1);
}
}
fetch join의 사용
fetch join은 주로 xToOne 관계에서 사용합니다. fetch join을 사용하면 연관된 엔티티를 처음 쿼리할 때 함께 로드함으로써 추가적인 쿼리 발생 없이 연관된 데이터에 접근할 수 있습니다.
@Test
void 페치_조인_사용() {
List<Member> result = queryFactory
.select(member)
.from(member)
.join(member.team, team).fetchJoin()
.fetch();
}
이 코드는 member를 조회하면서 member가 속한 team을 함께 로드합니다. fetchJoin() 호출로 각 member에 대한 정보를 가져오기 위한 추가적인 쿼리가 실행되지 않으며, 모든 데이터는 한번의 쿼리로 조회됩니다.
'QueryDSL' 카테고리의 다른 글
QueryDsl 프로젝션 결과 반환 (0) | 2024.03.25 |
---|---|
QueryDsl 서브쿼리 (0) | 2024.03.20 |
Querydsl 기본 문법 (0) | 2024.03.15 |
QueryDsl 소개 (0) | 2024.03.15 |
Springboot3.x에서 Querydls 설정 (0) | 2024.03.14 |