Querydsl 적용하기

junto·2024년 3월 2일
0

database

목록 보기
4/7
post-thumbnail

Querydsl 필요성

  • 프로젝트를 진행하면서 공간 상세 유형이 추가로 선택할 때마다 검색 조건에 추가하고 싶었다. JPQL로 이를 구현하고 싶었지만, 상세 타입이 하나만 있거나 아예 없거나 이 두가지 경우만 처리할 수 있었다.
  • 세부 사항이 추가될 때마다 서비스 로직에서 분기 처리를 할 수 있겠지만 굉장히 비효율적이다. Querydsl을 사용하면 조건에 따른 동적 쿼리를 손쉽게 처리할 수 있어 적용한 경험을 공유하려고 한다!
public interface SpaceRepository extends JpaRepository<Space, Long> {

    @Query("SELECT s FROM Space s " +
            "WHERE s.spaceType = :spaceType " +
            "AND s.realEstate.address.sido = :sido " +
            "AND s.realEstate.address.sigungu = :sigungu " +
            "AND s.maxCapacity >= :minCapacity " +
            "AND (:detailedType IS NULL OR :detailedType MEMBER OF s.detailedTypes) " +
            "AND s.isDeleted = false")
    Page<Space> findByCriteria(@Param("spaceType") SpaceType spaceType,
                               @Param("sido") String sido,
                               @Param("sigungu") String sigungu,
                               @Param("minCapacity") int minCapacity,
                               @Param("detailedType") DetailedType detailedType,
                               Pageable pageable);
}

Querydsl 왜 사용하는가?

  • 위의 예시처럼 조건에 따른 동적 쿼리를 작성해야 할 때 유용하다.
  • 타입 안전성(Type-Safety)으로 컴파일 오류를 알려준다.
  • SQL 쿼리를 자바 코드로 직접 작성할 수 있어서 가독성이 향상된다.
  • IDE가 자동완성을 제공해서 개발 생산성이 증가한다.

Querydsl 의존성 (SpringBoot 3.2)

implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
annotationProcessor("com.querydsl:querydsl-apt:5.0.0:jakarta")
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
annotationProcessor("jakarta.persistence:jakarta.persistence-api")

Querydsl Q-type

  • 위의 의존성을 주입하여 빌드하면 아래 경로에 Q-type클래스가 생성된다.
  • Q-type은 엔티티 클래스에 대한 타입 안전한 참조를 제공한다. 쿼리를 구성할 때 문자열이 아닌 jvm이 타입을 검증할 수 있는 객체를 제공하는 것이다.
  • 클래스 속성과 구조를 설명해 주는 메타데이터 역할을 한다.
@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer")
public class QAddress extends BeanPath<Address> {

    private static final long serialVersionUID = -279459010L;

    public static final QAddress address = new QAddress("address");

    public final StringPath dong = createString("dong");

    public final StringPath jibunAddress = createString("jibunAddress");

    public final StringPath roadAddress = createString("roadAddress");

    public final StringPath sido = createString("sido");

    public final StringPath sigungu = createString("sigungu");

    public QAddress(String variable) {
        super(Address.class, forVariable(variable));
    }

    public QAddress(Path<? extends Address> path) {
        super(path.getType(), path.getMetadata());
    }

    public QAddress(PathMetadata metadata) {
        super(Address.class, metadata);
    }
}

Querydsl 환경 설정

1. Spring Bean 등록(JPAQueryFactory)

@Configuration // Spring Bean으로 등록
public class QueryDslConfig {

    @PersistenceContext
    private EntityManager em;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(em);
    }
}
  • @PersistenceContext EntityManager 의존성 주입을 요청한다.
  • JPAQueryFactory는 Querydsl 핵심 클래스로 Type-Safe한 쿼리를 생성하고 실행한다. 생성자로 EntityManager를 전달하면 애플리케이션 어디서나 JPAQueryFactory를 주입받아 사용할 수 있게 된다.

2. 사용자 정의 쿼리 메서드 인터페이스

public interface CustomSpaceRepository {
    Page<Space> findBySpaceTypeInSeoulQuerydsl(SpaceType spaceType, Pageable pageable);

    Page<Space> findByCriteriaQuerydsl(SpaceType spaceType, String sido, String sigungu, int minCapacity, Set<DetailedType> detailedTypes, Pageable pageable);
}

3. 사용자 정의 쿼리 메서드 구체클래스

  • 이름 뒤에 Impl을 붙이도록 한다.
@Repository
@RequiredArgsConstructor
public class CustomSpaceRepositoryImpl implements CustomSpaceRepository {

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public Page<Space> findBySpaceTypeInSeoulQuerydsl(SpaceType spaceType, Pageable pageable) {}

    @Override
    public Page<Space> findByCriteriaQuerydsl(SpaceType spaceType, String sido, String sigungu, int minCapacity, Set<DetailedType> detailedTypes, Pageable pageable) {}
}

4. Jpa Repository 확장

  • 사용자 정의 쿼리 메서드 인터페이스를 상속하여 spaceRepository에서 Querydsl 쿼리를 사용할 수 있다.
public interface SpaceRepository extends JpaRepository<Space, Long>, CustomSpaceRepository{}

Querydsl 쿼리

@Override
public Page<Space> findByCriteriaQuerydsl(SpaceType spaceType, String sido, String sigungu, int minCapacity, Set<DetailedType> detailedTypes, Pageable pageable) {
    BooleanExpression conditions = space.spaceType.eq(spaceType)
            .and(space.realEstate.address.sido.eq(sido))
            .and(space.realEstate.address.sigungu.eq(sigungu))
            .and(space.maxCapacity.goe(minCapacity))
            .and(space.isDeleted.isFalse());

    if (detailedTypes != null && !detailedTypes.isEmpty()) {
        conditions = conditions.and(space.detailedTypes.any().in(detailedTypes));
    }

    List<Space> spaces = jpaQueryFactory.selectFrom(space)
            .where(conditions)
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetch();

    Long total = jpaQueryFactory
            .select(space.count())
            .from(space)
            .where(conditions)
            .fetchOne();
    long totalCount = total != null ? total : 0;

    return new PageImpl<>(spaces, pageable, totalCount);
}
  • Querydsl은 문법이 굉장히 직관적이라 처음 봐도 어떤 일이 벌어지고 있는지 유추가 가능하다.
  • where 조건
    • 비교 연산자
      • .eq(): eqals
      • .ne(): not equals
      • .lt(): less than
      • .le(): less than or equals
      • .gt(): greater than
      • .ge(): greater than or equals
    • 논리 연산자
      • .and(), .or(), .not()
    • 널 체크
      • .isNull(), .isNotNull
    • 문자열 연산
      • .startsWith(), .endsWith(), .contains(), .like()
    • 컬렉션 처리
      • .isEmpty(), .isNotEmpty()
      • .any(): 엔티티 컬렉션 속성 중 하나라도 주어진 조건을 만족하는 지 검사
      • .all(): 엔티티 컬렉션 속성이 모두 주어진 조건을 만족하는 지 검사
      • .none(): 엔티티 컬렉션 속성 중 어떤 것도 주어진 조건을 만족하지 않는지 검사
      • .selectCase(): 조건에 따라 다른 결과 반환
      • .groupBy(), .having()
    • 서브쿼리
      • .in(), .exists()

참고자료

profile
꾸준하게

0개의 댓글