QueryDsl - join과 서브쿼리

청포도봉봉이·2023년 3월 22일
0

Spring

목록 보기
6/35
post-thumbnail

join on절

    /**
     * 회원과 팀을 조인하면서, 팀 이름이 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'



join - 관계가 없는 2개의 entity

	@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만 적어준다.



join - no fetch join

 @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();

    }



join - fetch join 적용

	@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 절의 서브쿼리 해결방안
  1. 서브쿼리를 join으로 변경한다. (가능한 상황도 있고, 불가능한 상황도 있다.)
  2. 애플리케이션에서 쿼리를 2번 분리해서 실행한다.
  3. nativeSQL을 사용한다.
profile
서버 백엔드 개발자

0개의 댓글