QueryDsl - join과 서브쿼리

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

Spring

목록 보기
6/32
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개의 댓글