توابع الاستعلام المشتقّة
توابع الاستعلام المشتقّة
من أكثر ميزات Spring Data JPA إثارةً للإعجاب قدرتُه على توليد استعلام JPQL كامل — والـ SQL الناتج عنه — من مجرّد اسم تابع Java. أنت تُعلن عن النية؛ و Spring Data يشتقّ التنفيذ عند بدء تشغيل التطبيق. لا حاجة لـ @Query، ولا SQL، ولا تمديد EntityManager.
يشرح هذا الدرس بالتفصيل كيف يعمل هذا الاشتقاق، وما الكلمات المفتاحية المتاحة، وكيف يبدو الـ SQL المُولَّد، وأين يبدأ هذا النهج بالإيذاء — حتى تستخدمه بثقة دون مفاجآت في بيئة الإنتاج.
كيف تقرأ Spring Data اسم التابع
عند بدء تشغيل سياق التطبيق، تفحص Spring Data كل تابع مُعلَن في واجهة المستودع الذي لا يحمل تعليقًا توضيحيًا @Query ولا تنفيذًا مكتوبًا يدويًا. تحلّل الاسم وفق قواعد نحوية ثابتة:
- الكلمة المفتاحية للموضوع — ما الفعل المطلوب:
find…By،count…By،exists…By،delete…By،sum…By. - المعايير — الشرط الذي يأتي بعد
By: تعبير خاصية واحد أو أكثر مربوطة بـAnd/Or. - المعاملات — تُلحق باسم الخاصية:
Like،IgnoreCase،Between،LessThan،IsNull،In، وكثير غيرها. - نوع الإرجاع — يُستنتج من نوع الإرجاع Java المُعلَن: كيان واحد،
Optional<T>،List<T>،Page<T>، نوع بدائي لـcount، إلخ.
إذا تعذّر على المحلّل اللغوي تحليل مسار الخاصية مقابل الكيان المُدار يرمي PropertyReferenceException عند بدء التشغيل — وهو فشل سريع يمنع الأخطاء الصامتة في وقت التشغيل.
الكيان الذي سنعمل عليه
جميع الأمثلة أدناه تستخدم هذا الكيان:
findBy — النمط الأساسي
كل تابع findBy يُقابل عبارة SELECT … FROM orders WHERE …. الصيغة الأبسط تُصفّي على خاصية واحدة:
CustomerName في اسم التابع تُقابل حقل Java هو customerName، الذي يُحوّله Hibernate إلى العمود customer_name (بصيغة snake_case افتراضيًا). لا تكتب أسماء أعمدة SQL قطّ في توابع الاشتقاق.
countBy — العدّ دون جلب البيانات
يُصدر countBy عبارة SELECT COUNT(…) بدلًا من تحديد حالة الكيان. يجب أن يكون نوع الإرجاع long أو Long:
لأنه لا تُحمَّل حالة الكيان، يُعدّ countBy دومًا أرخص من جلب List واستدعاء .size(). هذا تمييز أداء مهم: يصل SELECT COUNT من قاعدة البيانات كرقم واحد؛ أما بديل SELECT * فيُسلسل كل عمود لكل صف متطابق عبر الشبكة إلى ذاكرة JVM الكومية (heap).
existsBy — فحص منطقي بوليانيّ
حين تحتاج فقط معرفة ما إذا كان صفٌّ موجودًا، يكون existsBy أرخص حتى من countBy. يُترجمه Hibernate إلى SELECT COUNT(*) > 0 (أو صيغة CASE WHEN EXISTS … بحسب اللهجة)، وتُحوّل Spring Data النتيجة إلى boolean:
deleteBy — الحذف المجمّع
يجب أن تعمل توابع deleteBy داخل معاملة (transaction). تجلب أولًا الكيانات المطابقة ثم تستدعي remove() على كل منها — مما يعني أن Hibernate يُطلق دوالّ ردّ النداء (@PreRemove) ويُكرّم سلاسل الاستدعاء (cascades). هذا مقصود لكنه قد يكون مكلفًا إذا تطابقت صفوف كثيرة.
IN يعقبه حذف فردي لكل سجل. إن احتجت عبارة DELETE FROM orders WHERE status = ? المجمّعة دون تحميل الكيانات، استخدم @Query مع @Modifying بدلًا من ذلك — فهو يُنفّذ عبارة SQL واحدة مباشرةً.
مرجع الكلمات المفتاحية للمعاملات
تتحد الكلمات المفتاحية التي تأتي بعد اسم الخاصية لتُكوّن شروطًا دقيقة:
Is/Equals—= ?(افتراضي حين لا توجد كلمة مفتاحية)Not—<> ?LessThan/LessThanEqual—< ?/<= ?GreaterThan/GreaterThanEqual—> ?/>= ?Between—BETWEEN ? AND ?(معاملان)Like/NotLike—LIKE ?(يجب أن تُدرج%في القيمة)Containing— يُلفّ المُعطى تلقائيًا في%…%StartingWith/EndingWith—?%/%?In/NotIn—IN (?)(المعاملCollection)IsNull/IsNotNull—IS NULL/IS NOT NULL(بلا معامل)True/False—= TRUE/= FALSE(بلا معامل)IgnoreCase— يُغلّف كلا الجانبين فيLOWER()
تحديد عدد النتائج
تدعم Spring Data استخدام Top وFirst كمحدّدات لحجم النتيجة مباشرةً في اسم التابع:
متى تكون الاستعلامات المشتقّة الأداة الصحيحة — ومتى لا تكون
تتألّق الاستعلامات المشتقّة في مرشّحات الكيان الواحدة البسيطة. لكنها تُصبح عبئًا حين:
- يتجاوز اسم التابع ~4 شروط — عندها يصبح أصعب قراءةً من JPQL المكافئ.
- تحتاج
JOINعبر كيانات متعددة — يدعم المحلّل التنقّل بالنقطة (findByCustomer_Email) لكن المسارات العميقة هشّة. - تحتاج شروطًا ديناميكية تعتمد على حالة وقت التشغيل — استخدم Specification API أو
@Queryبمعاملات مُسمّاة. - يتطلّب الأداء تلميح فهرسة محدّدًا، أو
FETCH JOINلتجنّب مشكلة N+1، أو استعلامًا أصليًا — مرّة أخرى، الجأ إلى@Query.
spring.jpa.show-sql=true وspring.jpa.properties.hibernate.format_sql=true إلى application.properties. فحص الـ SQL المُولَّد هو أسرع طريقة لاكتشاف ضرب ديكارتي عرضي أو ضربة فهرس مفقودة.
الخلاصة
تُحلّل Spring Data JPA أسماء التوابع عند بدء التشغيل وتشتقّ الـ JPQL المقابل — والـ SQL في نهاية المطاف — تلقائيًا. يُولّد findBy عبارات SELECT، ويُولّد countBy استعلامات COUNT كفؤة، ويُعدّ existsBy أرخص فحص للوجود، بينما تغطّي مجموعة غنية من الكلمات المفتاحية معظم الشروط الشائعة. استخدم الاستعلامات المشتقّة لمرشّحات الكيان الواحد الموجزة والمقروءة، وانتقل إلى @Query أو Specification API فور أن يصبح اسم التابع مُعقّدًا أو يتطلّب ضبط الأداء JPQL صريحًا.