المعاملات والاستعلامات المسماة
المعاملات والاستعلامات المسماة
كل استعلام JPQL عملي يحتاج إلى قيم خارجية — معرّف مستخدم للتصفية، سلسلة حالة للمطابقة، نطاق تاريخ للبحث فيه. دمج هذه القيم مباشرةً كنصوص حرفية في الاستعلام يُعدّ غير آمن (حقن SQL عبر JPQL) وغير فعّال (لأن مزوّد الثبات لن يتمكّن من إعادة استخدام خطة الاستعلام المُصرَّفة). لذلك يوفّر JPQL أسلوبَين لربط المعاملات، ويستكملهما JPA بآلية @NamedQuery التي تُعلن الاستعلامات مسبقًا عند تحميل الفئة.
المعاملات الموضعية
المعاملات الموضعية هي عناصر نائبة تُكتب كـ ?1 و?2 و... (تبدأ من الرقم واحد). تُربط القيم عبر TypedQuery.setParameter(int, Object).
المعاملات الموضعية موجزة لكنها هشّة — إذا أعدت ترتيب جملة WHERE يجب أيضًا إعادة ترقيم كل استدعاء setParameter. لأي شيء أكثر تعقيدًا من سطر واحد، تكون المعاملات المسماة هي الخيار الأفضل دائمًا تقريبًا.
المعاملات المسماة
تستخدم المعاملات المسماة بادئة النقطتين :name وتُربط عبر setParameter(String, Object). الاسم يوثّق نفسه بنفسه ومستقل عن الموضع.
@NamedQuery.
الأنواع الزمنية والأنواع الخاصة
عند ربط java.util.Date (الواجهة البرمجية القديمة) كان يجب توفير تلميح TemporalType. مع واجهة java.time الحديثة — LocalDate وLocalDateTime وInstant — يعالجها Hibernate 6 بشكل أصلي، فلا حاجة لأي تلميح.
null إلى setParameter يُصدر IS NULL في SQL المُولَّد — لا تحتاج إلى فرع استعلام منفصل للمرشّحات الاختيارية عند استخدام Criteria API، لكن مع استعلامات JPQL النصية تكتب عادةً فحوصات null صريحة في الـ JPQL أو تستخدم أساليب استعلام منفصلة.
التصفيح: setFirstResult و setMaxResults
يُتحكَّم في التصفيح عبر TypedQuery بأسلوب سلسلي، وليس من خلال جمل خاصة بلهجة SQL مثل LIMIT/OFFSET. هذا يُبقي JPQL محمولًا عبر قواعد البيانات المختلفة.
إعلان الاستعلامات المسماة بـ @NamedQuery
الاستعلام المسمّى هو سلسلة JPQL تُعلَن مرة واحدة — على فئة الكيان — ويُشار إليها باسم منطقي في كل مكان آخر. يُحلّل JPA ويتحقق من صحة JPQL عند بدء تشغيل التطبيق (عند بناء EntityManagerFactory)، وليس في وقت التشغيل. هذا يعني اكتشاف أخطاء الصياغة عند الإقلاع، وليس عند أول طلب مستخدم.
تستخدم الاستعلامات المسماة المتعددة على نفس الكيان @NamedQueries({ @NamedQuery(...), @NamedQuery(...) })، أو في Java 8 وما بعده يمكنك ببساطة تكرار التعليق التوضيحي كما هو موضح أعلاه (دعم التعليقات التوضيحية القابلة للتكرار).
تنفيذ الاستعلام المسمّى
استدع em.createNamedQuery(name, resultClass) بدلًا من em.createQuery(jpqlString, resultClass). ربط المعاملات متطابق تمامًا.
EntityName.methodName (مثل Order.findByStatus). يبحث Spring Data JPA عن استعلام مسمّى باتباع هذه الاتفاقية تلقائيًا قبل توليد استعلام من اسم الأسلوب، لذا الاسم مفيد مضاعف.
الاستعلامات المسماة في مستودعات Spring Data JPA
إذا كنت تستخدم Spring Data JPA، فإن أسلوب مستودع باسم findByStatus على مستودع Order يحلّ الاستعلام المسمّى Order.findByStatus تلقائيًا إذا وُجد. يمكنك أيضًا تزيين أسلوب المستودع بـ @Query كبديل مضمَّن، لكن الاستعلامات المسماة المُعلَنة مسبقًا تتميّز بالتحقق من الصحة وقت الإقلاع.
اعتبارات الأداء
- تخزين خطة الاستعلام في الذاكرة المؤقتة: عند استخدام ربط المعاملات (بكلا الأسلوبين) يُصرف مزوّد الثبات JPQL مرة واحدة ويخزّن الخطة. تسلسل القيم مباشرةً في سلسلة JPQL يُجبر على إعادة التصريف عند كل استدعاء ويُفقد هذه الذاكرة المؤقتة كليًا.
- الاستعلامات المسماة تُصرَّف مرة واحدة عند الإقلاع: خطتها تُعاد استخدامها عند كل استدعاء دون أي حمل تحليل، مما يجعلها مسار تنفيذ JPQL الأكثر كفاءة.
- استخدم getSingleResult بحذر: يرمي
NoResultExceptionإذا لم تتطابق أي سجلات، وNonUniqueResultExceptionعند وجود سجلات متعددة. لفّه أو استخدمgetResultList()وافحصisEmpty()إذا كان عدم وجود نتائج ممكنًا.
الخلاصة
استخدم المعاملات المسماة (:name) بدلًا من الموضعية للقراءة والمتانة. استفد من setFirstResult/setMaxResults للتصفيح المحمول عبر قواعد البيانات. أعلن الاستعلامات المُنفَّذة كثيرًا أو المشتركة كـ @NamedQuery على فئة الكيان للحصول على التحقق من الصياغة وقت الإقلاع وأفضل إعادة استخدام لخطة الاستعلام. في الدرس القادم ستجمع هذه التقنيات مع دوال التجميع والتجميع والجمل HAVING.