We are still cooking the magic in the way!
الاستعلام باستخدام JPQL
الاستعلام باستخدام JPQL
كل تطبيق يحفظ البيانات يحتاج في نهاية المطاف إلى استرجاعها بطرق مرنة ومصفّاة ومرتّبة. يمكنك اللجوء إلى SQL الأصلية — لكن هذا يعني ربط الكود بأسماء جداول وأعمدة محددة، والتعامل مع لهجات قواعد بيانات مختلفة، والعمل مع كائنات ResultSet الخام بدلًا من كائنات الكيانات المكتوبة التي تديرها Hibernate بالفعل. يحلّ JPQL (لغة استعلام Jakarta Persistence) هذه المشكلة بأن يتيح لك الاستعلام عن نموذج الكائنات مباشرةً. الفكرة الجوهرية هي: يعمل JPQL على الكيانات وحقولها المُعيَّنة، لا على جداول قاعدة البيانات وأعمدتها.
JPQL مقابل SQL: التحوّل المفاهيمي
حين تكتب استعلام SQL أصليًا فأنت تخاطب قاعدة البيانات الفيزيائية: أسماء الجداول والأعمدة وشروط الربط بين المفاتيح الخارجية. حين تكتب JPQL فأنت تخاطب نموذج نطاق Java: أسماء فئات الكيانات وأسماء الحقول والعلاقات التي أعلنتها بـ @OneToMany و@ManyToOne وما شابه ذلك.
لنفترض وجود كيان Product مُعيَّن على الجدول products، بحقل unitPrice مُعيَّن على العمود unit_price. في SQL تكتب:
أما في JPQL فتكتب:
تُترجم Hibernate سلسلة JPQL هذه إلى SQL مناسبة لأي قاعدة بيانات تستهدفها — MySQL أو PostgreSQL أو Oracle أو H2 — فيبقى كود الاستعلام محمولًا بين قواعد البيانات المختلفة.
Product وليس products). يمكنك تخصيصه بـ @Entity(name = "Prod")، لكن معظم الفرق تتركه باسم الفئة. معرّفات JPQL حسّاسة لحالة الأحرف في أسماء الكيانات والحقول، لكنها غير حسّاسة للكلمات المفتاحية مثل SELECT وFROM وWHERE.
تشريح جملة JPQL
تحتوي جملة SELECT في JPQL على نفس البنود المنطقية في SQL لكن على الكيانات:
- SELECT — ما تريد إسقاطه (الكيان بأكمله أو حقل بعينه أو تعبير constructor)
- FROM — الكيان المُستعلَم عنه مع متغير تعريفي (اسم مستعار)
- WHERE — شروط التصفية على حقول الكيان وعلاقاته
- ORDER BY — الترتيب حسب اسم الحقل
- GROUP BY / HAVING — التجميع (يُغطَّى في الدرس الثالث)
أول استعلام JPQL في Spring Boot 3
في مشروع Spring Boot 3 / Hibernate 6 يحصل المكوّن على EntityManager مُحقونًا من الحاوية. إليك الإعداد الأدنى — كيان Product وطريقة مستودع تُشغّل استعلام JPQL:
لاحظ أمرين:
- يُعيد
em.createQuery(jpql, Product.class)كائنTypedQuery<Product>، ما يعني أنgetResultList()تُعيدList<Product>بلا حاجة لتحويل صريح. - المعامل المُسمّى
:minPriceيُربط بـsetParameter. لا تدمج أبدًا مدخلات المستخدم في سلسلة JPQL — ذلك حقن JPQL، مماثل لحقن SQL. تُغطَّى المعاملات بعمق في الدرس الرابع.
إسقاط كيان كامل مقابل حقول فردية
اختيار الكيان بأكمله (SELECT p FROM Product p) مريح لكنه يحمّل كل عمود مُعيَّن. إن كنت تحتاج فقط الاسم والسعر لصفحة قائمة الأسعار، فاسقط تلك الحقول فحسب:
Object[] وهو هشّ (تحويل خاطئ = ClassCastException في وقت التشغيل). في الدرس التاسع ستستخدم تعبيرات constructor — SELECT NEW com.example.store.dto.PriceDto(p.name, p.unitPrice) FROM Product p — للحصول على List<PriceDto> آمن النوع. استخدم اختيار الكيان الكامل فقط حين تنوي تعديل الكائنات المُعادة ضمن نفس المعاملة.
ترقيم الصفحات بـ setFirstResult و setMaxResults
تحميل آلاف الصفوف في استعلام واحد خطأ أداء شائع. استخدم ترقيم الصفحات لاسترجاع صفحة في كل مرة — يُفوّض JPQL ذلك تلقائيًا إلى صياغة LIMIT / OFFSET الأصلية في قاعدة البيانات:
ORDER BY قد تُعيد قاعدة البيانات الصفوف بأي ترتيب، ما يعني أن صفحات مختلفة قد تُعيد نفس الصف أو تتخطى صفوفًا. اجعل عمود الترتيب ثابتًا وفريدًا قدر الإمكان — المفتاح الأساسي خيار آمن افتراضيًا.
حساسية حالة الأحرف وقواعد التسمية
لـ JPQL قواعد تسمية مهمة قد تُربك المطورين القادمين من SQL:
- أسماء الكيانات حسّاسة لحالة الأحرف — سيُلقي
FROM productاستثناءIllegalArgumentExceptionإن كانت الفئة مُسمّاةProduct. - أسماء الحقول حسّاسة لحالة الأحرف — استخدم اسم حقل Java (
unitPrice) لا اسم العمود (unit_price). - الكلمات المفتاحية غير حسّاسة لحالة الأحرف —
selectوSELECTوSelectكلها تعمل، لكن الأحرف الكبيرة هي الاصطلاح. - القيم الحرفية النصية تستخدم علامة الاقتباس المفردة:
WHERE p.status = \'ACTIVE\'.
كيف تُترجم Hibernate JPQL إلى SQL
حين يُستدعى em.createQuery(...)، يُحلّل محلل استعلام Hibernate سلسلة JPQL إلى شجرة AST داخلية، ثم يُولّد SQL مستهدفًا اللهجة المُهيَّأة لديك. يمكنك رؤية SQL المُولَّدة — وهو أمر لا غنى عنه في تنقيح الأخطاء — بإضافة هذا إلى application.properties:
بهذه الإعدادات، يُولّد استعلام SELECT p FROM Product p WHERE p.unitPrice > :minPrice على PostgreSQL شيئًا كالتالي:
لاحظ كيف عيَّنت Hibernate كلًّا من Product إلى products وunitPrice إلى unit_price، واستبدلت المعامل المُسمّى :minPrice بعنصر تعبئة JDBC ? — منحك بذلك استعلامًا مُعلَّم بالكامل وآمنًا من الحقن.
الخلاصة
JPQL لغة استعلام موجّهة للكائنات تعمل على نموذج كيانك لا على جداول قاعدة البيانات. تكتب الاستعلامات بمصطلحات أسماء الفئات والحقول، وتُترجمها Hibernate إلى SQL خاصة بلهجة قاعدة البيانات في وقت التشغيل. استخدم TypedQuery لتجنب التحويل الصريح، واستخدم دائمًا المعاملات ولا تدمج النصوص مطلقًا، وأضف ORDER BY عند ترقيم الصفحات، وفعّل تسجيل SQL أثناء التطوير لفهم ما يصل فعليًا إلى قاعدة البيانات. يمتد الدرس التالي هذا الأساس بانضمامات JPQL وانضمامات الجلب للتنقل في علاقات الكيانات.