أنواع الجلب: EAGER مقابل LAZY
أنواع الجلب: EAGER مقابل LAZY
كل علاقة ترسمها في Hibernate — سواء كانت @OneToOne أو @OneToMany أو @ManyToOne أو @ManyToMany — تحمل استراتيجية جلب تجيب على سؤال واحد: متى ينبغي لـ Hibernate تحميل البيانات المرتبطة؟ والإجابة لها تداعيات عميقة على الصحة واستهلاك الذاكرة وأداء الاستعلامات. الخطأ في هذا الأمر هو أحد أكثر أسباب بطء تطبيقات Spring Boot شيوعًا.
الاستراتيجيتان
الجلب الفوري (EAGER) يُحمّل العلاقة فورًا ضمن العملية ذاتها التي تُحمّل فيها الكيان المالك. إذا حمّلت كيان Customer وكانت مجموعة orders بنوع EAGER، فسيُنفّذ Hibernate ما يلزم من SQL لملء تلك المجموعة فورًا — لن تمتلك أبدًا كائن Customer لم تُهيَّأ فيه قائمة طلباته.
الجلب الكسول (LAZY) يُؤجّل التحميل. يمنحك Hibernate كائن بروكسي (لعلاقة واحدة) أو مجموعة مغلّفة غير مُهيَّأة (للمجموعات). يُطلَق SQL الحقيقي فقط عند أول مرة يصل فيها كودك إلى هذا البروكسي أو يتكرر على المجموعة.
القيم الافتراضية في Hibernate
تحدّد مواصفة JPA القيم الافتراضية ويلتزم Hibernate بها:
@ManyToOne— الافتراضي EAGER@OneToOne— الافتراضي EAGER@OneToMany— الافتراضي LAZY@ManyToMany— الافتراضي LAZY
يمكنك تجاوز القيمة الافتراضية باستخدام الخاصية fetch:
كيف يعمل LAZY في الممارسة
حين تُحدّد علاقةً بـ LAZY، يستبدل Hibernate الكائن بـ بروكسي (proxy object) (لعلاقات @ManyToOne / @OneToOne) أو بـ PersistentBag / PersistentList غير مُهيَّأة (للمجموعات). يشبه البروكسي النوع الحقيقي لكنه لا يحمل إلا المفتاح الأساسي. بمجرد استدعاء أي دالة getter غير معرِّفة، يُطلق Hibernate SQL ويستبدل البروكسي بالبيانات الحقيقية.
LazyInitializationException. وهي أكثر أخطاء Hibernate شيوعًا في وقت التشغيل.
LazyInitializationException — الخطأ الكلاسيكي
يظهر هذا الخطأ حين تُحمّل كيانًا داخل دالة خدمة، ثم تُعيده إلى متحكم أو مُسلسِل (serializer) يعمل خارج المعاملة، فيحاول المُسلسِل اجتياز مجموعة غير مُهيَّأة:
ثمة ثلاثة حلول صحيحة مرتّبة تنازليًا حسب الأفضلية:
- استخدم DTO — اسقط فقط ما يحتاجه المُستدعي داخل المعاملة؛ لا تكشف الكيانات من المتحكمات.
- استخدم استعلام JOIN FETCH — حمّل بالقوة العلاقات التي تحتاجها لهذه الحالة تحديدًا (يُغطّى في الدرس 8).
- أضف
@Transactionalعلى دالة الخدمة — يُبقي الجلسة مفتوحة أثناء الوصول إلى البيانات (انتبه للنمط المضاد Open-Session-in-View أدناه).
Open-Session-in-View — الإعداد الافتراضي المثير للجدل
يُفعّل Spring Boot بشكل افتراضي Open Session in View (OSIV) عبر الإعداد spring.jpa.open-in-view=true. يُبقي هذا جلسة Hibernate مفتوحة طوال دورة حياة طلب HTTP بأكملها، بما فيها مرحلة عرض النتيجة / تسلسل JSON. يُخفي هذا الإعداد LazyInitializationException لكن بتكلفة عالية: يظل اتصال قاعدة البيانات محجوزًا طوال مدة الطلب، بما في ذلك أي استدعاءات خارجية أو عرض بطيء يحدث بعد عودة طبقة الخدمة.
spring.jpa.open-in-view=false في application.properties. ستظهر لديك LazyInitializationExceptions في البداية — عاملها كإشارات تشخيصية تخبرك بالضبط أيّ العلاقات تحتاج JOIN FETCH أو إسقاط DTO. هذا الانضباط يُكافئك بضغط أقل على مجموعة الاتصالات وزمن استجابة أكثر قابلية للتنبؤ.
اختيار الاستراتيجية المناسبة
القاعدة التي يتبعها مطورو Hibernate ذوو الخبرة:
- استخدم
LAZYدومًا كقيمة افتراضية لجميع العلاقات. اجلب ما تحتاجه صراحةً لكل استعلام على حدة. EAGERعلى مستوى الرسم يناسب فقط كائنات القيم الصغيرة جدًا التي تُحتاج دائمًا ولن تنمو أبدًا (نادر جدًا).- اجلب البيانات بالقوة لكل استعلام باستخدام
JOIN FETCHفي JPQL أو Entity Graphs — لا بتغيير الرسم.
EAGER قد يُخفي ضربات ديكارتية
حين يمتلك كيان ما مجموعتَين أو أكثر بنوع EAGER، قد يضم Hibernate كليهما في استعلام SQL واحد مما ينتج عنه ضرب ديكارتي (Cartesian product): إذا كان لدى Customer عشرة orders ولكل طلب خمسة lines، سيُعيد الضم 50 صفًا لعميل واحد. يُزيل Hibernate التكرارات في الذاكرة، لكن قاعدة البيانات أجرت 50 مرة من العمل. كثيرًا ما يكون هذا غير ملحوظ أثناء التطوير (بيانات صغيرة) وكارثيًا في الإنتاج.
الخلاصة
نوع الجلب هو المقبض الذي يتحكم في متى يُطلَق SQL للبيانات المرتبطة. ينبغي تجاوز القيم الافتراضية في JPA — EAGER لـ @ManyToOne و@OneToOne، وLAZY للمجموعات — لجعل كل شيء افتراضيًا LAZY. اجلب العلاقات بالقوة فقط حين تحتاجها فعلًا لعملية محددة، باستخدام آليات لكل استعلام على حدة كـ JOIN FETCH أو Entity Graphs بدلًا من تغيير الرسم. أوقف Open-Session-in-View، وعامل LazyInitializationException كمشخّص، وفضّل إسقاطات DTO. الدرسان التاليان يترجمان هذه المبادئ إلى أنماط JPQL وEntity Graph ملموسة.