프로젝트/아카이뷰

[Spring boot] Query DSL 동적 쿼리

cks._.hong 2024. 1. 23. 08:52

Query DSL 동적 쿼리 왜 궁금했을까❓

Query DSL을 아카이뷰 프로젝트에 적용하는 과정을 보도록 하겠다.
 

[Spring boot] Query DSL

Query DSL 왜 궁금했을까❓ArchiVIEW의 검색 API를 만들어야 했는데 필터가 존재하여 Spring Data JPA를 사용하기에는 경우의 수를 고려하여 쿼리를 만들어야 했다. 상당히 비효율적이라 생각하여 동적 쿼

pslog.co.kr

위 포스팅을 통해 Query DSL에 대한 개념을 이해할 수 있다.

Query DSL 실습

Gradle 설정(Spring Boot 3.x)

Query DSL은 JPA 표준이 아니기 때문에 별도로 라이브러리를 추가해야 한다.
// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
  • 프로젝트를 실행해보면 build/generated/annotationProcessor에 QClass가 생성된 것을 확인할 수 있다.

  • Query DSL을 사용하기 위해서는 EntityManager와 JPAQueryFactory를 Bean으로 등록해야 한다.
@Configuration
public class QueryDslConfig {
    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}
  • QueryDSL은 JPAQueryFactory 객체를 이용하여 사용하는데 EntityManager를 파라미터로 받아 사용한다.
  • EntityManager는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하여 Thread간에 공유가 되서는 안된다. 이를 해결하기 위해 @PersistenceContext를 통해 Thread-safe 상태를 유지한다.

RecuritRepository

public interface RecruitRepository extends JpaRepository<Recruit, Integer>, RecruitRepositoryCustom {
}
  • Spring Data JPA에서 기본적으로 제공하는 기능과 QueryDSL의 Custom 기능들을 하나의 인터페이스에서 사용할 수 있도록 했다.

RecuritRepositoryCustom

public interface RecruitRepositoryCustom {
    List<Recruit> searchAll(RecruitDto.DetailListRequestDto requestDto);
}

RecuritRespositoryImpl

@RequiredArgsConstructor
public class RecruitRepositoryImpl implements RecruitRepositoryCustom {
    private final JPAQueryFactory factory;
    
    @Override
    public List<Recruit> searchAll(RecruitDto.DetailListRequestDto requestDto) {
        BooleanBuilder builder = new BooleanBuilder();
        QRecruit recruit = QRecruit.recruit;

        StringExpression start = Expressions.stringTemplate("FUNCTION('DATE_FORMAT', {0}, '%Y-%m')", recruit.start);
        StringExpression end = Expressions.stringTemplate("FUNCTION('DATE_FORMAT', {0}, '%Y-%m')", recruit.end);
		
        // 회사 필터
        if(requestDto.getCompanyId() != 0) {
            builder.and(recruit.company.id.eq(requestDto.getCompanyId()));
        }

        return factory
                .selectFrom(recruit)
                .join(recruit.company).fetchJoin()
                .where(builder
                        .and(start.eq(requestDto.getDate()).or(end.eq(requestDto.getDate()))))
                .fetch();
    }
}
  1. BooleanBuilder
    • 동적 쿼리를 생성하기 위해 사용하는 기능
  2. StringExpression
    • 채용 날짜 비교를 위한 변환
  3. selectFrom 
    • recurit 테이블로부터 조회
  4. join
    • inner join 수행
  5. fetchJoin
    • N + 1 문제를 개선하고 성능 향상을 위해 fetch join 수행
  6. where
    • BooleanBuilder와 StringExpression을 활용하여 조건문 설정
  7. fetch
    • Query를 생성하고 결과 반환