기존 QueryDsl 없이 NativeQuery 로 작성한 리포지토리 메서드를 리팩토링 하다가 발생한 에러이다.
@SpringBootTest 는 스프링을 띄워서 의존성 주입을 다 받기 때문에 상관없지만
리포지토리 테스트는 대부분 @DataJpaTest를 사용하기 때문에 QueryDsl 관련 빈을 주입받지 못해 발생한 에러였다.
문제 해결
아래 두가지를 순서대로 따라하니 문제가 해결되었다.
1. QueryDslConfig 작성
QueryDslConfig
@Configuration
public class QueryDslConfig {
@PersistenceContext
public EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
2. Test 에 @Import 추가
Test
@DataJpaTest
@Import(QueryDslConfig.class)
class ProductRepositoryTest {
...
}
QueryDsl은 하이버네이트 쿼리 언어(HQL: Hibernate Query Language)의 쿼리를 타입에 안전하게
생성 및 관리해주는 프레임워크이다.
QueryDsl 과 SpringDataJPA 를 함께 사용하여 기존 JPA 문제점인 복잡한 쿼리 및 동적 쿼리 작성의 한계를 보완할 수 있다.
[ QueryDsl의 장점 ]
쿼리를 자바 코드로 작성하므로 문법 오류를 컴파일 시점에 잡아낼 수 있다.
복잡한 쿼리나 동적 쿼리를 편리하게 작성할 수 있다.
인텔리제이와 같은 IDE의 자동 완성 기능의 도움을 받을 수 있다.
아래의 두 코드는 동일하게 작동하는 코드이다.
위는 NativeQuery 인데 문자열로 쿼리를 작성하기 때문에 실수의 여지가 많다.
아래처럼 QueryDsl 사용 시 자바 코드로 쿼리를 작성하므로 컴파일 시점에서 문법 오류를 잡아낼 수 있다.
@Query("select o from Order o where o.registeredDateTime >= :startDateTime" +
" and o.registeredDateTime < :endDateTime" +
" and o.orderStatus = :orderStatus")
List<Order> findOrdersBy(@Param("startDateTime") LocalDateTime startDateTime,
@Param("endDateTime") LocalDateTime endDateTime,
@Param("orderStatus") OrderStatus orderStatus);
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.8'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
//querydsl 추가
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
}
apply plugin: "com.ewerk.gradle.plugins.querydsl"
//querydsl 추가
implementation 'com.querydsl:querydsl-jpa'
//querydsl 추가
implementation 'com.querydsl:querydsl-apt'
//querydsl 추가 시작
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
library = "com.querydsl:querydsl-apt"
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
configurations {
querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
//querydsl 추가 끝
2. gradle 의 Annotation processor 와 Querydsl
간단히 요약하자면 기존엔 Querydsl 라이브러리를 쓰기 위해서는 gradle 의 plugin 을 써야했지만
// querydsl
implementation "com.querydsl:querydsl-core"
implementation "com.querydsl:querydsl-jpa"
// querydsl JPAAnnotationProcessor 사용 지정
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
// java.lang.NoClassDefFoundError(javax.annotation.Entity) 발생 대응
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
// java.lang.NoClassDefFoundError (javax.annotation.Generated) 발생 대응
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
이렇게만 추가하고 코끼리를 눌러서 gradle 을 최신화 해준다.
이후 아래의 순서대로 눌러준다.
그리고 generated 의 annotationProcessor 폴더를 확인해서 Q 클래스 생성을 확인하면 성공이다.
하지만 JPA 와 함께 사용하기 위해서는 사용자 정의 리포지토리(CustomRepository) 설정이 필요하다.
[ 사용자 정의 리포지토리 사용법 ]
QueryDslConfig 작성
사용자 정의 인터페이스 작성
사용자 정의 인터페이스 구현체 생성
스프링 데이터 리포지토리에 사용자 정의 인터페이스 상속
그림으로 표현하면 아래와 같다.
1. QueryDslConfig
가장 먼저 QueryDsl 을 JPA 와 함께 사용하기 위해 필요한 구현체인 JPAQueryFactory 의 빈 등록을 위한 Config 설정이다.
@Configuration
public class QueryDslConfig {
@PersistenceContext
public EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
2. 인터페이스 PostRepositoryCustom
사용자 정의 리포지토리를 설정해야한다.
public interface PostRepositoryCustom {
List<Post> getList(PostSearch postSearch);
}
3. 구현체 PostRepositoryImpl
@Repository
@RequiredArgsConstructor
public class PostRepositoryImpl implements PostRepositoryCustom{
private final JPAQueryFactory jpaQueryFactory;
@Override
public List<Post> getList(PostSearch postSearch) {
return jpaQueryFactory.selectFrom(QPost.post)
.limit(postSearch.getSize())
.offset(postSearch.getOffset())
.orderBy(QPost.post.id.desc())
.fetch();
}
}
3. PostRepository 에 PostRepositoryCustom 상속
public interface PostRepository extends JpaRepository<Post, Long>, PostRepositoryCustom {
}