@Query واستعلامات JPQL
@Query واستعلامات JPQL
أساليب الاستعلام المشتقة مريحة، غير أن لها حدودًا. حالما يتطلب استعلامٌ ما JOIN عبر جدولين، أو دالة تجميع، أو استعلامًا فرعيًا، أو أي منطق لا يمكن التعبير عنه باسم دالة واضح، تحتاج إلى كتابة الاستعلام بنفسك. تمنحك Spring Data JPA annotation واحدة لهذا الغرض: @Query.
ما هي JPQL؟
JPQL (لغة استعلام Jakarta Persistence) هي لغة الاستعلام المعرَّفة في مواصفة JPA. تبدو شبه متطابقة مع SQL، لكنها تعمل على الكيانات وحقولها، لا على الجداول والأعمدة. تترجم Hibernate (مزوّد JPA داخل Spring Boot) استعلام JPQL إلى نكهة SQL الصحيحة في وقت التشغيل — PostgreSQL أو MySQL أو H2 أو غيرها مما قمت بتهيئته.
فكر في كيان بسيط:
في JPQL تشير إلى Order (اسم الفئة) وo.status (الحقل)، وليس إلى اسم الجدول orders أو العمود status. هذا الفصل هو ما يجعل الكود مستقلًا عن المخطط المادي.
الاستخدام الأساسي لـ @Query
ضع الـ annotation مباشرةً فوق دالة المستودع. يحمل الخاصية value نص JPQL. اربط المعاملات بصيغة :name وطابقها مع معاملات الدالة باستخدام @Param:
Order)، وليس اسم جدول قاعدة البيانات (orders). إذا أعدت تسمية الجدول مع إبقاء اسم الفئة كما هو، فلن يتغير استعلام JPQL.
المعاملات الموضعية مقابل المسمّاة
تدعم JPQL كلا الأسلوبين. المعاملات المسمّاة (:name مع @Param) هي الأفضل بشدة لأنها تصمد أمام إعادة ترتيب المعاملات عند إعادة الهيكلة:
الربط عبر العلاقات
لأن JPQL تفهم نموذج الكائن، يمكنك اجتياز العلاقات بنقطة أو بـ JOIN صريح. للكود الذي يهتم بالأداء، يُعدّ JOIN FETCH الصريح الأداة الصحيحة — فهو يأمر Hibernate بتحميل العلاقة في استعلام SQL واحد بدلًا من إصدار استعلام لكل كيان (مشكلة N+1):
JOIN FETCH مع Pageable، يجب على Hibernate تحميل جميع الصفوف المطابقة في الذاكرة قبل التصفح، مما يلغي الغرض منه. استخدم بدلًا من ذلك استراتيجية الاستعلامين: استعلام @Query مع JOIN FETCH واستعلام عدد منفصل عبر خاصية countQuery في @Query، أو استخدم إسقاط DTO (مُغطّى في الدرس التالي).
إعادة نتائج غير كيانية
لا تقتصر على إعادة كائنات الكيان. يمكن لـ @Query الإسقاط على مجموعة فرعية من الحقول باستخدام تعبير المنشئ (constructor expression) أو إسقاط الواجهة:
الاستعلامات المعدِّلة: UPDATE و DELETE
الـ @Query للقراءة فقط بشكل افتراضي. لتشغيل جملة DML (UPDATE أو DELETE في JPQL، أو أي تعديل SQL) يجب إضافة annotation اثنتين:
@Modifying— تُخبر Spring Data أن هذا الاستعلام يُعدِّل الحالة.@Transactional— كل عملية كتابة يجب أن تعمل داخل معاملة.
نوع الإعادة int (أو Integer) يمنحك عدد الصفوف المتأثرة. يمكن للدالة أيضًا أن تُعيد void.
Order ثم شغّلت UPDATE جماعية في نفس المعاملة، فإن الكيان في الذاكرة لا يزال يحمل القيمة القديمة — Hibernate لا تحدّثه تلقائيًا. إما استدع entityManager.clear() بعد التحديث الجماعي، أو اضبط @Modifying(clearAutomatically = true) لتتولى Spring Data ذلك.
خاصية countQuery
عندما يستخدم @Query خاصتك JOIN FETCH أو SELECT معقدة لا تستطيع Hibernate تحويلها تلقائيًا إلى استعلام COUNT للتصفح بالصفحات، قدّم استعلام عدد صريحًا:
JPQL مقابل HQL مقابل Criteria
JPQL هو المعيار الخاص بـ JPA. نكهة Hibernate الخاصة (HQL) هي مجموعة عليا منه — تضيف ميزات كتحويلات TREAT والحسابات الموسّعة — لكن فضّل JPQL القياسية ما لم يكن لديك سبب ملموس. واجهة Criteria API (مُغطّاة في درس لاحق من سلسلة هذا البرنامج التعليمي) هي البديل الآمن النوع البرمجي، مفيد عندما يجب تحديد شكل الاستعلام في وقت التشغيل.
الخلاصة
يُعدّ @Query مع JPQL الأداة الأساسية لأي استعلام لا يمكن التعبير عنه كاسم دالة مشتقة. استخدم المعاملات المسمّاة مع @Param للمحافظة على القابلية للصيانة، واستخدم JOIN FETCH لإزالة مشكلات N+1، واستخدم تعبيرات المنشئ أو إسقاطات الواجهة عندما تحتاج فقط مجموعة فرعية من الحقول، وأضف @Modifying مع @Transactional لأي جملة DML. تغطي هذه الأنماط الغالبية العظمى من استعلامات المستودع في العالم الحقيقي.