0Pricing
Spring Boot 4 Complete Guide · 강의

Specification과 기준 기반 동적 필터링

JPA Specification API로 타입 안전한 동적 조건식을 조합해 런타임 기반 필터링을 구현합니다.

Specification과 기준 기반 동적 필터링은(는) CoddyKit의 무료 Spring Boot 4 Complete Guide 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Spring Boot 4 Complete Guide 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Why Dynamic Filtering?

Real applications rarely query with fixed criteria. A product search screen might filter by name, category, price range, or any combination the user picks at runtime.

Writing a separate repository method for every combination explodes fast: findByName, findByNameAndCategory, findByCategoryAndPriceBetween... This does not scale.

Spring Data JPA's Specification API lets you compose type-safe query fragments at runtime and combine them with and() / or(). In this lesson you'll build dynamic predicates the clean way.

Enabling Specifications on a Repository

To use Specifications, your repository must extend JpaSpecificationExecutor<T> in addition to JpaRepository.

This adds overloaded methods such as findAll(Specification<T>), findAll(Specification, Pageable), findOne(Specification), and count(Specification).

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface ProductRepository
        extends JpaRepository<Product, Long>,
                JpaSpecificationExecutor<Product> {
}

Anatomy of a Specification

A Specification<T> is a functional interface with one method:

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb)

  • root — the entity you query from; use root.get("field") to reference columns.
  • query — the overall CriteriaQuery (for ordering, distinct, grouping).
  • cb — the CriteriaBuilder factory that builds Predicate objects like equal, like, greaterThan.

Your First Specification

Here is a Specification that filters products by an exact category name. A common idiom is to expose factory methods returning Specifications.

Notice root.get("category") walks into the related Category entity, then .get("name") reaches its field.

import org.springframework.data.jpa.domain.Specification;

public class ProductSpecs {

    public static Specification<Product> hasCategory(String category) {
        return (root, query, cb) ->
                cb.equal(root.get("category").get("name"), category);
    }
}

Combining Specifications

The power of Specifications is composition. Use the static and instance helpers on the interface:

  • Specification.allOf(...) / Specification.anyOf(...) — combine many.
  • spec.and(other) — logical AND.
  • spec.or(other) — logical OR.
  • Specification.not(spec) — negation.

Each combinator returns a new Specification, so chaining stays immutable and readable.

Specification<Product> spec =
        ProductSpecs.hasCategory("Laptops")
            .and(ProductSpecs.priceLessThan(new BigDecimal("2000")));

List<Product> result = productRepository.findAll(spec);

Building Predicates Conditionally

The real win is assembling a Specification only from the filters the user actually supplied. Start from an empty conjunction and append non-null criteria.

In Spring Boot 4, Specification.unrestricted() returns a no-op base that matches everything — the ideal neutral starting point.

public Specification<Product> build(ProductFilter f) {
    Specification<Product> spec = Specification.unrestricted();

    if (f.getName() != null) {
        spec = spec.and(ProductSpecs.nameContains(f.getName()));
    }
    if (f.getCategory() != null) {
        spec = spec.and(ProductSpecs.hasCategory(f.getCategory()));
    }
    if (f.getMaxPrice() != null) {
        spec = spec.and(ProductSpecs.priceLessThan(f.getMaxPrice()));
    }
    return spec;
}

LIKE, Ranges, and Case-Insensitive Matching

The CriteriaBuilder exposes the building blocks you need for flexible text and numeric filters:

  • cb.like(cb.lower(root.get("name")), "%" + term.toLowerCase() + "%") — case-insensitive contains.
  • cb.between(root.get("price"), min, max) — inclusive range.
  • cb.greaterThanOrEqualTo(...) / cb.lessThanOrEqualTo(...) — open-ended bounds.
public static Specification<Product> nameContains(String term) {
    return (root, query, cb) ->
            cb.like(cb.lower(root.get("name")),
                    "%" + term.toLowerCase() + "%");
}

public static Specification<Product> priceLessThan(BigDecimal max) {
    return (root, query, cb) ->
            cb.lessThanOrEqualTo(root.get("price"), max);
}

Null-Safe Specifications

