멋쟁이사자처럼 13기/정기 세션

[멋사/Mac OS] 5주차 정기 세션 (Part 3. QueryDSL 소개 및 JPQL과 비교)

개발자 지망생 리네 2025. 6. 20. 21:49

3. QueryDSL 소개 및 JPQL과 비교

(1) JPQL의 단점

- 오타나 문법 오류를 컴파일 시에 찾을 수 없음

-> 실행 전까지 오류 발견이 불가능

-> 개발 중 오류 발견이 어렵고, 실행 중 예외 발생

 

- 동적 쿼리 작성이 불편

-> if문이 많아질수록 가독성이 나빠짐


(2) QueryDSL의 장점

- 타입 안전성 보장

-> 컴파일 시점에 username이 String 타입인지 확인 가능

-> 오타가 있으면 바로 에러가 발생하여 실행 전에 오류를 방지

 

- 동적 쿼리 작성이 간편

-> BooleanBuilder를 사용해서 조건을 유동적으로 추가 가능

-> if문을 최소화하고 가독성을 높임

 

  JPQL QueryDSL
문법 문자열 기반 코드 기반
타입 체크 런타임 오류 가능 컴파일 시 오류 잡힘
동적 쿼리 어렵고 복잡 유연하고 깔끔
IDE 지원 거의 없음 자동완성 리팩토링 용이

(3) QueryDSL

- QueryDSL: JPA 위에서 동작하는 안전한 쿼리 작성 도구, Java 코드로 쿼리를 작성

-> 동적 쿼리에 강함, 문법 오류를 컴파일 타임에 체크, IDE에서 자동완성 가능

 

- 기본 사용 구조

QMember m = QMember.member;
queryFactory.selectFrom(m)
    .where(m.age.gt(20))
    .fetch();	// 실제 쿼리를 실행하고 결과를 리스트로 받아옴

 

-> Member 엔티티를 기반으로 만들어진 Q타입 객체를 사용

 

+) Q타입

- Q타입 : 엔티티 기반으로 QueryDSL이 자동 생성하는 쿼리 전용 클래스

- build/generated 폴더에 위치

-> 자동 완성 가능

-> 컴파일 오류로 오타/문법 실수 바로 잡힘

-> 리팩토링에 강함


(4) QueryDSL 실습

- Gradle 설정

def querydslVersion = "5.0.0"

dependencies {

	//queryDSL 의존성
	implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
	annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
	annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
	annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
  
}

def querydslDir = "$buildDir/generated/sources/annotaionProcessor/java"

sourceSets {
	main {
		java {
			srcDirs += querydslDir
		}
	}
}

tasks.withType(JavaCompile).configureEach {
	options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}

clean {
	delete file(querydslDir)
}

 

-> 계속 Q클래스가 뜨지 않아서 아예 디렉토리를 지정해두었다.

 

- QueryFactory 빈 등록

package com.week5.likelionmember.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QueryDslConfig {

    @Bean
    public JPAQueryFactory queryFactory(EntityManager em) {
        return new JPAQueryFactory(em);
    }
}

 

- MemberTest.java 수정

package com.week5.likelionmember;

import com.week5.likelionmember.domain.Member;
import com.week5.likelionmember.repository.MemberRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class MemberTest implements CommandLineRunner {

    private final MemberRepository memberRepository;


    public MemberTest(MemberRepository memberRepository) { 
        this.memberRepository = memberRepository;
    }

    @Override
    public void run(String... args) throws Exception {

        // 쿼리 메서드 실행
        List<Member> queryMethodResult = memberRepository.findByUsernameAndAgeGreaterThanEqual("yujin", 23)
		System.out.println("쿼리 메소드 결과: ");
        
        // @Query JPQL 실행
        List<Member> jpqlResult = memberRepository.findUsernameAndOlderThan("yujin", 23);
        System.out.println("@Query JPQL 결과:");
        jpqlResult.forEach(System.out::println);
        
        // QueryDSL 실행
        QMember m = QMember.member;
        List<Member> querydslResult = queryFactory.selectFrom(m)
            .where(
            	m.age.goe(23),
                m.university.eq("서울여자대학교")
            )
            .fetch();
    }
}