본문 바로가기
데이터 접근 기술

QueryDSL 적용 방법 알아보기.

by oncerun 2023. 2. 6.
반응형

Querydsl은 많은 프로젝트에 검증받은 프레임워크다.

 

jpql을 래핑 하여 사용한다는 점에서 jpql의 한계를 우회하기 위해 성능 악화에 영향을 주는 기능도 있는 것으로 보인다.

 

추가적으로 Querydsl을 적용하는 방법도 다양하기 때문에 여러 가지 방법을 알아보고 적합한 방법으로 적용하는 것도 필요하다.

 

우선 어떤 방식으로 적용할 것인지 어떤 방법이 있는지 알아보려고 한다.

 

Spring Data JPA 공식문서에 있는 Querydsl Extension을 알아보자.

 

해당 섹션에서는 QuerydslPredicateExecutor<T> 를 통해 조회조건을 통해 검색할 수 있는 방법을 지원한다고 한다.

public interface QuerydslPredicateExecutor<T> {

    Optional<T> findById(Predicate predicate);  (1)

    Iterable<T> findAll(Predicate predicate);   (2)

    long count(Predicate predicate);            (3)

    boolean exists(Predicate predicate);        (4)

    // … more functionality omitted.
}

사용 시 해당 인터페이스를 repository의 상속하면 QuerydslPredicateExecutor에 정의된 메서드를 사용할 수 있다.

findAll 기준으로 정렬 기준을 넘기거나, Pageable 타입의 매개변수도 넘길 수 있다.

한계는 매우 명확해보인다. 조인을 할 수가 없다. 그리고 Predicate를 생성해서 넘겨주는 부분에서 layer 간의 결합이 발생한다는 것이다.

 

Querydsl Extension에는Querydsl Web Support라는 섹션이 존재하는데 이는 Reqeust의 쿼리 스트링으로 부터 query를 얻어 Querydsl을 사용하는 방법을 기술한다.

 

 

? firstname=Dave&lastname=Matthews와 같이 query parameter를 통해 request를 보내는 경우

 

QuerydslPredicateArgumentResolver를 사용하여 Q 타입의 객체로 바인딩할 수 있다고 한다.

 

Spring MVC의 ArgumentResolver를 이용하는 방법으로 보이며 controller layer에서 손쉽게 사용할 수 있어 보인다.

 

이 기능을 자동으로 활성화하기 위해선 @EnableSpringDataWebSupport를 활성화해야 하며 classpath에 Querydsl 라이브러리를 찾을 수 있어야 한다.

 

@QuerydslPredicate 애노테이션을 추가하면 QuerydslPredicateExecutor에서 사용할 수 있는 Predicate가 제공된다고 한다.

@Controller
class UserController {

    @Autowired UserRepository repository;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,    (1)
    Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {

        model.addAttribute("users", repository.findAll(predicate, pageable));

        return "index";
    }
}

(1) 쿼리 스트링 인자를 User 타입의 Predicate로 매칭시켜 할당합니다.

 

이를 바인딩하는 기본 규칙은 다음과 같다.

객체가 하나의 속성으로 넘어온 경우 eq()를 사용한다.

객체가 like 속성으로 넘어오는 경우 contains()를 사용한다.

Collection이 하나의 속성으로 전달된 경우 in()을 사용한다.

 

이러한 규칙은 QuerydslBinderCustomizer를 통해 커스터마이징이 가능하다.

interface UserRepository extends CrudRepository<User, String>,
        QuerydslPredicateExecutor<User>,                (1)
        QuerydslBinderCustomizer<QUser> {               (2)

    @Override
    default void customize(QuerydslBindings bindings, QUser user) {

        bindings.bind(user.username).first((path, value) -> path.contains(value))    (3)
        bindings.bind(String.class)
                .first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
        bindings.excluding(user.password);                                           (5)
    }
}

(1) QuerydslPredicateExecutory <>는 Predicate를 사용하여 특정 개체를 찾도록 제공합니다.

(2) QueryBinderCustomizer <Q?> shortcut으로 @QuerydslPredicate(bindings=..)으로 사용할 수 있다.

(3) single property를 contains를 사용하여 바인하게 할 수 있다.

(4) String에 관련된 프로파티를 대소문자를 구분하지 않고 검색하도록 할 수 있다.

(5) password와 관련된 바인딩을 제외할 수도 있다.

 

eq, contains 등과 같은 조건만을 default로 동작하기 때문에 이를 회피하고 별도로 커스텀하기 위해 QuerydslBinderCustomizer를 구현하는 작업이 필요하다는 점을 생각해 보면 간단하다고 생각되지는 않는다.

 

Custom Repository를 통해 JPARepository와 Querydsl을 포함한 Repository를 composite 하는 방법도 알아본다.

 

Spring Data가 기본으로 제공하는 query method가 맘에 들지 않으면 이를 커스텀하여 하나의 구현체를 자동으로 합쳐 만들어주는 기능이 있다.

 

