[TOY] JPA와 복잡한 동적 쿼리 사용 위한 Specification의 사용

최지나·2024년 1월 16일
2
post-thumbnail

JPA란

Specification 이란?

  • org.springframework.data.jpa.domain.Specification

  • Spring Data JPA에서 Specification은 repository 계층에서 복잡한 쿼리 조건을 생성하고 조합하기 위한 인터페이스

  • JPQL 을 직접 작성하지 않고도 동적으로 쿼리 구성 가능

  • 여러개의 필드에 따른 복잡한 쿼리 조건으로 데이터를 조회하고자 할 때 findAll(Specification spec)과 같은 메서드와, JpaSpecificationExecutor 인터페이스를 사용하여 데이터 동적 필터링 가능

코드 구현

  • ProductRepository.java
@Repository
public interface ProductRepository extends JpaRepository<Product, String>, JpaSpecificationExecutor<Product> {

  // specification 사용
  Page<Product> findAll(Specification<Product> spec, Pageable pageable);  

  Page<Product> findByType(ProductType type, Pageable pageable);

//...
}
  • ProductSpecification.java
    • 제품(product) 조회 시 type으로 조회 하고, PRODUCT_NAME, CATEGORY_NAME, STOCK, PRICE, DESCRIPTION 중 하나의 검색 값으로 검색할 수 있도록 구현
public class ProductSpecification {

    public static Specification<Product> withDynamicQuery(ProductType type, ProductListReq productListReq) {
        return (root, query, builder) -> {
            List<Predicate> predicates = new ArrayList<>();

            if (type != null) {
                predicates.add(builder.equal(root.get("type"), type));
            }

            if (productListReq != null && productListReq.getSearchKeyWord() != null
                    && !productListReq.getSearchKeyWord().isEmpty()) {
                switch (productListReq.getSearchType()) {
                    case PRODUCT_NAME:
                        predicates.add(builder.like(root.get("name"), "%" + productListReq.getSearchKeyWord() + "%"));
                        break;
                    case CATEGORY_NAME:
                        predicates.add(builder.like(root.get("category").get("name"),
                                "%" + productListReq.getSearchKeyWord() + "%"));
                        break;
                    case STOCK:
                        predicates.add(
                                builder.equal(root.get("stock"), Integer.parseInt(productListReq.getSearchKeyWord())));
                        break;
                    case PRICE:
                        predicates.add(
                                builder.equal(root.get("price"), new BigDecimal(productListReq.getSearchKeyWord())));
                        break;
                    case DESCRIPTION:
                        predicates.add(
                                builder.like(root.get("description"), "%" + productListReq.getSearchKeyWord() + "%"));
                        break;
                }
            }

            return builder.and(predicates.toArray(new Predicate[0]));
        };
    }
}

느낀점

  • Specification을 사용하지 않았다면 type의 개수 * searchType의 종류 만큼의 JPQL을 생성해야 했지만, 이를 하나씩 다 만들지 않고도 단 하나의 JPQL만으로 동적 쿼리들을 수행할 수 있어서 편리하고 유지보수에 용이하다고 생각했다!
  • 검색 조건이 많지 않을 때는 반드시 Specification을 만들 필요는 없지만, 조건이 많고 복잡해질수록 만들어서 관리하는게 좋을 것 같다
  • 알수록 신기한 JPA,,, ^o^
profile
의견 나누는 것을 좋아합니다 ლ(・ヮ・ლ)

2개의 댓글

comment-user-thumbnail
2024년 1월 16일

처음보는 JPA 기능이네요! 유용해보여요

1개의 답글