A neat alternative to if guards: let each factory return null when its input is absent. Spring Data treats a null Specification (and a null returned from toPredicate) as "no restriction" and skips it during composition.

This keeps the builder flat, but be explicit — returning null can surprise readers, so document it.

public static Specification<Product> hasCategory(String category) {
    return (root, query, cb) ->
            category == null
                ? null
                : cb.equal(root.get("category").get("name"), category);
}

Joins Inside a Specification

For collection associations or to avoid lazy-loading pitfalls, create an explicit Join from the root. This generates a SQL JOIN and lets you filter on the related entity's columns.

Tip: when a join can multiply rows, call query.distinct(true) to avoid duplicate parents in the result.

import jakarta.persistence.criteria.Join;

public static Specification<Product> hasTag(String tag) {
    return (root, query, cb) -> {
        Join<Product, Tag> tags = root.join("tags");
        query.distinct(true);
        return cb.equal(tags.get("label"), tag);
    };
}

Pagination and Sorting

Specifications combine seamlessly with paging. Pass a Pageable alongside the Specification and Spring Data applies WHERE, ORDER BY, LIMIT, and OFFSET in one query.

The returned Page also runs a count query (using the same Specification) so you get total elements for free.

Pageable pageable = PageRequest.of(0, 20, Sort.by("price").descending());

Page<Product> page = productRepository.findAll(build(filter), pageable);

long total = page.getTotalElements();
List<Product> items = page.getContent();

A Standalone Composition Demo

Specifications are lambdas, so the composition logic itself is plain Java you can reason about without a database. This runnable example mirrors how and()/or() short-circuit by composing boolean predicates the same way.

It demonstrates the mental model: a Specification is a deferred predicate that you combine before executing.

import java.util.List;
import java.util.function.Predicate;

public class SpecDemo {
    record Product(String name, String category, int price) {}

    public static void main(String[] args) {
        List<Product> products = List.of(
            new Product("UltraBook", "Laptops", 1800),
            new Product("GamerX", "Laptops", 2400),
            new Product("OfficeMouse", "Accessories", 25)
        );

        Predicate<Product> spec = p -> p.category().equals("Laptops");
        spec = spec.and(p -> p.price() <= 2000);

        products.stream()
                .filter(spec)
                .forEach(p -> System.out.println(p.name()));
    }
}

Quick Check

You are building a dynamic filter where each optional criterion may or may not be present. You want a neutral base Specification that matches all rows, then conditionally add restrictions. Which is the correct Spring Boot 4 starting point?

Recap

You learned how to build runtime-driven, type-safe filters with the JPA Specification API:

  • Extend JpaSpecificationExecutor to unlock findAll(Specification, ...).
  • A Specification is a lambda over (root, query, cb) returning a Predicate.
  • Compose with and(), or(), not(), and start from Specification.unrestricted().
  • Add restrictions conditionally so only supplied filters affect the query.
  • Use cb.like/cb.lower, cb.between, and explicit root.join(...) with query.distinct(true) for richer queries.
  • Pass a Pageable for paging, sorting, and an automatic count query.

This pattern replaces dozens of hand-written finder methods with one clean, composable builder.

자주 묻는 질문

“Specification과 기준 기반 동적 필터링” 강의는 무료인가요?

네 — “Specification과 기준 기반 동적 필터링” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Spring Boot 4 Complete Guide 강의 전체를 잠금 해제할 수 있습니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.

“Specification과 기준 기반 동적 필터링”에서 뭘 배우나요?

JPA Specification API로 타입 안전한 동적 조건식을 조합해 런타임 기반 필터링을 구현합니다. 브라우저에서 직접 실행하는 실습 코드로 Spring Boot 4 Complete Guide을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Spring Boot 4 Complete Guide을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Spring Boot 4 Complete Guide은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“Specification과 기준 기반 동적 필터링” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Spring Boot 4 Complete Guide 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Spring Boot 4 Complete Guide 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 파생 쿼리 메서드와 키워드 해석
  2. @Query를 활용한 JPQL 및 네이티브 쿼리
  3. Specification과 기준 기반 동적 필터링
  4. 페이지 매김, 정렬 및 슬라이스 스트리밍
← Spring Boot 4 Complete Guide(으)로 돌아가기