반응형
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()
}
설정 설명
- querydslDir은 QClass가 생성될 경로를 지정하는 변수입니다.
- sourceSets 설명
- sourcesSets: Gradle 프로젝트 구조에서 소스 파일, 리소스 파일을 관리하는 기본적인 구조입니다.
- main: 주요 소스 세트이며, 기본적으로 src/main/java 디렉터리가 포함됩니다.
- main.java.srcDirs: main 소스 세트 내의 java 소스 코드 디렉터리 리스트를 지정합니다.
- tasks.withType... 설명
- javaCompile 작업 유형을 가진 모든 Gradle 작업에 대해 설정을 적용합니다.
- configureEach: Gradle 6.0 이상에서 제공하는 메서드로, 모든 JavaCompile 작업에 대해 설정을 하나씩 적용해 줍니다.
- options.annotationProcessorGeneratedSourcesDirectory: 애노테이션 프로세서가 생성한 소스 파일을 저장할 디렉터리를 지정합니다.
- file(querydslDir): querydslDir에 지정된 경로를 파일 객체로 변환하여 그 위치를 설정합니다.
이제 도메인 클래스를 하나 만듭니다.
@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
- https://velog.io/@juhyeon1114/Spring-QueryDsl-gradle-%EC%84%A4%EC%A0%95-Spring-boot-3.0-%EC%9D%B4%EC%83%81
- https://dev.gmarket.com/33
반응형
'[개발] 프레임워크 > JPA' 카테고리의 다른 글
[JdbcTemplate] batchUpdate 를 통해 bulk insert를 하는 방법 (1) | 2024.12.07 |
---|---|
[JPA] @BatchSize와 쿼리 캐시에 관해서 알아보기 (1) | 2024.11.28 |
JPA 2차 캐시와 레디스 캐시에 대하여 생각해보기 (2) | 2024.11.21 |
JPA 1차 캐시와 2차 캐시 (1) | 2024.11.12 |
낙관적 락과 비관적 락 (2) | 2024.11.03 |