Criteria: المسندات والاستعلامات الديناميكية
Criteria: المسندات والاستعلامات الديناميكية
قدّم الدرس السابق Criteria API وأوضح كيفية بناء استعلام مكتوب (typed) بسيط. إلا أن الفائدة الحقيقية من هذه الواجهة تكمن في قدرتها على تأليف شروط الاستعلام أثناء وقت التشغيل. فحين يتمكّن المستخدم من تصفية قائمة منتجات حسب الاسم أو الفئة أو الحد الأدنى للسعر أو الأقصى أو أي تركيبة من هذه المعايير، يعجز نص JPQL الثابت عن التعبير عن كل الاحتمالات. المسندات (predicates) هي اللبنات الأساسية التي تحل هذه المشكلة.
ما هو المسند (Predicate)؟
في Criteria API، يمثّل Predicate تعبيرًا بوليانيًا في SQL — أي شيء يمكن أن يظهر في جملة WHERE أو شرط انضمام. CriteriaBuilder هو المصنع الذي ينشئ هذه المسندات، ويمكنك دمجها بمعاملات منطقية لنمذجة شروط بالغة التعقيد.
الطرق الشائعة في المُنشئ التي تُنتج مسندات:
cb.equal(expr, value)— اختبار المساواةcb.like(expr, pattern)— SQLLIKEcb.greaterThanOrEqualTo(expr, value)/cb.lessThanOrEqualTo(expr, value)cb.between(expr, lower, upper)cb.isNull(expr)/cb.isNotNull(expr)cb.in(expr).value(v1).value(v2)— SQLINcb.and(p1, p2, ...)/cb.or(p1, p2, ...)— الدمج المنطقيcb.not(predicate)— النفي
بناء مسند بسيط
لنفترض وجود كيان Order بحقول status وtotalAmount وعلاقة كثيرة-لواحد مع Customer. يبدو استعلام الطلبات ذات الحالة SHIPPED والمبلغ الذي يتجاوز حدًا معينًا هكذا:
كل مسند كائن مستقل بذاته. تدمجها بتمريرها إلى cb.and() قبل تسليم النتيجة إلى cq.where().
تأليف استعلامات ديناميكية
النمط الأساسي لنماذج البحث هو تجميع المسندات في قائمة وإضافة المسند فقط عندما تكون قيمة التصفية المقابلة موجودة فعلًا. هذا يبقي SQL المولَّد موجزًا — لا حاجة لخدع AND 1=1 الزائفة.
where(): عندما لا تُوفَّر أي مرشحات، تكون predicates فارغة ويُنتج toArray مصفوفة بطول صفر. يعامل JPA هذا الاستدعاء where(new Predicate[0]) على أنه لا قيد — ما يعادل حذف جملة WHERE كليًا. أي أن الطريقة ذاتها تتعامل بسلاسة مع "إرجاع الكل" والاستعلامات المصفَّاة بالكامل.
شروط OR والمنطق المتداخل
امزج cb.and() وcb.or() لنمذجة شروط مركّبة. لنفترض أنك تريد طلبات إما PENDING أو متأخرة عن الموعد (حالة OVERDUE)، ومبلغها يتجاوز حدًا أدنى:
يُترجم هذا إلى:
LIKE والبحث غير الحساس لحالة الأحرف
مطابقة السلاسل النصية الجزئية متطلب شائع. لف تعبير العمود بـ cb.lower() وتحويل سلسلة البحث إلى أحرف صغيرة في Java لتحقيق مطابقة محمولة غير حساسة لحالة الأحرف:
%term) يعطّل مسح فهارس B-tree في معظم قواعد البيانات. للبحث النصي الكامل على الجداول الكبيرة في الإنتاج، فضّل حلًا متخصصًا (مثل tsvector في PostgreSQL أو Elasticsearch أو فهرس نص كامل) بدلًا من LIKE '%...'. احتفظ بـ LIKE ذي الحرف البدل الأمامي للوحات الإدارة والبحث قليل الحجم.
الاستعلامات الفرعية كمسندات
تدعم Criteria API الاستعلامات الفرعية المترابطة عبر cq.subquery(). هذا مفيد لشروط "exists" — كإرجاع العملاء الذين لديهم طلب مشحون واحد على الأقل:
نمط المواصفة (Specification) في Spring Data JPA
كتابة كل منطق المسندات داخل مستودع واحد يصبح مطوّلًا بسرعة. تلفّ واجهة Specification<T> في Spring Data JPA بناءَ مسند واحد في وحدة lambda قابلة لإعادة الاستخدام والتأليف. فعّلها بتوسيع JpaSpecificationExecutor<T> في واجهة مستودعك:
عرّف مواصفات فردية كطرق مصنع ثابتة في فئة أدوات مساعدة:
ادمجها ونفّذها بواجهة برمجية سلسلة:
cb.conjunction()؟ تُعيد مسندًا يكون دائمًا TRUE، تعمل بوصفها عملية لا تأثير لها عند غياب قيمة المرشّح. هذا يتيح لكل طريقة مواصفة معالجة فحص القيم الفارغة داخليًا، فلا يضطر المستدعي أبدًا للحراسة من القيم الفارغة.
اعتبارات الأداء
- ربط المعاملات لا تسلسل النصوص. تُربط جميع قيم Criteria API تلقائيًا كمعاملات JDBC — حقن SQL مستحيل، وتستطيع قاعدة البيانات تخزين خطط التنفيذ مؤقتًا.
- تجنّب مجموعات النتائج الفضفاضة. يمكن للاستعلامات الديناميكية أن تُعيد ملايين الصفوف إن كانت جميع المرشحات فارغة. طبّق ترقيمًا للصفحات (
setFirstResult/setMaxResults) لأي بحث يواجه المستخدم. - انضمامات الجلب في الاستعلامات الديناميكية. إن انضممت بشكل مشروط إلى ارتباط للتصفية به، اجلبه أيضًا لتجنّب تحميل N+1. استخدم
root.fetch("customer", JoinType.LEFT)حين يكون الانضمام لتحميل البيانات، وroot.join()حين يكون للتصفية فحسب. - التمييز عند جلب مجموعات. الانضمام لعلاقة واحد-لكثير ينفخ مجموعة النتائج. أضف
cq.distinct(true)أو استخدمDISTINCTفي الاستعلام لإلغاء التكرار.
الخلاصة
المسندات هي الوحدات الذرية في استعلامات Criteria API. ابنها بشكل فردي باستخدام CriteriaBuilder، اجمعها في قائمة، وادمجها بـ cb.and() / cb.or() لتمثيل أي تركيبة مرشحات قد يختارها المستخدم. يرفع نمط المواصفة من Spring Data JPA هذا إلى مستوى أعلى من إعادة الاستخدام بتغليف كل مسند ككائن مُسمّى قابل للتأليف. معًا، تستبدل هاتان التقنيتان نهج تسلسل السلاسل الهش في SQL الديناميكي ببديل آمن من حيث الأنواع وقابل للاختبار وسهل القراءة.