ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • QueryDSL 서브 쿼리 사용법
    Java/JPA 2019. 7. 14. 17:44
    반응형

     QueryDSL 에서는 서브 쿼리도 작성이 가능합니다. 다만 모든 절에서 지원하는 것은 아니고,  select 절과 where 절에서만 지원합니다.

     

    먼저 select 절에서의 서브 쿼리를 보겠습니다.

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class MemberCountDTO {
    
        private String teamName;
        private Long memberCount;
    }

     우선 쿼리의 결과로 팀의 이름과, 팀에 속한 멤버들의 수를 담는 DTO 클래스입니다.

     

    SELECT 절에서의 SubQuery

        @Test
        public void simpleSubQueryTest() {
            QMember m = QMember.member;
            QTeam t = QTeam.team;
    
            query.select(Projections.fields(MemberCountDTO.class, t.teamName,
                    ExpressionUtils.as(
                            JPAExpressions.select(m.team.count())
                                    .from(m)
                                    .where(m.team.eq(t)),
                            "memberCount")
            ))
                    .from(t)
                    .fetch()
                    .stream()
                    .forEach(result -> {
                        log.info("team name is : " + result.getTeamName());
                        log.info("member count is : " + result.getMemberCount());
                    });
        }

     제가 참고하면서 공부하고 있는 < 자바 ORM 표준 JPA 프로그래밍 (김영한 저) > 책에서는 JPASubQuery 객체를 이용하여 서브쿼리를 작성하는 예제가 나와있습니다. 하지만 현재 버전이 올라가면서 JPASubQuery 객체는 이용하지 않고, 위의 예제 코드처럼 JPAExpressions 객체를 이용하여 서브쿼리를 생성합니다.

     

    ExpressionUtils 는 QueryDSL 내부에서 새로운 Expression 를 생성할 수 있도록 도와줍니다. 두 번째 인자로는 alias를 지정하여 memberCount 프로퍼티가 MemberCountDTO 객체에 잘 주입될 수 있도록 해줍니다.

    쿼리의 결과로 위와 같이 SQL 이 작성되었습니다.

     


     

    WHERE 절에서의 서브쿼리

        @Test
        public void whereSubQueryTest() {
            QMember m = QMember.member;
            QTeam t = QTeam.team;
    
            query.selectFrom(t)
                    .where(t.teamId.in(
                            JPAExpressions
                                    .select(m.team.teamId)
                                    .from(m)
                                    .where(m.memberId.eq(1L))
                    ))
                    .fetch()
                    .stream()
                    .forEach(result -> {
                        log.info("team id is " + result.getTeamId());
                        log.info("team name is " + result.getTeamName());
                    });
        }

     위의 예제는 Member 의 id 가 1인 멤버가 속한 팀을 조회하는 쿼리입니다. where 절에서는 Expression을 사용하지 않으므로 ExpressionUtils 클래스를 사용하지 않아도 됩니다. 위의 쿼리는 아래와 같은 SQL 문으로 실행되는 것을 확인할 수 있습니다.

     


     

     서브쿼리는 사실 성능적인 측면에서는 그다지 좋지 못합니다. 물론 서브쿼리를 사용하는 편이 더 나은 경우도 있습니다. 하지만 대부분의 경우에는 서브쿼리를 지양하는 것이 좋다고 생각합니다.

     

     서브쿼리의 문제점은 다음과 같습니다.

    1. 서브쿼리는 테이블과 다르게 SQL 구문이 실행될 때에만 존재하는 비영속적인 임시 테이블입니다. 이 말은 곧, 서브쿼리에 접근할 때마다 SELECT 구문을 실행하여 데이터를 만들어야한다는 뜻입니다. 따라서 SELECT 구문을 실행하는 비용이 추가적으로 발생하게 됩니다.
    2. 서브쿼리는 비영속적인 임시 테이블이므로 실행된 후에 결과를 저장해두기 위해 메모리에 적재되어야 합니다. 메모리 용량이 충분하다면 이러한 오버헤드가 적지만, 데이터양이 큰 경우에는 DBMS가 저장소에 있는 파일에 결과를 쓸 때도 있습니다. 이러한 문제는 저장소의 성능에 따라 접근 속도가 급격하게 떨어지게 됩니다.
    3. 명시적인 제약과 인덱스가 작성되어 있는 테이블과 달리, 서브쿼리는 이러한 메타정보가 존재하지 않으므로 옵티마이저의 최적화를 받을 수 없습니다.

     

     위와 같은 문제로 서브쿼리가 필요하다면 아래와 같은 방법을 고려하는 것이 좋아보입니다.

    (출처 : https://jojoldu.tistory.com/379?category=637935)

    • 1. Join을 이용하여 해결하는 방법
    • 2. 어플리케이션 레벨에서 처리하는 방법
    • 3. 쿼리를 나누어서 실행하는 방법

    참고자료

    https://jojoldu.tistory.com/379?category=637935

     

    [Querydsl] 서브쿼리 사용하기

    안녕하세요! 이번 시간에는 Querydsl에서의 Subquery 기본 가이드를 진행합니다. 개인적으로 ORM을 사용하며, 객체지향적으로 엔티티가 구성되어있으면 서브쿼리가 필요한 일은 거의 없다고 생각하는데요. 혹시나..

    jojoldu.tistory.com

     

    김영한님의 저서 < 자바 ORM 표준 JPA 프로그래밍 >

     

    한빛미디어 출판 < SQL 레벨업 >

    반응형

    'Java > JPA' 카테고리의 다른 글

    [JPA] Auditing 설정 방법  (0) 2019.08.15
    [QueryDSL] select 절에서 조회 대상 지정 방법  (0) 2019.07.13
    JPA QueryDSL 시작해보기  (0) 2019.07.11

    댓글

Designed by Tistory.