inblog logo
|
LifeLog, DevLog
    JPA

    QueryDSL 정리 - 1

    QueryDSL 셋업, 간단한 실습
    KYJTHEYJ's avatar
    KYJTHEYJ
    Feb 24, 2026
    QueryDSL 정리 - 1
    Contents
    QueryDSL?gradle 선언JPAQueryFactory 등록하기Projection 패턴

    QueryDSL?

    • Q 클래스 파일들을 이용하여
      SQL 과 유사하게 쿼리를 작성하여 데이터 처리를 돕는 프레임워크

    이제 JPA 를 처음 사용해보기도 했는데, 생각보다 쿼리 자체를 끌어와서 쓰기엔
    복잡한 부분도 있었고, 만약 JPA 환경이 아니면 뭘 쓰는게 나을까를 알아보다가

    JOOQ 라는 것을 알게 되었고, 동적쿼리 활용은 QueryDSL도 겸사겸사 알게 되었다

    우선 데이터는 MySQL의 Country 예제를 가져왔다

    gradle 선언

    우선 기존 QueryDSL 은 취약점이 있기도 하고, 전체적으로 프로젝트가
    멈춘거 같아 좀 알아보고 들어보고 하다 OpenFeign 측이 fork 하여
    계속 이어나가는 거 같아 OpenFeign QueryDSL 을 실습하려한다

    현재 버전은 7.1

    자세한 링크는 https://github.com/OpenFeign/querydsl

    implementation 'io.github.openfeign.querydsl:querydsl-core:7.1'
    implementation 'io.github.openfeign.querydsl:querydsl-jpa:7.1'
    annotationProcessor 'io.github.openfeign.querydsl:querydsl-apt:7.1:jpa'
    
    // Q 클래스 생성 설정을 따로 관리할 경우 (설정 안할시 build 내에 첨부됨)
    def generated = 'src/main/generated'
    
    sourceSets {
        main.java.srcDirs += [generated]
    }
    
    tasks.withType(JavaCompile).configureEach {
        options.generatedSourceOutputDirectory = file(generated)
    }
    
    clean {
        delete file(generated)
    }

    후에 그래들 빌드 후 기존 빌드를 클린하고 컴파일 자바를 해보니

    Q 클래스 파일들이 생성된 걸 확인했다

    • Q class?

      Entity를 QueryDSL에서 활용할 때 타입 관련하여 안전한 쿼리 작성을 위해
      QueryDSL이 생성하는 클래스 파일들

    JPAQueryFactory 등록하기

    다음으로는 JPAQueryFactory Bean 을 등록했다

    • Repository 단에서 주입받아 사용하기 위해 선언

    • QueryDSL 쿼리를 만들기 위한 시작점이 되는 것이 JPAQueryFactory 이다

    • QueryDSL 을 활용할 때 EntityManager 의 직접 관리가 상당히 번거롭기 때문에
      (코드의 생성이 매번 있어야함) 내부에 우선 선언하고 주입하여 사용한다

    • 또, Spring 의 트랜잭션 관리와 연동하기 위해서 등록해야한다

    이제 사용을 위해 Repository 를 작성하는데,
    보통 Impl 파일로 구현을 추상한다 (DIP)

    // queryDsl 기본 구조
    
    queryFactory
        .select(조회대상)
        .from(대상엔티티)
    	.join(대상엔티티.연관 필드, 연관된 엔티티의 Q 클래스)[.fetchJoin()]
        .where(조건)
        .orderBy(정렬)
        .fetch();
    
    // 연관 데이터의 데이터도 필요하면? fetchJoin
    // Repository interface & Implement
    public interface PostRepository {
        List<PostSummaryDto> findPostSummary(String username);
    }
    
    @Repository
    @RequiredArgsConstructor
    @Transactional
    public class PostRepositoryImpl implements PostRepository {
        private final JPAQueryFactory queryFactory;
    
        @Override
        public List<PostDto> findPostSummary(String username) {
            return queryFactory
                    .select(Projections.constructor(
                            PostDto.class
                            , post.content
                            , comment.countDistinct().intValue()
                    )).from(post)
                    .leftJoin(post.comments, comment)
                    .where(post.user.username.eq(username))
                    .groupBy(post.id)
                    .fetch();
        }
    }
    • 우선 일반적인 전체 조회를 구성해서 response dto 에 담았다

    • 내부에서 우선 Bean 으로 선언했던 JPAQueryFactory 를 주입한 후에
      이를 빌더 패턴으로 작성하여 실행하는 타입이다

    Projection 패턴

    Record 클래스 + Constructor 생성 패턴이 제일 효율적으로 보인다

    // Projections.bean - setter로 매핑
    queryFactory
        .select(Projections.bean(PostDto.class,
            post.id,
            post.title
        ))
        .from(post)
        .fetch();
    
    // Projections.fields - 필드에 직접 매핑 (setter 없어도 됨)
    queryFactory
        .select(Projections.fields(PostDto.class,
            post.id,
            post.title
        ))
        .from(post)
        .fetch();
    
    // Projections.constructor * - 생성자로 매핑
    // 만약 DTO가 정적 팩토리 패턴이면 프로젝션 할수 없음 유의
    // record + Projections.constructor
    queryFactory
        .select(Projections.constructor(PostDto.class,
            post.id,
            post.title
        ))
        .from(post)
        .fetch();
    
    // @QueryProjection * (하지만 DTO가 queryDSL 의존이 됨)
    // 컴파일 시점에 체킹이 됨
    // DTO 생성자에 어노테이션 추가
    class PostDto {
        @QueryProjection
        public PostDto(Long id, String title) { ... }
    }
    
    queryFactory
        .select(new QPostDto(post.id, post.title))
        .from(post)
        .fetch();

    Share article
    Contents
    QueryDSL?gradle 선언JPAQueryFactory 등록하기Projection 패턴

    LifeLog, DevLog - https://github.com/KYJTHEYJ

    RSS·Powered by Inblog