QueryDSL 적용 과정 및 사용 방법

coldrice99·2024년 11월 14일
0
post-thumbnail

개요

이번 작업에서는 JPQL로 작성된 쿼리를 QueryDSL로 변경하여 동적 쿼리 작성N+1 문제 해결을 목표로 했습니다. QueryDSL은 직관적인 메서드 체인을 통해 동적 쿼리를 작성할 수 있는 라이브러리로, 엔티티 간의 조인을 명확하게 설정하여 JPA를 활용한 쿼리 성능을 개선할 수 있습니다.

QueryDSL 적용 과정

  1. 의존성 추가 및 Q 클래스 생성

    • build.gradle 파일에 QueryDSL 의존성을 추가합니다.

      dependencies {
          implementation 'com.querydsl:querydsl-jpa'
          implementation 'com.querydsl:querydsl-apt'
      }
      
      configurations {
          querydsl.extendsFrom compileClasspath
      }
      
      def querydslDir = "$buildDir/generated/querydsl"
      
      sourceSets {
          main.java.srcDir querydslDir
      }
      
      compileQuerydsl {
          options.annotationProcessorPath = configurations.querydsl
      }
      
      tasks.withType(JavaCompile) {
          options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
      }
    • 이후, ./gradlew compileQuerydsl 명령어를 실행하여 각 엔티티의 Q 클래스를 생성합니다. 예를 들어 Todo 엔티티가 있다면, QueryDSL이 QTodo라는 이름의 클래스를 자동 생성합니다. 이 클래스는 QueryDSL에서 Todo 필드에 접근할 수 있는 객체입니다.

  2. JPAQueryFactory 빈 설정

    • QueryDSL을 사용하기 위해 JPAQueryFactory 빈을 등록해야 합니다. 이를 위해 JPAConfiguration이라는 설정 클래스를 생성합니다.
      @Configuration
      public class JPAConfiguration {
          
          @PersistenceContext
          private EntityManager entityManager;
          
          @Bean
          public JPAQueryFactory jpaQueryFactory() {
              return new JPAQueryFactory(entityManager);
          }
      }
    • 위 설정으로 JPAQueryFactory가 빈으로 등록되어, QueryDSL을 통해 JPA의 동적 쿼리를 작성할 수 있게 됩니다.
  3. Custom Repository 생성

    • QueryDSL을 적용할 메서드를 Repository에 추가하려면, Custom RepositoryImplementation 클래스를 생성해야 합니다.

      Custom Repository Interface (TodoRepositoryCustom):

      public interface TodoRepositoryCustom {
          Optional<Todo> findByIdWithUser(Long todoId);
      }

      Custom Repository Implementation (TodoRepositoryCustomImpl):

      @Repository
      public class TodoRepositoryCustomImpl implements TodoRepositoryCustom {
      
          private final JPAQueryFactory queryFactory;
      
          public TodoRepositoryCustomImpl(JPAQueryFactory queryFactory) {
              this.queryFactory = queryFactory;
          }
      
          @Override
          public Optional<Todo> findByIdWithUser(Long todoId) {
              QTodo todo = QTodo.todo;
              QUser user = QUser.user;
              
              Todo result = queryFactory.selectFrom(todo)
                  .leftJoin(todo.user, user).fetchJoin()
                  .where(todo.id.eq(todoId))
                  .fetchOne();
              
              return Optional.ofNullable(result);
          }
      }
      • QTodoQUser는 QueryDSL이 자동 생성한 클래스입니다. 이를 통해 TodoUser 엔티티의 필드에 접근할 수 있습니다.
      • leftJoin(todo.user, user).fetchJoin()을 통해 TodoUser 엔티티를 조인하여 N+1 문제를 방지하고 한 번의 쿼리로 데이터를 가져옵니다.
      • fetchOne()을 사용하여 단일 결과를 가져오도록 구현했습니다.
  4. Main Repository에 Custom Repository 확장

    • 기존 TodoRepositoryTodoRepositoryCustom을 상속하도록 하여 QueryDSL 메서드를 사용할 수 있게 설정합니다.
      public interface TodoRepository extends JpaRepository<Todo, Long>, TodoRepositoryCustom {
          // JpaRepository의 기본 메서드 외에 QueryDSL 메서드 사용 가능
      }
  5. Service에서 QueryDSL 메서드 사용

    • 이제 TodoService에서 QueryDSL로 구현한 findByIdWithUser 메서드를 사용할 수 있습니다.

      @Service
      public class TodoService {
      
          private final TodoRepository todoRepository;
      
          @Autowired
          public TodoService(TodoRepository todoRepository) {
              this.todoRepository = todoRepository;
          }
      
          public TodoResponse getTodo(long todoId) {
              Todo todo = todoRepository.findByIdWithUser(todoId)
                      .orElseThrow(() -> new InvalidRequestException("Todo not found"));
              
              User user = todo.getUser();
              
              return new TodoResponse(
                      todo.getId(),
                      todo.getTitle(),
                      todo.getContents(),
                      todo.getWeather(),
                      new UserResponse(user.getId(), user.getEmail(), user.getNickname()),
                      todo.getCreatedAt(),
                      todo.getModifiedAt()
              );
          }
      }

요약 및 느낀 점

  1. Q 클래스 사용: QueryDSL은 Q 클래스를 통해 엔티티 필드에 접근하며 동적 쿼리를 작성할 수 있도록 해줍니다.
  2. JPAQueryFactory 빈 설정: QueryDSL을 사용하기 위해 JPAQueryFactory 빈을 설정하는 과정이 필요했습니다.
  3. 조인 및 fetchJoin 사용: leftJoin(...).fetchJoin()으로 연관 엔티티 데이터를 한 번의 쿼리로 가져오며, N+1 문제를 방지할 수 있었습니다.

처음 QueryDSL을 적용하는 과정이 어려웠지만, 앞으로 더 복잡한 동적 쿼리를 쉽게 구현할 수 있는 좋은 방법이라는 점을 느꼈습니다. JPA와 QueryDSL을 함께 사용하면 쿼리 작성이 더 유연해지고 성능 최적화도 가능해지므로, 더 다양한 상황에서 활용해볼 수 있을 것 같습니다.

profile
서두르지 않으나 쉬지 않고

0개의 댓글