/**
* 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
*/
@Test
public void join_on_filtering() {
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);
}
assertThat(result.size()).isEqualTo(4);
}
- on() : 외부 조인시 조인하는 테이블에 where 조건을 걸어줌
- inner 조인시에는 그냥 where절에 조건을 거는게 낫다.
- JPQL : select m, t from Member m left join m.team t on t.name = 'teamA'
@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);
}
}
- 관계가 없는 table에서의 join은 leftJoin절에 join할 entity만 적어준다.
@PersistenceUnit
EntityManagerFactory emf;
@Test
public void fetchJoinNo() {
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() {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("패치 조인 적용").isTrue();
}
- join절 뒤에 fetchJoin()을 사용해주면 된다.
JPA fetch join은 관계형 데이터베이스에서 여러 개의 테이블 간의 관계를 가진 엔티티들을 조회할 때 사용하는 방법 중 하나다.
기본적으로 JPA에서 엔티티를 조회할 때, 지연 로딩(Lazy Loading) 방식으로 동작한다. 이 방식은 연관된 엔티티를 조회할 때 해당 엔티티를 사용할 때까지는 데이터베이스에서 조회하지 않는다. 하지만 이 방식은 지속적인 데이터베이스 접근으로 인해 성능 저하를 일으킬 수 있다.
이때, fetch join을 사용하면 지연 로딩이 아닌 즉시 로딩(Eager Loading) 방식으로 연관된 엔티티를 함께 조회할 수 있다. 이를 통해 성능 저하를 방지할 수 있다.
/**
* 나이가 가장 많은 회원을 조회
*/
@Test
public void subQuery() {
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(
select(memberSub.age.max())
.from(memberSub)
))
.fetch();
assertThat(result.get(0).getAge()).isEqualTo(40);
}
@Test
public void subQueryGoe() {
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.goe(
select(memberSub.age.avg())
.from(memberSub)
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(30, 40);
}
@Test
public void subQueryIn() {
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.in(
select(memberSub.age)
.from(memberSub)
.where(memberSub.age.gt(10))
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(20, 30, 40);
}
@Test
public void selectSubQuery() {
QMember memberSub = new QMember("memberSub");
List<Tuple> result = queryFactory
.select(member.username,
select(memberSub.age.avg())
.from(memberSub)
)
.from(member)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
- from 절의 서브쿼리 한계
JPA JPQL 서브쿼리의 한계점으로 from 절의 서브쿼리(인라인 뷰)는 지원하지 않는다. 당연히 Querydsl
도 지원하지 않는다. 하이버네이트 구현체를 사용하면 select 절의 서브쿼리는 지원한다. Querydsl도
하이버네이트 구현체를 사용하면 select 절의 서브쿼리를 지원한다.
- from 절의 서브쿼리 해결방안
- 서브쿼리를 join으로 변경한다. (가능한 상황도 있고, 불가능한 상황도 있다.)
- 애플리케이션에서 쿼리를 2번 분리해서 실행한다.
- nativeSQL을 사용한다.