Belirtimler ve Ölçüt Tabanlı Dinamik Filtreleme
Çalışma zamanında yönlendirilen filtreleme için JPA Specification API ile tür güvenli dinamik koşulları birleştirin.
Belirtimler ve Ölçüt Tabanlı Dinamik Filtreleme, CoddyKit'te ücretsiz bir Spring Boot 4 Complete Guide dersidir. Bu, 4 dersinin 3. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Spring Boot 4 Complete Guide öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Spring Boot 4 Complete Guide kursu toplamda 4 dersten oluşur.
Bu dersin bazı bölümleri henüz çevrilmemiş olup İngilizce olarak gösterilmektedir.
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
CriteriaBuilderfactory that buildsPredicateobjects likeequal,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 aPredicate. - Compose with
and(),or(),not(), and start fromSpecification.unrestricted(). - Add restrictions conditionally so only supplied filters affect the query.
- Use
cb.like/cb.lower,cb.between, and explicitroot.join(...)withquery.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.
Sıkça Sorulan Sorular
“Belirtimler ve Ölçüt Tabanlı Dinamik Filtreleme” dersi ücretsiz mi?
Evet — “Belirtimler ve Ölçüt Tabanlı Dinamik Filtreleme” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Spring Boot 4 Complete Guide kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Spring Boot 4 Complete Guide kursu toplamda 4 dersten oluşur.
“Belirtimler ve Ölçüt Tabanlı Dinamik Filtreleme” dersinde ne öğreneceğim?
Çalışma zamanında yönlendirilen filtreleme için JPA Specification API ile tür güvenli dinamik koşulları birleştirin. Spring Boot 4 Complete Guide ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.
Spring Boot 4 Complete Guide öğrenmeye başlamak için deneyim gerekli mi?
Önceden deneyim gerekmez. CoddyKit'te Spring Boot 4 Complete Guide, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 3. dersidir.
“Belirtimler ve Ölçüt Tabanlı Dinamik Filtreleme” dersi ne kadar sürer?
Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.
Bu Spring Boot 4 Complete Guide dersinde kod yazıp çalıştırabilir miyim?
Evet. Her Spring Boot 4 Complete Guide dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.
Bu kursun tüm dersleri
- Türetilmiş Sorgu Metotları ve Anahtar Sözcük Çözümleme
- @Query ile JPQL ve Yerel Sorgular
- Belirtimler ve Ölçüt Tabanlı Dinamik Filtreleme
- Sayfalandırma, Sıralama ve Dilim Akışı