-
[Spring Boot] Spring Data JPA + QueryDSL 설정Spring Boot/기타 2023. 1. 15. 11:35반응형
QueryDSL 이란?
쿼리를 문자가 아닌 코드로 작성해도, 쉽고 간결하며 그 모양도 쿼리와 비슷하게 개발 할 수 있는 프로젝트가 바로 QueryDSL입니다. QueryDSL도 Criteria처럼 JPQL 빌더 역할을 하는데 JPA Criteria를 대체할 수 있습니다.
QueryDSL은 오픈소스 프로젝트이며, 처음에는 HQL(Hibernate Query Language)을 코드로 작성할 수 있도록 해주는 프로젝트로 시작해서 지금 JPA, JDO, JDBC, Lucene, Hibernate Search, MongoDB, Collections 및 RDFBean을 지원합니다.
참고 - http://querydsl.com/static/querydsl/4.4.0/reference/html_single/
QueryDSL 설정
1. gradle 설정
build.gradle
plugins { id 'java' id 'org.springframework.boot' version '2.7.7' id 'io.spring.dependency-management' version '1.0.15.RELEASE' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { // Spring Boot implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' testImplementation 'org.springframework.boot:spring-boot-starter-test' // Data implementation 'mysql:mysql-connector-java' // QueryDSL 라이브러리 implementation 'com.querydsl:querydsl-core' // QueryDSL JPA 라이브러리 implementation 'com.querydsl:querydsl-jpa' // QueryDSL 관련된 쿼리 타입(QClass)을 생성할 때 필요한 라이브러리로, annotationProcessor을 사용하여 추가 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' // Lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' } tasks.named('test') { useJUnitPlatform() } // clean task를 실행 시 QClass를 삭제 clean { // QClass가 생성되는 위치 delete file('src/main/generated') }
annotationProcessor는 자바 컴파일러 플러그인 일종으로, 컴파일 단계에서 프로젝트 내의 @Entity(javax.persistence.Entity) 애노테이션을 선언한 클래스를 탐색하여 com.querydsl.apt.jpa.JPAAnnotationProcessor를 통해 쿼리 타입(QClass)을 생성합니다. 생성된 쿼리 타입(QClass)은 com.querydsl.core.QueryFactory에 주입하여 사용합니다.
참고 - http://honeymon.io/tech/2020/07/09/gradle-annotation-processor-with-querydsl.html
2. Configuration 설정
JPAQueryFactory를 주입받아 QueryDSL을 사용할 수 있도록 설정하세요.
QueryDslConfig.java
package com.example.querydsl.config; import com.querydsl.jpa.impl.JPAQueryFactory; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing @Configuration public class QueryDslConfig { @PersistenceContext private EntityManager entityManager; public QueryDslConfig() { } @Bean public JPAQueryFactory jpaQueryFactory() { return new JPAQueryFactory(this.entityManager); } }
3. Spring Data Jpa Custom Repository 적용
Spring Data Jpa에서는 Custom Repository를 JpaRepository 상속 클래스에서 사용할 수 있도록 지원합니다.
- TodoRepository는 JpaRepository 인터페이스를 상속받아서 기본적인 메서드를 사용할 수 있도록 정의한 인터페이스
- TodoRepositoryCustom은 QueryDSL을 사용하기 위한 메소드를 정의한 인터페이스
- TodoRepositoryImpl를 TodoRepositoryCustom 인터페이스에서 정의된 메소드를 구현하는 클래스
Todo.java
package com.example.querydsl.domain.entity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @Builder @AllArgsConstructor @NoArgsConstructor @ToString @Entity @Table(name = "todo") public class Todo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id", nullable = false) private Long id; @Column(name="title") private String title; @Column(name="description") private String description; @Column(name="completed") private Boolean completed; }
TodoRepository.javapackage com.example.querydsl.domain.repository; import com.example.querydsl.domain.entity.Todo; import org.springframework.data.jpa.repository.JpaRepository; public interface TodoRepository extends JpaRepository<Todo, Long>, TodoRepositoryCustom { }
TodoRepositoryCustom.javapackage com.example.querydsl.domain.repository; import com.example.querydsl.domain.entity.Todo; import java.util.List; public interface TodoRepositoryCustom { /** * QueryDSL을 사용하여 To-Do 목록 조회 */ List<Todo> getTodos(Todo todo); }
TodoRepositoryImpl.javapackage com.example.querydsl.domain.repository; import com.example.querydsl.domain.entity.QTodo; import com.example.querydsl.domain.entity.Todo; import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @RequiredArgsConstructor @Repository public class TodoRepositoryImpl implements TodoRepositoryCustom { private final JPAQueryFactory jpaQueryFactory; @Override public List<Todo> getTodos(Todo todo) { QTodo qtodo = QTodo.todo; return jpaQueryFactory .selectFrom(qtodo) .fetch(); } }
4. 단위 테스트
TodoRepositoryTest.java
package com.example.querydsl.domain.repository; import static org.junit.jupiter.api.Assertions.assertTrue; import com.example.querydsl.domain.entity.Todo; import java.util.List; import javax.persistence.EntityManager; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; @Slf4j @SpringBootTest @ActiveProfiles("local") @Disabled class TodoRepositoryTest { @Autowired private TodoRepository todoRepository; @Autowired EntityManager entityManager; @BeforeEach void init() { setUpTodos(); } @Transactional @DisplayName("getTodos_QueryDSL을 사용하여 To-Do 목록 조회") @Test void testGetTodos() { // Given Todo todo = Todo.builder() .title("Title Test") .description("Description Test") .completed(true) .build(); // When List<Todo> todos = todoRepository.getTodos(todo); // Then log.debug("todos:[{}]", todos); assertTrue(!todos.isEmpty()); } /** * To-Do 목록을 설정 */ @Disabled void setUpTodos() { String title; String description; Boolean completed; for (int a = 1; a <= 100; a++) { title = "Title Test" + a; description = "Description Test" + a; if (a % 2 == 0) { completed = true; } else { completed = false; } insertTodo(title, description, completed); } entityManager.flush(); entityManager.clear(); } /** * To-Do 한 건을 저장 */ @Disabled void insertTodo( String title, String description, Boolean completed) { todoRepository.save( Todo.builder() .title(title) .description(description) .completed(completed) .build()); } }
5. 단위 테스트 확인
testGetTodos() 실행 시 To-Do 목록이 조회되는지 확인하세요.
소스 코드는 Github Repository - https://github.com/tychejin1218/blog/tree/main/query-dsl 를 참조하세요.
반응형'Spring Boot > 기타' 카테고리의 다른 글
[Spring Boot] Amazon S3로 파일 업로드 및 삭제 (0) 2023.01.27 [Spring boot] Replication Database 환경에서 Master/Slave DataSource 구성하기 (2) 2023.01.17 [Spring Boot] 유효성 검사 처리 (Custom Validation) (0) 2022.06.06 [Spring Boot] 에러 메시지 처리 (Custom Exception) (0) 2022.06.06 [Spring Boot] MockMvc 사용 시 한글이 깨지는 현상 (0) 2021.08.29