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
- 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' 카테고리의 다른 글
낙관적 락과 비관적 락 (2) | 2024.11.03 |
---|