ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • spring jpa QuerydslBinderCustomizer
    카테고리 없음 2023. 4. 21. 15:26
    이번 시간에 알아볼 것은 querydsl의 QuerydslBinderCustomizer을 알아볼 예정이다. QuerydslBinderCustomizer는 인터페이스이며 추상 메서드는 void customize(QuerydslBindings bindings, T root) 한개를 갖고 있다. 현재 필자는 회사나 집에서 java8을 쓰기 때문에 java8 기준으로 설명한다 아주 상세하게 컨트롤은 하지 못해도 기본적인 동적쿼리(예로 있으면 검색 아니면 검색하지 않는다)를 간단하게 만들수 있다. 뭐 기능이 얼마나 있는지는 모르겠지만 일단 필자가 테스트한 경우는 기본적인 것만 해봤기 때문에 그것만 설명을 하겠다. 예전에 querydsl를 공부할때 남겨두었던 클래스들을 재 사용했다.
    public interface AccountRepository extends QueryDslPredicateExecutor<Account>,
      QuerydslBinderCustomizer<QAccount>, JpaRepository<Account, Long>, CustomAccountRepository {
    
      @Override
      default void customize(QuerydslBindings bindings, QAccount user) {
        bindings.bind(String.class)
          .first((StringPath path, String value) -> path.containsIgnoreCase(value));
        bindings.excluding(user.password);
      }
    }
    
    여기서 추가 된것은 QueryDslPredicateExecutor 와 QuerydslBinderCustomizer이다. JpaRepository와 CustomAccountRepository는 예전에 공부할때 쓰던거라 일단 남겨두었다. QueryDslPredicateExecutor 인터페이스는 JpaRepository 와 비슷한 추상 메서드를 갖고 있다. 메서드는 거의 비슷하지만 파라미터가 Predicate predicate 위주로 되어있다. java8은 인터페이스에서도 구현을 할 수 있어 QuerydslBinderCustomizer의 구현체를 AccountRepository에 만들었다. 구현체에 있는 것들은 실제 request를 보낼때 바인딩되어 그 바인딩된 것으로 쿼리를 하는 것이다. 말로 설명할려니 조금 힘들다. 실제로 테스트를 해보면서 알아가보자.
    @RestController
    @RequiredArgsConstructor
    public class AccountController {
    
      private final AccountRepository accountRepository;
    
      private final ModelMapper modelMapper;
    
      @GetMapping("/accounts")
      public Page<AccountDto.Response> accounts(@QuerydslPredicate(root = Account.class) Predicate predicate,
                                    Pageable pageable){
        Page<Account> all = accountRepository.findAll(predicate, pageable);
        List<AccountDto.Response> collect = all.getContent()
          .stream()
          .map(i -> modelMapper.map(i, AccountDto.Response.class)).collect(toList());
    
        return new PageImpl<>(collect, pageable, all.getTotalElements());
      }
    }
    
    어떤한 테스트를 하기 위한 controller이다. 파라미터로는 @QuerydslPredicate(root = Account.class) Predicate 와 Pageable을 받고 있다. 실제로 어떻게 되는지 테스트를 해보자. http://localhost:8080/accounts
    {
      "content": [
        {
          "id": 1,
          "name": "wonwoo",
          "password": "1PassWord",
          "email": "wonwoo@test.com"
        },
        {
          "id": 2,
          "name": "wonwoo",
          "password": "2PassWord11",
          "email": "123@test.com"
        },
        {
          "id": 3,
          "name": "kevin",
          "password": "3PassWord2",
          "email": "aaa@test.com"
        },
        {
          "id": 4,
          "name": "ggg",
          "password": "PassWord33",
          "email": "bbb@test.com"
        },
        {
          "id": 5,
          "name": "ggg",
          "password": "PassWord44",
          "email": "ccc@test.com"
        },
        {
          "id": 6,
          "name": "keven",
          "password": "PassWord5",
          "email": "ddd@test.com"
        },
        {
          "id": 7,
          "name": "qqqq",
          "password": "PassWord6",
          "email": "ggg@test.com"
        }
      ],
      "totalElements": 7,
      "last": true,
      "totalPages": 1,
      "size": 20,
      "number": 0,
      "sort": null,
      "first": true,
      "numberOfElements": 7
    }
    
    파라미터를 아무것도 보내지 않았을 때 이런 결과가 있다고 가정하자. 이번에는 파라미터를 보내보자. http://localhost:8080/accounts?name=won
    {
      content: [
        {
          id: 1,
          name: "wonwoo",
          password: "1PassWord",
          email: "wonwoo@test.com"
        },
        {
          id: 2,
          name: "wonwoo",
          password: "2PassWord11",
          email: "123@test.com"
        }
      ],
      totalElements: 2,
      last: true,
      totalPages: 1,
      size: 20,
      number: 0,
      sort: null,
      first: true,
      numberOfElements: 2
    }
    
    name에 won이 들어가는 것을 모두 가져왔다. 다음에는 password를 검색해보자. http://localhost:8080/accounts?password=3
    {
      content: [
        {
          id: 1,
          name: "wonwoo",
          password: "1PassWord",
          email: "wonwoo@test.com"
        },
        {
          id: 2,
          name: "wonwoo",
          password: "2PassWord11",
          email: "123@test.com"
        },
        {
          id: 3,
          name: "kevin",
          password: "3PassWord2",
          email: "aaa@test.com"
        },
        {
          id: 4,
          name: "ggg",
          password: "PassWord33",
          email: "bbb@test.com"
        },
        {
          id: 5,
          name: "ggg",
          password: "PassWord44",
          email: "ccc@test.com"
        },
        {
          id: 6,
          name: "keven",
          password: "PassWord5",
          email: "ddd@test.com"
        },
        {
          id: 7,
          name: "qqqq",
          password: "PassWord6",
          email: "ggg@test.com"
        }
      ],
      totalElements: 7,
      last: true,
      totalPages: 1,
      size: 20,
      number: 0,
      sort: null,
      first: true,
      numberOfElements: 7
    }
    
    하지만 결과와 다르게 모두 출력 되었다. 그 이유는 위의 코드에서 봤듯이 password는 제외 시켰기 때문이다.
    bindings.excluding(user.password);
    
    이번에는 특정 어떠한 파라미터로 오면 그것은 containsIgnoreCase 아닌 eq로 체크 하고 싶다면 아래와 같이 하면 된다.
    @Override
    default void customize(QuerydslBindings bindings, QAccount user) {
      bindings.bind(user.name).first((path, value) -> path.eq(value));
      bindings.bind(String.class)
        .first((StringPath path, String value) -> path.containsIgnoreCase(value));
      bindings.excluding(user.password);
    }
    
    user.name은 like가 아닌 eq으로 해놨다. 그럼 우리는 정확한 값이 나올때만 출력된다. 아까와 동일하게 http://localhost:8080/accounts?name=won 브라우저에 쳐보자.
    {
      content: [
    
      ],
      totalElements: 0,
      last: true,
      totalPages: 0,
      size: 20,
      number: 0,
      sort: null,
      first: true,
      numberOfElements: 0
    }
    
    그럼 위와 같은 결과를 볼 수 있을 것이다. 그렇다면 이번에는 정확하게 문자를 집어넣어서 해보자. http://localhost:8080/accounts?name=wonwoo
    {
      content: [
        {
          id: 1,
          name: "wonwoo",
          password: "1PassWord",
          email: "wonwoo@test.com"
        },
        {
          id: 2,
          name: "wonwoo",
          password: "2PassWord11",
          email: "123@test.com"
        }
      ],
      totalElements: 2,
      last: true,
      totalPages: 1,
      size: 20,
      number: 0,
      sort: null,
      first: true,
      numberOfElements: 2
    }
    
    그럼 위와 같이 두건이 검색 된다. 우리는 QuerydslBinderCustomizer를 사용해서 좀더 간단하게 코딩을 할 수 있게 되었다. 물론 복잡한 것은 Custom한 레파지토를 만들어 querydsl을 사용하거나 JPQL 혹은 네이티브 SQL을 사용해야 될 것이다. 하지만 저렇게 간단하게 해결 할 수 있는 부분도 물론 있을 것이다.

    댓글

Designed by Tistory.