이를 사용하면 Querydsl을 구현한 구현체와 Spring data Repository를 합쳐 하나의 구현체를 빈으로 등록해 주는 과정을 진행하여 통합해 사용할 수 있다.

 

이를 위해 Querydsl을 위한 인터페이스와 이를 구현하고 이때 postfix는 default Impl을 붙여야 한다.

 

이후 사용할 repository에 해당 인터페이스를 추가.

interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

    // Declare query methods here
}

 

QuerydslRepositorySupport

 

Querydsl library를 사용할 때 사용할 수 있는 추상클래스이다.

이를 상속하여 repository를 사용해야 하기 때문에 실제 repository는 구현체가 필요하다.

별도의 공식문서에서 사용사례나 설명을 찾기가 힘들었다.

다음 API 스펙을 보면 어떻게 구현해야 할지 감이 온다.

 

QuerydslRepositorySupport (Spring Data JPA Parent 3.0.1 API)

 

QuerydslRepositorySupport (Spring Data JPA Parent 3.0.1 API)

java.lang.Object org.springframework.data.jpa.repository.support.QuerydslRepositorySupport Base class for implementing repositories using Querydsl library. Author: Oliver Gierke, Mark Paluch Constructor Summary Constructors Method Summary All MethodsInstan

docs.spring.io

그런데 이 추상 클래스 필드에는 내가 아는 JPAQueryFactory가 없다. 대신 EntityManager, Querydsl, PathBuilder <?> 타입의 필드로만 이루어져 있는 데 사용법이 조금 많이 다르다.

 

또 CQRS로 분리하거나 단순히 command와 query만 분리하려고 해도 각각 상속받아야 하는 번거로움이 보인다.

이를 개선하고 사용하기 위해선 Querydsl을 사용하기 위한 추상클래스를 직접 만들어서 사용해야 할 수도 있다.

 

다른 곳은 어떻게 사용하나?

 

 

실제로 Querydsl을 사용하기 위해선 JPLQueryFactory만 있으면 되기 때문에 필드 주입이 가장 간편할 것 같은데 왜 extends, implements를 통해 복잡하게 생성해야 하는지 궁금했다.

 

굳이 확장 모듈의 기능을 사용하지 않는다면 그냥 JPAQueryFactory를 주입받아서 사용하는 게 가장 좋지 않나?라는 생각을 가지고 관련 자료를 찾아보았다.

 

“2020 우아한 테크콘서트 수십억 건에서 Querydsl 사용하기” 유튜브 영상을 요약한 다음 글을 보았다.

https://velog.io/@youngerjesus/%EC% 9A% B0% EC%95%84% ED%95%9C-%ED%98%95% EC% A0% 9C% EB%93% A4% EC% 9D%98-Querydsl-%ED%99% 9C% EC% 9A% A9% EB% B2%95

 

우아한 형제들의 Querydsl 사용법

이 글은 "우아한테크콘서트2020 수십억건에서 Querydsl 사용하기" 와 발표자이신 이동욱님의 기술 블로그를 보고 작성한 글입니다. 모든 예제와 추가로 Querydsl 사용 문법은 https://github.com/Youngerjesus/Q

velog.io

 

첫 번째의 주제에서는 Querydsl을 사용하는 다양한 방법 중에 가장 간편하게 사용할 수 있는 방법을 알려주는데, 예상하듯 JPAQueryFactory를 생성자 주입을 통해 처리하는 것이 가장 간단하고 깔끔하다고 한다.

 

동적 쿼리도 작성하는 다양한 방법이 존재하는데 추천하는 방법은 BooleanExpression 사용하는 것이다.

 

BooleanBuilder를 작성하거나, Where 절과 파라미터로 Predicate를 이용하는 방법, where 절과 파라미터로 Predicate를 상속한 BooleanExpression을 사용하는 방법이 있다고 한다.

 

BooleanExpression를 추천하는 이유는 BooleanExpression은 재사용이 가능하며 이를 조합하여 새로운

BooleanExpression을 생성할 수 있다는 것과 실제 동적으로 null이 들어왔을 때 where 절에서 해당 조건이 무시된다고 한다.

 

지금까지 Querydsl을 다양하게 적용하는 방법에 대해 간단히 알아보았다. 

 

공부할 때는 다양한 방법 모두 사용해 보면서 학습 테스트를 진행했고 실제 적용할 때는 extends와 implements를 제외하고 단순 필드 주입으로 처리했다.!

 

jpql로 동적쿼리를 처리하던 것을 Querydsl을 변경하는 리팩토링을 하고 있는데 생각보다 쉽지 않다. 

관련해서 조금 더 공부해 보자.

반응형

'데이터 접근 기술' 카테고리의 다른 글

Querydsl 기본(3)  (0) 2023.02.07
Querydsl 기본(2)  (0) 2023.02.06
[Querydsl] 기본  (0) 2023.02.01
Hibernate Best Practices  (0) 2023.02.01
[Querydsl] 시작  (0) 2023.02.01

댓글