Spring

Querydsl 설명 및 예제

Beekei 2021. 9. 15. 15:16
반응형

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에서 확인 하실 수 있습니다.

 

GitHub - devbeekei/querydsl: JPA + Querydsl 예제

JPA + Querydsl 예제. Contribute to devbeekei/querydsl development by creating an account on GitHub.

github.com

 

반응형