업무에서 Querydsl을 사용하게 될 것 같아서 간단하게 알아보는 시간을 가져 보았다.
Querydsl를 짧게 표현하자면 SQL, JPQL등 쿼리를 코드로 작성하게 해주는 빌더 오픈소스 프레임워크이다.
소개
배경
- Querydsl을 사용하기 이전에는 query를 문자열로 작성해야 했다. JPQL을 생각해 보면 아래와 같이 작성하고 있었다.
- @query("select a from Test a") Optional<Test> findAll();
- 이와 같이 작성하면 쿼리를 문자열로 작성하다보니 실수할 수 있고 또 문제가 컴파일 때 발생하지 않고 실행 시에만 문제를 찾을 수 있다는 단점이 있었다.
- 이를 해결하고자 Querydsl은 자바코드로 쿼리를 작성할 수 있게 해주어 개발자의 실수를 줄일고 컴파일 타임에 오류를 찾을 수 있도록 하여 문제를 해결하였다.
- HQL 뿐만아니라 JPA, JDO, JDBC, Lucene, Hibernate Search, MongoDB, Collections 및 RDFBean 다양한 곳에서 지원한다.
원칙
- 타입 안전성 (Type safety)
- 핵심 원칙으로 쿼리는 도메인 유형의 속성을 반영하는 생성된 쿼리 유형을 기반으로 ㄱ구성된다. 또한 메서드 호출은 완전히 형식이 안전한 방식으로 구성된다.
- 일관성
- 쿼리 paths 및 operations은 모든 구현에서 동일하며 쿼리 인터페이스에도 공통 기본 인터페이스를 가지고 있다.
설정하기
Querydsl을 사용하기 위해서는 기본적인 설정이 필요하다. 빌드 툴에 따라서 방법이 다르다!
메이븐
메이븐에서는 pm.xml에 두가지 작업을 해주어야 한다.
- 종속성 추가
- querydsl-apt모듈은 Querydsl의 Annotation Porcessor Tool(APT)이다.
- 개발자가 작성한 Querydsl 쿼리 클래스를 분석하여 쿼리를 생성해 주는 역할
- 해당 쿼리는 querydsl-core 모듈에서 사용된다.
- querydsl-jpa모듈은 JPA와 함께 사용하기 위한 Querydsl 모듈이다.
- 해당 모듈에는 querydsl-core 모듈이 내포되어 있다.
- 쿼리를 작성하고 실행하는데 필요한 기능을 제공한다.
- querydsl-apt모듈은 Querydsl의 Annotation Porcessor Tool(APT)이다.
- <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>${querydsl.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> <version>${querydsl.version}</version> </dependency>
- apt 플러그인 설정
- JPAAnnotationProcessor는 javax.persistence.Entity 주석이 달린 도메인 유형을 찾아 쿼리타입을 생성해준다. (만약 Hibernate 어노테이션을 사용한다면, APT프로세서를 com.querydsl.apt.hibernate.HibernateAnnotationProcessor 으로 변경해주어야 한다.)
- mvn clean install을 진행하면, target/generated-sources/java 디렉터리에 QClass(Query 타입)이 생성된다. 만약 이클립스를 사용할 경우, mvn eclipse:eclipse 를 실행하면 target/generated-sources/java 디렉터리가 소스 폴더에 추가된다.
- 생성된 Query 타입을 이용하면 JPA 쿼리 인스턴스와 쿼리 도메인 모델 인스턴스를 생성할 수 있다.
- <project> <build> <plugins> ... <plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.1.3</version> <executions> <execution> <goals> <goal>process</goal> </goals> <configuration> <outputDirectory>target/generated-sources/java</outputDirectory> <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor> </configuration> </execution> </executions> </plugin> ... </plugins> </build> </project>
그래들
그래들은 공식문서에 나와있지 않아 여러 블로그들을 참고하였다.
build.gradle에 아래와 같이 종속성들을 추가해 주면 된다.
dependencies {
//querydsl
implementation "com.querydsl:querydsl-jpa"
// implementation "com.querydsl:querydsl-core"
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa" // querydsl JPAAnnotationProcessor 사용 지정
annotationProcessor "jakarta.annotation:jakarta.annotation-api" // java.lang.NoClassDefFoundError (javax.annotation.Generated) 대응 코드
annotationProcessor "jakarta.persistence:jakarta.persistence-api" // java.lang.NoClassDefFoundError (javax.annotation.Entity) 대응 코드
}
def generated = "$buildDir/generated/querydsl"
// querydsl QClass 파일 생성 위치를 지정
tasks.withType(JavaCompile) {
options.getGeneratedSourceOutputDirectory().set(file(generated))
}
// java source set 에 querydsl QClass 위치 추가
sourceSets {
main.java.srcDirs += [ generated ]
}
// gradle clean 시에 QClass 디렉터리 삭제
clean {
delete file(generated)
}
- 메이븐과 마찬가지로 querydsl-jpa와 querydsl-apt를 추가해주면 된다.
- gradle clean complieJava를 진행하면 지정해 둔 경로에 QClass가 생성된다.
- 이때 querydsl-apt는 APT이기 때문에 annotationProcessor로 등록해 주면 된다.
annotationProcessor "jakarta.annotation:jakarta.annotation-api" // java.lang.NoClassDefFoundError (javax.annotation.Generated) 대응 코드
annotationProcessor "jakarta.persistence:jakarta.persistence-api" // java.lang.NoClassDefFoundError (javax.annotation.Entity) 대응 코드
- 이 두 코드는 Q파일을 찾지 못해 발생하는 오류인 java.lang.NoClassDefFoundError(javax.annotation.Entity / javax.annotation.Generated)에러에 대응하기 위한 코드이다.
def generated = "$buildDir/generated/querydsl"
- 빌듯이 querydsl Q클래스들을 위치시킬 경로
JavaCompile
compileJava {
options.compilerArgs << '-Aquerydsl.generatedAnnotationClass=javax.annotation.Generated'
}
- 해당 코드는 Q파일 내 Generated를 import 할 때 Java 9에 있는 javax.annotation.processing.Generated로만 import가 되게 된다. Java 9 버전 이하 버전일 경우 넣어줘야 하는 것으로 보인다.
soruceSets
// java source set 에 querydsl QClass 위치 추가
sourceSets {
main.java.srcDirs += [ generated ]
}
- 빌듯이 QClass 소스도 함께 build 하기 위해서 sourceSets 추가
tasks.withType
// querydsl QClass 파일 생성 위치를 지정
tasks.withType(JavaCompile) {
options.getGeneratedSourceOutputDirectory().set(file(generated))
}
- https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html#org.gradle.api.tasks.compile.CompileOptions:generatedSourceOutputDirectory
- 설정으로 QClass의 파일 생성 위치를 지정하는 코드이다.
clean
// gradle clean 시에 QClass 디렉터리 삭제
clean {
delete file(generated)
}
- build clean시에 생성되었던 QClass를 모두 삭제시키는 코드이다.
일단 사용해 보기
Querydsl은 생각보다 간단하게 사용할 수 있다.
- 퀴리 타입 생성하기 (QClass 이용)
- 아래 두 가지 방법 중 한 가지를 택하면 된다.
- QEntity entity = QEntity.entity;
- QEntity entity = new QEntity(”myTestEntity”);
- 아래 두 가지 방법 중 한 가지를 택하면 된다.
- JPAQueryFactory 생성하기
- 생성하기 위해서는 EntityManager를 필요로 한다.
- 쿼리 사용하기
@SpringBootTest
public class EntityTest {
@Autowired
EntityManager em;
QEntity entity = QEntity.entity;
@Test
void selectTest() {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
Entity findEntity= queryFactory
.selectFrom(entity)
.where(entity .id.eq("test"))
.fetchOne();
assertThat(findFactory.getId()).isEqualTo("test");
}
}
DI 주입하기
Querydsl을 사용하는 모든 클래스에서 JPAQueryFactory를 생성하기보다 DI를 통해 간편하게 사용하도록 만들 수 있다.
package com.pno.web;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Configuration
public class QuerydslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
}
Repository 만들기
기존에 사용하던 JpaRepository의 기능들 또한 모두 사용해야 하기에 Spring data의 커스텀 레파지토리 만들기를 사용하여 custom Repository를 추가하여 준다. (https://docs.spring.io/spring-data/jpa/docs/2.1.3.RELEASE/reference/html/#repositories.custom-implementations)
이러한 패턴으로 생성하게 되면 사용하는 useRepository에서 모두 사용할 수 있게 된다.
- 커스텀 레파지토리는 interface로 Querydsl을 사용하여 구현하고자 하는 메서드들을 작성하면 된다.
- 이후 커스텀 레파지토리를 구현하여 실제 querydsl을 사용하여 쿼리를 구현한다. (이때 반드시 커스텀 레파지토리를 구현하는 클래스에는 Impl을 붙여주어야 한다.)
- 또한 구현 클래스에는 JPAQueryFactory와 사용하고자 하는 QClass가 필요하다.
public interface TestRepositoryCustom {
Entity findEntityOnByEntityId(String entityId);
}
import com.example.web.domain.entity;
import com.example.web.domain.QEntity;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
RequiredArgsConstructor
public class TestRepositoryCustomImpl implements TestRepositoryCustom {
// 반드시 필요
private final JPAQueryFactory jpaQueryFactory;
QEntity entity = QEntity.entity;
@Override
public Entity findFactoryOnByFactoryId(String entityId) {
return jpaQueryFactory
.select(entity)
.from(entity)
.where(entity.id.eq(entityId))
.fetchOne();
}
}
spring data에서 제공하는 QuerydslRepositorySupport 클래스를 이용하는 경우도 있는데 이경우는 pageable도 제공하는 등 다양한 추가 기능들이 있다. 만약 사용하고 싶다면 impl에서 추가적으로 QuerydslRepositorySupport를 상속받아서 생성자에 super(Entity);를 추가해주기만 하면 된다.
import com.example.web.domain.entity; import com.example.web.domain.QEntity; import com.querydsl.jpa.impl.JPAQueryFactory; import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; public class TestRepositoryCustomImpl extends QuerydslRepositorySupport implements TestRepositoryCustom { // 반드시 필요한 곳 private final JPAQueryFactory jpaQueryFactory; QEntity entity = QEntity.entity; @Autowired public FactoryRepositoryCustomImpl(JPAQueryFactory jpaQueryFactory) { super(Factory.class); this.jpaQueryFactory = jpaQueryFactory; } @Override public Entity findFactoryOnByFactoryId(String entityId) { return jpaQueryFactory .select(entity) .from(entity) .where(entity.id.eq(entityId)) .fetchOne(); } }