프로젝트/아카이뷰
[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();
}
}
- BooleanBuilder
- 동적 쿼리를 생성하기 위해 사용하는 기능
- StringExpression
- 채용 날짜 비교를 위한 변환
- selectFrom
- recurit 테이블로부터 조회
- join
- inner join 수행
- fetchJoin
- N + 1 문제를 개선하고 성능 향상을 위해 fetch join 수행
- where
- BooleanBuilder와 StringExpression을 활용하여 조건문 설정
- fetch
- Query를 생성하고 결과 반환