< Dev-Kidult />

Springboot - QueryBinder 본문

개발/Back-end

Springboot - QueryBinder

개른이 2022. 12. 28. 16:04

목적


@GetMapping 에서의 조건에 따른 조회에서 빠르고, 단순하고, 쉽게 조회 할 수 있도록 하기 위함.

 

종류


GitHub - turkraft/spring-filter: Painless filtering library for JPA entities and MongoDB collections. Smoothly integrates with Spring APIs.

Spring Data JPA - Reference Documentation

Home - DGS Framework

 

1. spring filter


LGU+, OASIS 에서 사용한 라이브러리

장점

두개의 프로젝트를 진행하면서 쌓인 경험치로 인해 계속 쓰는데 큰 어려움이 따르지 않는다.

많은 Comparators(eq, not, in, gt, goe, is null, not null…), functions를 지원한다.

프론트에서 쓸 수 있는 쿼리빌더 라이브러리도 있다.

 

단점

기본적으로 Specification을 확장하여 구현되어 있으며, Predicate를 쓰는거보다 확장하여 쓰기가 번거롭다.

Release Version 2.1.2 · turkraft/spring-filter Numberformat을 쓰는건 고쳐졌으나, @ManyToOne 필드에 접근하여 검색하면 (ex, user.id:1) 결과값 목록이 뻥튀기 되어 나온다.

그리고 연관관계따라 join하는 방법이 고정되어있어서 변경을 하고자 하면은 추가구현이 필요하다.

통계등 기타 특수한 상황에서 도메인을 못쓰기 때문에 무용지물이 된다.

Support for Spring Boot 3.0 · Issue #243 · turkraft/spring-filter 버전업에 대한 이슈처리도 빠르지 못한점이 아쉽다.

 

사용법

1. Controller

GET ~/api/...?filter=name~'%blah%' and company.id:1

@GetMapping
fun getList(@Filter(parameterName = "filter") filter: FilterSpecification<Entity>, page: Pageable) { ... }
  • parameterName의 default값은 filter이며 변경 가능하다.

 

2. Extends

open class EntityFilterSpecification<T>(filter: Filter?) : FilterSpecification<T>(filter) {
    override fun toPredicate(root: Root<T>, query: CriteriaQuery<*>, criteriaBuilder: CriteriaBuilder): Predicate? {
        query.distinct(true)
        return super.toPredicate(root, query, criteriaBuilder)
    }
}
  • 확장은 기본적으로 FilterSpecification을 받아 확장하며 toPredicate 함수를 오버라이딩 해서 쓴다. (위 코드는 distinct 추가한 모습)

 

3. Join

override fun toPredicate(root: Root<T>, query: CriteriaQuery<*>, criteriaBuilder: CriteriaBuilder): Predicate? {
        val role = SessionUtils.getRole()
        if (role != Role.ADMIN) {
            val company = root.join<T, Company>("company", JoinType.LEFT)
            val workspaceContracts = root.join<T, WorkspaceContract>("workspaceContracts", JoinType.LEFT)

            joins = mapOf("company" to company, "workspaceContracts" to workspaceContracts)
            if (role == Role.COMPANY_MEMBER) {
                val workspaceUsers = root.join<T, WorkspaceUser>("workspaceUsers", JoinType.LEFT)
                val user = workspaceUsers.join<WorkspaceUser, User>("user", JoinType.LEFT)
                joins["workspaceUsers"] = workspaceUsers
                joins["user"] = user
            }
        }
        return super.toPredicate(root, query, criteriaBuilder)
    }
  • 위 코드는 OASIS의 WorkspaceFilterSpecification 의 일부분이다.
  • FilterSpecification에는 joins라는 필드가 있다.
  • toPredicate함수에서 파라미터로 받은 값을 predicate로 전환하는 과정에서 연관관계가 맺어진 필드에 접근이 이뤄진다면 맺어진 관계에 따라서 (onetoone, onetomany…) 정해진 left join 혹은 right join을 걸어서 쿼리를 만든다.
  • 하지만 joins에 이미 정의가 되어있다면 해당 값을 가지고 쿼리를 만든다.

 

2. Spring Data QueryDsl Web


장점

Spring data에 포함되어 있어 문제가 생겼을 때 수정이 빠르고, 관련된 커뮤니티나 혹은 검색했을 때 많은 내용을 쉽게 찾을 수 있다.

predicate로 바인딩해주기때문에 쓰기 쉽다.

GET ~/api/...?name=blah&id=1 // -> QEntity.entity.name.eq("blah").and(QEntity.entity.id.eq(1))

//example
fun getAll(@QuerydslPredicate(root = Entity::class) predicate: Prediacte, pageable: Pageable) {
  return repository.findAll(predicate, pageable)
}
fun getAllWithName(name: String,@QuerydslPredicate(root = Entity::class) predicate: Prediacte) {
  return repository.findAll(QEntity.entity.name.eq(name).and(predicate), pageable)
}

QClass로 정의만 되어있다면 다 받아서 쓸 수 있다. 그렇기 때문에 현재 통계데이터를 뽑는데에 querydsl로 구현하여 조회중인데 여기서도 활용이 가능할 것 같다.

 

단점

기본적으로 equals이다. 다른 comparator를 지원하지 않아서 customize를 진행해야 한다.

커스텀되는 대상은 QClass의 필드들, 타입이 있고, 제외 할 필드도 지정 할 수 있다.

override fun customize(bindings: QuerydslBindings, root: QEntity) {
        bindings.bind(String::class.java).first { path: StringPath, value -> path.containsIgnoreCase(value) }
        bindings.bind(root.name).first { path, value -> path.contains(value) }
        bindings.excluding(root.subName)
    }

 

3. Spring Graplql, DGS framework


언더페칭 및 오버페칭이 적은 점과 기존 서버에 많은 제어권이 들어가 있는 REST 보다 클라이언트에게 많은 제어권을 넘기는 개발 방식. 단일 Endpoint등 이점은 많으나

위에 적어둔 목적과 상이하게 graphql파일 추가 관리, 추가적인 러닝포인트, paging 미지원, 위 spring filter, querydsl web처럼 auto binding이 안되어서 추가 개발이 필요한 점 등 당장 적용하기엔 불편한 점이 많다.

 

Is there a way to auto-bind a Spring GraphQL @Argument input to QueryDSL Predicate? 나와 같은 갈증을 느끼는 사람도 역시 있다.

 

 

결론


지금의 상황에서 Graphql을 쓰기에는 득보단 실이 많아보인다.

하지만 Spring Data QueryDsl Web은 충분히 도입해볼만한 여지가 충분히 있다고 판단된다.

개인적으로는 마이너한 spring filter보다는 spring에서 직접 관리되고 있는 querydsl쪽이 관련자료도 많고 개선의 여지나 spring 버전업으로 인한 사이드이펙에 대한 대응도 훨씬 빠르고 잘 처리 될거같아서

querydsl쪽을 더 추천하는 바이다.

반응형
Comments