0Pricing
Spring Boot 4 Complete Guide · درس

المواصفات والتصفية الديناميكية القائمة على المعايير

تركيب مسندات ديناميكية آمنة من حيث الأنواع باستخدام JPA Specification API للتصفية التي يحددها وقت التشغيل.

المواصفات والتصفية الديناميكية القائمة على المعايير درس مجاني في Spring Boot 4 Complete Guide على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في 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.

الأسئلة الشائعة

هل درس «المواصفات والتصفية الديناميكية القائمة على المعايير» مجاني؟

نعم — نص درس «المواصفات والتصفية الديناميكية القائمة على المعايير» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Spring Boot 4 Complete Guide، انتقل إلى CoddyKit PRO. تتضمن دورة Spring Boot 4 Complete Guide 4 دروس في المجموع.

ماذا ستتعلم في «المواصفات والتصفية الديناميكية القائمة على المعايير»؟

تركيب مسندات ديناميكية آمنة من حيث الأنواع باستخدام JPA Specification API للتصفية التي يحددها وقت التشغيل. تتمرن على Spring Boot 4 Complete Guide مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Spring Boot 4 Complete Guide؟

لا تُشترط خبرة سابقة. Spring Boot 4 Complete Guide على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «المواصفات والتصفية الديناميكية القائمة على المعايير»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Spring Boot 4 Complete Guide هذا؟

نعم. كل درس في Spring Boot 4 Complete Guide يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. أساليب الاستعلام المشتقة وحل الكلمات المفتاحية
  2. استعلامات JPQL وSQL الأصلية باستخدام @Query
  3. المواصفات والتصفية الديناميكية القائمة على المعايير
  4. الترقيم والترتيب وبث الشرائح
← العودة إلى Spring Boot 4 Complete Guide