واجهة Criteria API
واجهة Criteria API
تتسم JPQL بالتعبير والوضوح، لكنها تعاني من ثغرة جوهرية: استعلامك مجرد نص عادي. أي خطأ إملائي في اسم كيان، أو حقل مكتوب خطأً، أو جملة join مشوّهة تُترجَم بنجاح ولا تظهر إلا وقت التشغيل. تحل واجهة Criteria API هذه المشكلة بإتاحة بناء الاستعلامات برمجيًا — على شكل شجرة من كائنات Java — ليتمكن المترجم وبيئة التطوير من التحقق من كل جزء منها قبل أن يعمل التطبيق أصلًا.
تقع واجهة Criteria API في حزمة jakarta.persistence.criteria وهي مدمجة بإحكام مع EntityManager في JPA. في Spring Boot 3 مع Hibernate 6 لديك كل ما تحتاجه على classpath بالفعل — لا شيء إضافي للإضافة.
الكائنات الثلاثة الأساسية
كل استعلام Criteria يُبنى من ثلاثة كائنات متعاونة:
CriteriaBuilder— المصنع. تحصل عليه منEntityManager. يُنشئ كائنات الاستعلام والتعبيرات والمحددات (predicates) وبنود الترتيب. فكّر فيه على أنه مجموعة الكلمات المفتاحية في JPQL تحوّلت إلى واجهة Java برمجية.CriteriaQuery<T>— تعريف الاستعلام. يحمل نوع النتيجة وبندFROMومحددWHEREوالترتيب والتجميع. كائنCriteriaQueryواحد لكل استعلام منطقي.Root<T>— متغير نطاق الكيان، مكافئ للاسم المستعار فيFROM Order o. يمنحك وصولًا مكتوبًا (typed) إلى السمات الثابتة للكيان عبرget("fieldName")أو، مع Metamodel،get(Order_.status).
Root من نفس CriteriaQuery الذي سيُستخدم فيه — خلط الجذور عبر كائنات استعلام مختلفة خطأ شائع يُنتج استثناءً وقت التشغيل.
بناء استعلام SELECT بسيط
النمط دائمًا هو: احصل على CriteriaBuilder ← أنشئ CriteriaQuery ← أضف Root ← هيّئ المحددات ← نفّذ بـ TypedQuery.
لاحظ أن نتيجة em.createQuery(cq) هي TypedQuery<Order> مكتوبة النوع. على عكس createQuery(String) المستندة إلى نص، يعرف المترجم نوع القيمة المُعادة؛ لا حاجة لأي cast غير مفحوص.
إضافة الترتيب والتصفح الصفحي
يرتبط الترتيب والتصفح الصفحي بـ CriteriaQuery أو TypedQuery:
setMaxResults على التقليم في الذاكرة. يترجم مزوّد JPA دالتَي setMaxResults / setFirstResult إلى بنية التصفح الصفحي الخاصة بقاعدة البيانات (LIMIT/OFFSET في MySQL وPostgreSQL، FETCH FIRST في DB2، ROWNUM في إصدارات Oracle القديمة). تتجاهل قاعدة البيانات الصفوف قبل أن تنتقل عبر الشبكة، وهذا يُحدث فارقًا هائلًا على نطاق واسع.
دمج محددات متعددة
الاستعلامات الحقيقية تُصفّي بعدة شروط. تُدمج CriteriaBuilder.and() وcb.or() المحددات تمامًا كـ AND/OR في SQL. كل استدعاء يُعيد Predicate جديدة — تُركّبها كبناء شجرة تعبيرات منطقية.
تقبل cb.and(Predicate...) معاملًا متغيرًا (vararg) فيمكنك تمرير عدد شروط تشاء. المقابل cb.or() يُستخدم لمجموعات OR. يمكنك تداخلها إلى عمق اعتباطي.
ضم الكيانات المرتبطة
الضمات (Joins) في واجهة Criteria API كائنات صريحة من نوع Join<Z, X>. تُنشئها من Root باستدعاء join()، مُحددًا اسم حقل العلاقة ونوع الضم اختياريًا.
للحصول على fetch join (الذي يُحمّل الارتباط مسبقًا لتجنب مشكلة N+1) استدع order.fetch("items") بدلًا من order.join("items"). الكائن Fetch ليس Join، لكنه يقبل نفس معامل JoinType ويُنتج نفس دلالات JOIN ... FETCH في SQL.
setMaxResults يُسبب تحذير حد في الذاكرة. عندما يُنتج fetch join نتيجة one-to-many وتُصفّح كذلك، يضطر Hibernate لجلب جميع الصفوف في الذاكرة والتصفح هناك — لا يستطيع دفع LIMIT إلى SQL بأمان. الحل هو التصفح بمعرّف الكيان في استعلام أول، ثم جلب الارتباطات في استعلام ثانٍ مُقيَّد بتلك المعرّفات.
الاستعلامات القياسية واستعلامات العدد
ليس كل استعلام يُعيد كيانات كاملة. لعدّ الصفوف، غيّر نوع النتيجة إلى Long واستخدم cb.count():
يعمل النمط ذاته مع cb.sum() وcb.max() وcb.avg() وسائر تعبيرات التجميع — غيّر فقط معامل النوع العام في CriteriaQuery ليطابق ما يُعيده التجميع.
لماذا تختار Criteria API على JPQL؟
- الأمان وقت الترجمة — الأخطاء الإملائية في أسماء الحقول تفشل في الترجمة، لا في الإنتاج الساعة 2 صباحًا.
- الاستعلامات الديناميكية — يمكنك إضافة محددات شرطيًا في حلقة، وهو أمر مستحيل مع قالب نصي دون تشابك في التسلسل.
- دعم بيئة التطوير — الإكمال التلقائي على دوال
CriteriaBuilderومساراتRoot.get()(أكثر مع Metamodel الذي يأتي في الدرس التالي). - أمان إعادة البناء — إذا أعدت تسمية حقل في كيان، يُخبرك المترجم فورًا بكل موضع استعلام معطوب.
المقايضة هي الإطناب. استعلام JPQL من خمسة أسطر يصبح خمسة عشر سطرًا في Criteria. للاستعلامات الثابتة كثيفة القراءة تكون JPQL أوضح في الغالب؛ أما الاستعلامات المبنية ديناميكيًا من مدخلات المستخدم (شاشات البحث بفلاتر اختيارية) فإن Criteria API هي الأداة الصحيحة.
الخلاصة
تبني واجهة Criteria API استعلامات JPA على شكل كائنات Java قابلة للتركيب بدلًا من نصوص. الأنواع الثلاثة الأساسية — CriteriaBuilder وCriteriaQuery<T> وRoot<T> — تُعيّن مباشرةً إلى بنية SELECT وFROM وWHERE في SQL. تُضيف محددات بـ cb.equal() وcb.greaterThan() ومتغيراتها، وتدمجها بـ cb.and()/cb.or()، وتُنفّذ عبر TypedQuery مكتوب النوع. الضمات كائنات Join صريحة؛ ضمات الجلب تستخدم fetch(). يُقدّم الدرس القادم JPA Metamodel الذي يستبدل أسماء الحقول النصية مثل "status" بثوابت آمنة النوع مُولَّدة تلقائيًا، مما يجعل استعلامات Criteria أكثر متانة.