ข้อกำหนดและการกรองแบบไดนามิกตามเกณฑ์
ประกอบเพรดิเคตแบบไดนามิกที่ปลอดภัยด้านชนิดด้วย API JPA Specification สำหรับการกรองตามเงื่อนไขขณะรันไทม์
ข้อกำหนดและการกรองแบบไดนามิกตามเกณฑ์ เป็นบทเรียน Spring Boot 4 Complete Guide ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 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
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.
เรียนรู้ Java ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 21
- บทเรียน
- 84
คำถามที่พบบ่อย
บทเรียน “ข้อกำหนดและการกรองแบบไดนามิกตามเกณฑ์” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ข้อกำหนดและการกรองแบบไดนามิกตามเกณฑ์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Spring Boot 4 Complete Guide ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Spring Boot 4 Complete Guide มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ข้อกำหนดและการกรองแบบไดนามิกตามเกณฑ์”
ประกอบเพรดิเคตแบบไดนามิกที่ปลอดภัยด้านชนิดด้วย API JPA Specification สำหรับการกรองตามเงื่อนไขขณะรันไทม์ คุณปฏิบัติ Spring Boot 4 Complete Guide ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 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 ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- เมธอดคำค้นหาที่อนุมานและการแปลคีย์เวิร์ด
- คำค้นหา JPQL และเนทีฟด้วย @Query
- ข้อกำหนดและการกรองแบบไดนามิกตามเกณฑ์
- การแบ่งหน้า การเรียงลำดับ และการสตรีมสไลซ์