객체 관계 매핑 프레임워크는 Enterprise Java의 핵심입니다. 이는 객체 지향 방식과 관계형 데이터베이스 모델 간의 불일치를 보완합니다. 또한 개발자가 더 깔끔하고 간결한 지속성 코드와 도메인 로직을 작성할 수 있도록 합니다. -QueryDSL 공식 홈페이지중 baeldung의 QueryDSL 목적
https://www.baeldung.com/intro-to-querydsl
즉 QueryDSL은 Java 언어로 작성된 쿼리를 위한 타입 안전 쿼리 생성 라이브러리입니다. SQL, JPA, MongoDB 등 다양한 데이터베이스와의 상호작용을 지원하며, 개발자가 보다 직관적이고 안전하게 데이터베이스 쿼리를 작성할 수 있도록 도와줍니다.
주요 특징
타입 안전성
QueryDSL은 쿼리의 문법과 데이터 타입을 컴파일 시점에 검사합니다. 이는 런타임 에러를 줄여주고, 코드 작성 시 IDE의 자동 완성 기능을 통해 쿼리를 더 빠르고 정확하게 작성할 수 있도록 합니다.
유연한 백엔드 지원
QueryDSL은 SQL, JPA, MongoDB, Lucene 등 다양한 데이터베이스를 지원합니다. 이를 통해 개발자는 특정 기술에 종속되지 않고, 여러 데이터베이스와의 상호작용을 동일한 방식으로 처리할 수 있습니다.
가독성 높은 쿼리
QueryDSL은 SQL 쿼리를 Java 코드로 변환할 수 있도록 설계되었습니다. 이는 쿼리의 가독성을 높이고, 쿼리를 동적으로 생성할 수 있는 유연성을 제공합니다.
정적 쿼리 생성
쿼리를 정적으로 생성하므로, 쿼리의 구조를 쉽게 이해하고 수정할 수 있습니다. 이는 팀원 간의 협업을 용이하게 하고, 코드 유지 관리성을 높여줍니다.
적용 방법
Ex) Gradle
(QueryDSL에 사용할 JPA 엔티티 클래스가 이미 적용된 기준)
(필자는 Todo와 User의 엔티티 기준으로 작성할 예정)
1. QueryDSL 의존성
// 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"
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
의존성 설명
- com.querydsl:querydsl-jpa는 QueryDSL의 JPA(자바 지속성 API) 모듈로, JPA를 사용하여 데이터베이스와 상호작용하는 기능을 제공합니다.
- 버전은 5.0.0이며, jakarta는 Jakarta EE를 사용하고 있음을 나타냅니다. 이 버전은 Jakarta EE 9 및 그 이후 버전에서 사용되는 새로운 패키지 구조를 따릅니다.
역할
- JPA 엔티티를 기반으로 쿼리를 생성하고 실행하는 데 필요한 기능을 제공합니다. QueryDSL은 SQL을 Java 코드로 안전하게 작성할 수 있게 해 줍니다.
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
의존성 설명
- com.querydsl:querydsl-apt는 QueryDSL의 APT(Annotation Processing Tool) 모듈입니다. JPA 엔티티에 대한 메타데이터를 생성하기 위해 애노테이션 프로세서를 제공합니다.
역할
이 모듈은 JPA 엔티티 클래스에서 Q 클래스(쿼리 DSL을 위한 클래스)를 자동으로 생성합니다. Q 클래스는 QueryDSL의 쿼리를 구성하는 데 필요한 타입 안전한 표현을 제공합니다.
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
의존성 설명
- jakarta.annotation:jakarta.annotation-api는 Jakarta EE의 애노테이션 API입니다.
- 이는 Java EE와 Jakarta EE에서 사용하는 표준 애노테이션을 포함합니다.
역할
QueryDSL의 APT 모듈에서 필요로 하는 애노테이션을 제공하여, JPA 엔티티에 대한 메타데이터를 생성할 수 있도록 돕습니다.
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
의존성 설명
jakarta.persistence:jakarta.persistence-api는 Jakarta EE의 JPA API입니다.
역할
JPA 엔티티 클래스의 메타데이터를 처리하는 데 필요합니다. 이 API는 데이터베이스와의 상호작용을 위한 기본 인터페이스를 제공합니다.
2. JPAConfiguration
QueryDSL을 사용하기 위한 설정 클래스입니다.
JPAConfiguration이 왜 필요한가?
EntityManager 주입
- @PersistenceContext 어노테이션을 사용하여 EntityManager를 주입합니다. EntityManager는 JPA에서 데이터베이스와 상호작용하는 데 필요한 객체로, 데이터베이스의 CRUD(Create, Read, Update, Delete) 작업을 수행하는 데 필수적입니다.
- QueryDSL은 JPA를 기반으로 동작하기 때문에, EntityManager가 필요합니다.
- @PersistenceContext : JPA에서 엔티티 객체의 생명 주기를 관리하는 환경을 의미. 영속성 컨텍스트 내에서 엔티티 객체는 데이터베이스와 동기화되며, 엔티티의 상태 변경이 데이터베이스에 반영된다.
JPAQueryFactory 빈 등록
- @Bean 어노테이션을 통해 JPAQueryFactory 객체를 Spring의 Application Context에 빈으로 등록합니다.
- JPAQueryFactory는 QueryDSL에서 쿼리를 생성하는 데 사용되는 주체로, 이 객체를 통해 타입 안전한 쿼리를 작성할 수 있습니다.
- @Bean : 애플리케이션의 특정 구성 및 객체 생성을 중앙 집중화하여 관리하기 위해 사용됩니다. 이를 통해 개발자는 Spring 컨테이너가 객체의 생명 주기를 관리하도록 하고, 객체 간의 의존성을 주입할 수 있습니다.
의존성 주입
- Spring의 IoC(제어의 역전) 원칙을 따르기 때문에, JPAQueryFactory를 사용하는 클래스에서는 DI(의존성 주입)를 통해 JPAQueryFactory 인스턴스를 쉽게 사용할 수 있습니다.
- 이렇게 하면 코드의 재사용성이 높아지고, 관리가 용이해집니다.
구성의 분리
- 이 설정 클래스를 통해 애플리케이션의 설정과 비즈니스 로직을 분리할 수 있습니다.
- 설정 관련 코드와 비즈니스 로직을 분리하면 코드의 가독성이 높아지고, 유지보수가 용이해집니다.
QueryDSL의 초기화
- JPAQueryFactory는 내부적으로 EntityManager를 사용하여 쿼리를 실행하므로, 이 클래스가 있어야 QueryDSL이 제대로 작동합니다.
- QueryDSL은 이 객체를 통해 쿼리를 빌드하고 실행할 수 있으며, EntityManager와 통합되어 SQL 쿼리로 변환됩니다.
JPAConfiguration 클래스는 QueryDSL을 사용하기 위해 필수적인 설정 클래스로, EntityManager를 주입받아 JPAQueryFactory를 빈으로 등록하려 쿼리 생성을 가능하게 합니다. -> 이러한 설정이 있어야 QueryDSL을 통해 데이터베이스와 안전하고 효율적으로 상호작용할 수 있다.
3. RepositoryCustom
RepositoryCustom 인터페이스는 QueryDSL을 사용하여 데이터베이스와 상호작용하는 데 매우 중요한 역할을 합니다.
RepositoryCustom이 왜 필요한가?
커스텀 쿼리 작성
- QueryDSL은 동적 쿼리를 안전하게 작성할 수 있도록 도와줍니다. 기본 JPA 메서드로는 처리하기 어려운 복잡한 쿼리를 만들 때 이 인터페이스가 필요합니다.
- 예를 들어, findByIdWithUser(Long todoId) 메서드는 특정 todoId에 대한 Todo 엔티티와 해당 사용자 정보를 함께 조회하는 쿼리를 정의하는 데 사용됩니다. QueryDSL의 JPAQueryFactory를 통해 동적 쿼리를 생성할 수 있습니다.
비즈니스 로직과 데이터 접근 분리
- QueryDSL을 사용하면 비즈니스 로직과 데이터 접근 로직을 명확하게 분리할 수 있습니다.
- TodoRepositoryCustom 인터페이스를 구현하여 데이터 접근 로직을 집중화하면 코드의 가독성과 유지보수성이 높아집니다.
타입 안전성
- QueryDSL은 타입 안전성을 제공합니다. 즉, 쿼리를 컴파일 시점에 검사하여 오류를 방지할 수 있습니다.
- TodoRepositoryCustom을 통해 QueryDSL을 구현하면 이러한 타입 안전성을 활용하여 안정적인 데이터 접근을 구현할 수 있습니다.
다양한 쿼리 지원
- QueryDSL은 다양한 쿼리 방식을 지원하며, 이를 통해 복잡한 조인이나 필터링을 쉽게 구현할 수 있습니다.
- TodoRepositoryCustom 인터페이스는 QueryDSL 메서드와 논리를 추가하는 기본 틀을 제공하여, 다양한 쿼리 요구사항을 처리할 수 있게 합니다.
재사용성 및 확장성
- 여러 엔티티에 대한 커스텀 메서드를 정의할 수 있으며, 향후 요구사항이 변경되거나 확장될 때, TodoRepositoryCustom 인터페이스를 통해 쉽게 추가할 수 있습니다.
- 이렇게 하면 QueryDSL의 장점을 살리면서도 구조적으로 잘 정리된 코드를 유지할 수 있습니다.
TodoRepositoryCustom 인터페이스는 QueryDSl을 효과적으로 활용하기 위하 기반을 제공합니다. 그래서 이를 통해 복잡한 쿼리를 작성이 가능하고 비즈니스 로직과 데이터 접근 로직을 분리하며 타입 안전성을 확보하고, 코드의 재사용성과 확장성을 높일 수 있습니다!
4. TodoRepositoryImpl
TodoRepositoryImpl 클래스는 TodoRepositoryCustom 인터페이스를 구현하여 사용자 요구사항에 맞게 커스텀 쿼리 메서드를 제공합니다.
QueryDSL 활용
- 이 클래스는 QueryDSL의 JPAQueryFactory를 사용하여 쿼리를 작성합니다. JPAQueryFactory는 JPA 엔티티를 대상으로 쿼리를 작성하는 데 유용한 도구입니다.
- private final JPAQueryFactory jqf;를 통해 JPAQueryFactory의 인스턴스를 주입받아 쿼리 작성 시 사용할 수 있습니다.
QueryDSL의 타입 안전성 활용
- QueryDSL은 컴파일 타임에 쿼리의 유효성을 검사할 수 있는 기능을 제공합니다. QTodo와 QUser와 같은 Q타입 클래스를 통해 쿼리의 속성을 직접 접근할 수 있으며, 이는 코드의 안정성을 높이고 실수를 줄이는 데 기여합니다. TodoRepositoryImpl 클래스는 이러한 Q타입 클래스를 사용하여 쿼리를 작성합니다.
Fetch Join을 통한 N+1 문제 해결
- .leftJoin(todo.user, user)와 .fetchJoin()을 통해 Todo와 연관된 User 엔티티를 동시에 조회합니다.
- 이는 N+1 문제를 방지하는 데 중요한 역할을 합니다.
- N+1 문제란? : 하나의 쿼리로 데이터를 조회한 후 각 데이터에 대해 추가 쿼리가 실행되는 문제를 말합니다. 이로 인해 성능 저하가 발생할 수 있습니다.
타입 안전성
- QueryDSL을 사용하여 생성된 QTodo와 QUser 객체는 타입 안전성을 제공합니다.
- 즉, 쿼리 작성 시 컴파일 타임에 타입 오류를 체크할 수 있어 런타임 오류를 줄일 수 있습니다.
Optional을 통한 Null 안전성
- fetchOne() 메서드는 단일 결과를 반환하며, 결과가 없을 경우 null을 반환합니다.
- Optional.ofNullable(result)를 사용하여 결과를 Optional로 감싸 null 값을 안전하게 처리합니다. 이를 통해 클라이언트 코드에서 null 체크를 쉽게 할 수 있습니다.
전체적인 흐름
1. 쿼리 생성: QTodo와 QUser를 통해 쿼리를 구성합니다.
2. 조인 수행: Todo 엔티티와 연관된 User 정보를 left join합니다.
3. fetchJoin() 사용: N+1 문제를 해결하기 위해 fetch join을 수행합니다.
4. 조건 지정: todo.id.eq(id)로 조건을 추가하여 특정 todoId에 해당하는 Todo를 찾습니다.
5. 결과 반환: 조회된 Todo 객체를 Optional로 감싸서 반환합니다.
즉 TodoRepositoryImpl 클래스는 QueryDSL을 사용하여 복잡한 쿼리를 간편하고 안전하게 작성이 가능합니다. 그리고 커스텀 메서드를 통해 비즈니스 로직에 맞는 쿼리를 효율적으로 처리가 가능합니다.
'Spring > QueryDSL, JPQL' 카테고리의 다른 글
QueryDSL을 이용한 검색 기능 (0) | 2024.10.07 |
---|---|
JPQL (4) | 2024.10.02 |