본문 바로가기
[개발] 프레임워크/JPA

QueryDSL에 대해서 알아보자.

by Devsong26 2024. 10. 20.

QueryDSL란?

  • 자바 기반의 데이터베이스 질의 언어로, 타입 안전하고 직관적인 방식으로 작성할 수 있도록 지원하는 라이브러리

 

QType이란?

  • QueryDSL이 제공하는 정적 타입 기반의 쿼리 작성 방식을 구현하기 위한 클래스
  • QType은 보통 Q 접두사가 붙은 엔티티 클래스를 의미하며, 쿼리를 작성할 때 사용되는 타입 안전한 표현식 객체
  • 엔티티 클래스를 기반으로 필드와 메서드에 대한 메타 정보를 담고 있어, 쿼리 작성 시 컴파일 시점에 타입 검사 가능

 

주요 특징

  • 타입 안정성: 쿼리를 문자열로 작성하는 대신, 자바 코드로 작성하기 때문에 컴파일 시점에 타입 오류를 검증할 수 있습니다.
  • 동적 쿼리 지원: 조건에 따라 동적으로 쿼리를 생성할 수 있어 복잡한 검색 조건을 처리하기에 유용합니다.
  • 통합성: JPA뿐만 아니라 SQL, MongoDB, Lucene등 다양한 데이터 저장소에 대해 통합된 방식으로 쿼리를 작성할 수 있습니다.
  • 직관적인 코드: 자바 코드로 쿼리를 작성하기 때문에 가독성이 높고, 쿼리 작성을 위한 도메인 모델을 직접 활용할 수 있습니다.
  • 엔티티와 밀접한 연관성: ORM과 함께 사용하면, 엔티티 클래스와 밀접하게 연관된 쿼리를 작성할 수 있어 객체 지향적인 접근이 가능합니다.

 

QueryDSL의 단점

  • 빌드 시간 증가: QueryDSL은 엔티티 클래스에서 Q-타입을 생성하는 컴파일러 플러그인이 필요합니다. 이로 인해 프로젝트 빌드 시간이 약간 증가할 수 있습니다.
  • 복잡한 쿼리 가독성: 간단한 쿼리에서는 가독성이 좋지만, 매우 복잡한 쿼리에서는 메서드 체이닝으로 인해 오히려 가독성이 떨어질 수 있습니다. 복잡한 조인이나 서브쿼리를 작성할 때는 SQL과 비교하여 다소 불편할 수 있습니다.

 


 

 

적용해보기

 

공식 홈페이지 기준으로 QueryDSL은 2021.7.22이 릴리즈된 5.0 버전을 끝으로 더 이상 신규 버전이 출시되지 않았습니다.

따라서 Latest 버전인 5.0을 이용하여 적용을 해보려고 합니다.

 

추가 소식
https://github.com/querydsl/querydsl/releases
https://mvnrepository.com/artifact/com.querydsl/querydsl-jpa

위의 URI에 들어가면 2024.1.30에 신규 버전인 5.1.0이 출시된 것을 확인할 수 있습니다.

 

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.3.4'
	id 'io.spring.dependency-management' version '1.1.6'
}

...

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"
    ...
}

...

// Qtype 생성 경로
def querydslDir = "$buildDir/generated/querydsl"

sourceSets {
	main.java.srcDirs += [ querydslDir ]
}

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

clean.doLast {
	file(querydslDir).deleteDir()
}

 

이제 도메인 클래스를 하나 만듭니다.

 

@Getter
@Setter
@Entity(name = "inquiry")
public class Inquiry {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "title", nullable = false)
    private String title;

    @Column(name = "content", nullable = false)
    private String content;

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @CreatedDate
    @Column(name="created_at", nullable = false, updatable = false)
    protected LocalDateTime createdAt; // 생성일시

    @CreatedBy
    @Column(name="created_member_id", nullable = false, updatable = false, length = 100)
    protected Long createdMemberId; // 생성자

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @LastModifiedDate
    @Column(name="modified_at", nullable = false)
    protected LocalDateTime modifiedAt; // 수정일시

    @LastModifiedBy
    @Column(name="modified_member_id", nullable = false, length = 100)
    protected Long modifiedMemberId; // 수정자

}

 

 

빌드 시 다음과 같이 Q타입이 생기는 것을 확인할 수 있습니다.

 

 

 

InquiryRepository 를 만듭니다.

 

@Repository
public class InquiryRepository {

    private final JPAQueryFactory queryFactory;

    public InquiryRepository(EntityManager entityManager) {
        this.queryFactory = new JPAQueryFactory(entityManager);
    }

    public List<Inquiry> findInquiriesByContainsTitle(String title) {
        QInquiry inquiry = QInquiry.inquiry;

        return queryFactory
                .selectFrom(inquiry)
                .where(inquiry.title.contains(title))
                .fetch();
    }

}

 

QType을 사용할 때 QInquiry.inquiry를 사용하는 이유
  • QueryDSL에서 타입 안전한 쿼리 작성을 지원하기 위해 미리 생성된 정적 객체를 활용하기 위함
  • 타입 안전성과 코드 가독성
  • 쿼리 구조에 대한 명확한 구분 (QInquiry inquiry = QInquiry.inquiry라고 정의하는 것은 쿼리에서 inquiry라는 별칭을 명확하게 사용할 수 있음)

 

 

테스트 코드를 작성합니다.

 

@Test
public void test_findInquiriesByTitle(){
    // Given
    final String title = "테스트";

    // When
    final List<Inquiry> inquiries = inquiryRepository.findInquiriesByContainsTitle(title);

    // Then
    assertEquals(1, inquiries.size());
}

 

 

테스트 코드가 수행되면 아래와 같이 로그가 찍히는 것을 확인할 수 있습니다.

select
    i1_0.id,
    i1_0.content,
    i1_0.created_at,
    i1_0.created_member_id,
    i1_0.modified_at,
    i1_0.modified_member_id,
    i1_0.title 
from
    inquiry i1_0 
where
    i1_0.title like ? escape '!'

 


 

에러 정리

Execution failed for task ':compileQuerydsl'. > java.lang.NoClassDefFoundError: javax/persistence/Entity

 

이 에러를 해결하기 위해서는 다음과 같이 dependencies에 추가하면 됩니다.

annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

 

 

 


 

References URIs

 

 

'[개발] 프레임워크 > JPA' 카테고리의 다른 글

낙관적 락과 비관적 락  (2) 2024.11.03