الربط وجلب البيانات دفعةً واحدة في JPQL
الربط وجلب البيانات دفعةً واحدة في JPQL
في SQL تربط الجداول، وفي JPQL تربط علاقات الكيانات. التحوّل دقيق لكنه عميق الأثر: بدلًا من تسمية الأعمدة والمفاتيح الأجنبية تتنقّل عبر رسم الكائنات التي تعرفها JPA بالفعل، ويترجم Hibernate هذا التنقّل إلى SQL ملائمة لقاعدة البيانات الأساسية. يتناول هذا الدرس المفردات الكاملة للربط في JPQL — الداخلي والخارجي والضمني والصريح، وأهمّها جميعًا ربط الجلب (fetch join) — مع شرح مقايضات الأداء التي يجب على كل مطوّر في بيئة الإنتاج أن يفهمها.
نموذج المجال
تستخدم جميع الأمثلة في هذا الدرس نموذج تجارة إلكترونية مصغّرًا. الكيان Order يحتوي على عدة كيانات OrderItem، وكل منها مرتبط بكيان Product؛ كما ينتمي الكيان Order إلى كيان Customer واحد.
لاحظ أن كل @ManyToOne و@OneToMany يستخدم FetchType.LAZY. هذا هو الإعداد الصحيح للإنتاج — تُحمَّل الكائنات المترابطة فقط عند الوصول إليها صراحةً. وهو أيضًا ما يجعل فهم الربط أمرًا بالغ الأهمية: فالتحميل الكسول بدون ربط مناسب يؤدي مباشرةً إلى مشكلة N+1.
الربط الضمني مقابل الربط الصريح
يدعم JPQL أسلوبَين نحويَّين لربط الكيانات ذات الصلة.
الربط الضمني — التنقّل عبر نقطة المسار. يُولّد Hibernate ربط SQL المطلوب تلقائيًا:
الربط الصريح — الكلمة المفتاحية JOIN مع اسم مستعار، تمامًا كما في SQL:
كلاهما ينتج نفس INNER JOIN في SQL. يُفضَّل الربط الصريح متى احتجت إلى الإشارة إلى الكيان المرتبط أكثر من مرة في الاستعلام — فهو يتجنّب التنقّلات المتكررة في المسار ويوضّح القصد.
الربط الداخلي والخارجي والضربي
يحاكي JPQL أنواع الربط القياسية في SQL، لكنه يستهدف العلاقات بدلًا من الجداول:
JOIN/INNER JOIN— يعيد فقط الصفوف التي تتحقق فيها العلاقة (مفتاح أجنبي غير فارغ، مجموعة غير فارغة).LEFT JOIN/LEFT OUTER JOIN— يعيد الكيان المالك حتى عندما تكون الكيان المرتبط غائبًا (NULL على الجانب الأيمن).CROSS JOIN(نادر الاستخدام) — الضرب الديكارتي لنطاقَي كيانَين.
DISTINCT إلى SQL، وتزيل التكرارات من قائمة Java. بدونها سيظهر كيان Customer لديه ثلاثة طلبات ثلاث مرات في القائمة لأن SQL تنتج ثلاثة صفوف.
مشكلة N+1 — لماذا وُجد ربط الجلب
تخيّل أنك تحمّل 50 طلبًا ثم تطبع اسم عميل كل طلب:
هذا يُطلق استعلامًا واحدًا لجلب الطلبات بالإضافة إلى 50 استعلامًا إضافيًا لجلب كل عميل على حدة — 51 رحلة إجمالًا. مع مئات الصفوف يصبح هذا عنق زجاجة خطير. هذه هي المشكلة الكلاسيكية المعروفة بـ مشكلة N+1 في الاختيار.
FETCH JOIN — تحميل الرسم البياني في استعلام واحد
يخبر ربط الجلب Hibernate بتحميل الكيان المرتبط بنشاط في نفس جملة SQL، متجاوزًا الإعداد الكسول لهذا الاستعلام تحديدًا:
SQL المولَّدة تكون تقريبًا:
استعلام واحد بدلًا من 51. المقايضة هي مجموعة نتائج أوسع — أعمدة أكثر لكل صف — لكن ذلك دائمًا تقريبًا أفضل من عشرات الرحلات الإضافية.
LEFT JOIN FETCH للارتباطات القابلة للقيم الفارغة
إذا كانت الارتباط قد يكون فارغًا (مثلًا طلب بدون عميل في نموذجك)، استخدم LEFT JOIN FETCH حتى تُعاد الطلبات التي بلا عميل أيضًا:
ربط جلب المجموعة — مصيدة الصفوف المكررة
جلب مجموعة من نوع @OneToMany يُضخّم النتيجة على المستوى SQL. طلب واحد يحتوي على أربعة عناصر يُنتج أربعة صفوف SQL جميعها تشير إلى نفس كائن Order. بدون DISTINCT ستحتوي قائمة Java على أربع نسخ من هذا الطلب:
LIMIT/OFFSET) على استعلام يجلب مجموعة بـ fetch join، لأن عدد الصفوف في قاعدة البيانات لا يساوي عدد الكيانات. يعود Hibernate إلى جلب جميع الصفوف في الذاكرة ثم يُرقّمها صفحيًا هناك. للنقاط الطرفية ذات الترقيم الصفحي، اجلب المعرّفات أولًا باستعلام عادي مع ترقيم صفحي، ثم حمّل الرسم البياني الكامل في استعلام ثانٍ باستخدام WHERE o.id IN :ids.
ربط الجلب المتسلسل
يمكنك جلب ارتباطات متعددة في استعلام واحد. هنا نحمّل الطلبات مع عناصرها، ونُهيّئ منتج كل عنصر أيضًا:
o.items وo.tags) يُنشئ ضربًا ديكارتيًا لكلتيهما. استخدم @BatchSize أو استعلامات منفصلة للمجموعة الثانية.
استخدام ربط الجلب في مستودع Spring Data
يتيح لك Spring Data JPA وضع استعلامات JPQL مباشرةً على أساليب المستودع باستخدام @Query:
استعلام countQuery المنفصل مطلوب عندما يحتوي الاستعلام الرئيسي على ربط جلب — لا يستطيع Spring Data اشتقاق استعلام العدّ تلقائيًا من استعلام يحتوي على fetch join.
الخلاصة
تتبع روابط JPQL القواعد الدلالية ذاتها لـ SQL لكنها تعمل على رسم الكائنات. استخدم JOIN / LEFT JOIN الصريح عندما تحتاج إلى التصفية أو الفرز حسب كيان مرتبط. استخدم JOIN FETCH / LEFT JOIN FETCH عندما تحتاج إلى تهيئة ارتباط وتجنّب مشكلة N+1. أضف دائمًا DISTINCT عند جلب مجموعة بربط جلب، وتجنّب دمج الترقيم الصفحي مع جلب روابط المجموعات. الدرس القادم يتناول دوال التجميع والتجميع في JPQL.