반응형
Querydsl이란?
Querydsl 정적 타입을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해 주는 프레임워크
문자열로 작성하거나 XML 파일에 쿼리를 작성하는 대신, Querydsl이 제공하는 플루언트(Fluent) API를 이용해서 쿼리를 생성할 수 있다.
단순 문자열과 비교해서 Fluent API를 사용할 때의 장점은 다음과 같다.
Fluent API의 장점
- IDE의 코드 자동 완성 기능 사용
- 문법적으로 잘못된 쿼리를 허용하지 않음
- 도메인 타입과 프로퍼티를 안전하게 참조할 수 있음
- 도메인 타입의 리팩토링을 더 잘 할 수 있음
Repository 구현
Querydsl을 사용하는 Repository를 구현할 때 JpaRepository와 함께 상속 해 하나의 Repository에서 JPA 와 Querydsl와 함께 사용할 수 있다.
public interface ProductRepository extends JpaRepository<Product, Long>, CustomProductRepository {
// JPA 메서드
public void jpaMethod();
}
public interface CustomProductRepository {
// Querydsl 메서드
public void querydslMethod();
}
public class CustomProductRepositoryImpl extends QuerydslRepositorySupport implements CustomProductRepository {
@Override
public void querydslMethod() {
...
}
}
ProductRepository.jpaMethod();
ProductRepository.querydslMethod();
하지만 본인은 DIP원칙으로 구축하므로 JpaRepository와 QuerydslRepository를 따로 구축한다.
public interface ProductJpaRepository extends JpaRepository<Product, Long>, ProductRepository {
}
public interface ProductRepository {
// JPA 메서드
public void jpaMethod();
}
public class ProductRepositorySupport extends QuerydslRepositorySupport {
// Querydsl 메서드 구현
public void querydslMethod() {
...
}
}
ProductRepository.jpaMethod();
ProductRepositorySupport.querydslMethod();
DIP 원칙을 지키면서 JPA와 Querydsl 메서드를 하나의 Repository에서 함께 쓰는걸 원했지만 어떻게 해도 되지 않았다...
JPA + Querydsl 구축 예제
Config
1. build.gradle 설정
buildscript {
ext {
// Querydsl Plugin 버전
querydslPluginVersion = '1.0.10'
}
repositories {
// Plugin 저장소
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
// Querydsl Plugin 의존성 등록
classpath("gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:${querydslPluginVersion}")
}
}
...
dependencies {
...
// jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// lombok
implementation "org.projectlombok:lombok"
annotationProcessor 'org.projectlombok:lombok'
// querydsl
implementation 'com.querydsl:querydsl-jpa'
implementation 'com.querydsl:querydsl-apt'
annotationProcessor 'com.querydsl:querydsl-apt'
...
}
// Querydsl Plugin 설정
apply plugin: 'com.ewerk.gradle.plugins.querydsl'
// Q파일 생성 위치
def querydslSrcDir = "src/main/generated"
querydsl {
library = "com.querydsl:querydsl-apt"
jpa = true
querydslSourcesDir = querydslSrcDir
}
sourceSets { main.java.srcDirs = ['src/main/java', querydslSrcDir] }
configurations { querydsl.extendsFrom compileClasspath }
clean { delete file(querydslSrcDir) } // gradle clean시 Q파일 삭제
compileQuerydsl { options.annotationProcessorPath = configurations.querydsl }
tasks.withType(JavaCompile) { options.annotationProcessorGeneratedSourcesDirectory = file(querydslSrcDir) }
2. Configuration 클래스 생성
@Configuration
public class BeanConfig {
@PersistenceContext
private EntityManager entityManager; // 엔티티를 관리하는 클래스
@Bean
public JPAQueryFactory jpaQueryFactory() { // JPAQueryFactory Bean 등록
return new JPAQueryFactory(entityManager);
}
}
3. build 설정
엔티티를 생성하고 build해보면 build.gradle에 설정한 querydslSrcDir 경로로 QClass들이 생성된다.
엔티티를 수정하고 build를 해보면 QClass를 찾을 수 없다는 에러가 발생한다.
매번 다시 build project를 하고 서버를 작동시킬 순 없으니 서버 시작 시 gradle clean을 추가해준다.
4. .gitignore 설정
.gitignore 설정을 하지 않으면 git 커밋 시 QClass들까지 커밋하게 된다.
...
### Querydsl ###
src/main/generated/
Repository 생성
생성된 QClass로 query문법을 작성하면 된다.
@Repository
public class ProductRepositorySupport extends QuerydslRepositorySupport {
private final JPAQueryFactory queryFactory;
public ProductRepositorySupport(JPAQueryFactory queryFactory) {
super(Product.class);
this.queryFactory = queryFactory;
}
public Page<ProductDTO> pageList(String searchName, PagingRequest pagingRequest) {
Pageable paging = pagingRequest.pageable();
QueryResults<ProductDTO> result = queryFactory
.select(Projections.constructor(ProductDTO.class,
product.id, product.name, product.price
)).from(product)
.where(searchName(searchName))
.orderBy(product.id.desc())
.offset(paging.getOffset())
.limit(paging.getPageSize())
.fetchResults();
return new PageImpl<>(result.getResults(), paging, result.getTotal());
}
// 상품명 검색(동적 쿼리)
private BooleanExpression searchName(String searchName) {
return searchName != null ? product.name.contains(searchName) : null;
}
}
- Projections.constructor() - 생성자 기반으로 바인딩
- Projections.fields() - Set 기반으로 바인딩
- Where 다중 파라미터(BooleanExpression)를 사용해서 동적 쿼리 적용
전체 소스는 github에서 확인 하실 수 있습니다.
반응형
'Spring' 카테고리의 다른 글
Spring Boot + log4j2 (0) | 2021.10.07 |
---|---|
Spring Boot + Flyway를 이용한 데이터베이스 마이그레이션 (0) | 2021.09.16 |
Spring AOP란? (0) | 2021.09.11 |
Spring REST Docs 구축 예시 (0) | 2021.09.10 |
Spring REST Docs란? (0) | 2021.09.10 |