تعيين الوراثة
تعيين الوراثة
يلجأ التصميم الكائني الطبيعي إلى الوراثة لتمثيل التسلسلات الهرمية في العالم الحقيقي: نوع أساسي Payment مع فئات فرعية ملموسة CreditCardPayment وBankTransferPayment وCryptoPayment. غير أن قواعد البيانات العلائقية لا تمتلك مفهومًا أصيلًا للأنواع الفرعية. تجسر JPA هذه الهوّة عبر ثلاث استراتيجيات مختلفة لتعيين الوراثة، لكل منها تبادلات قابلة للقياس في تعقيد المخطط وأداء الاستعلام وانضباط الأعمدة غير القابلة للقيم الخالية. اختيار الاستراتيجية الصحيحة من البداية يُجنّبك عمليات نقل مؤلمة للمخطط في وقت لاحق.
لمحة سريعة عن الاستراتيجيات الثلاث
- SINGLE_TABLE — جدول واحد للتسلسل الهرمي بأكمله، يُحدّد عمود المميّز النوع الفرعي.
- JOINED — جدول واحد لكل فئة؛ تشترك جداول الأنواع الفرعية في المفتاح الأساسي ذاته وتنضم إلى الوالد.
- TABLE_PER_CLASS — جدول مستقل لكل فئة ملموسة؛ لا أعمدة مشتركة ولا انضمامات.
@Inheritance دون تحديد strategy، تتخذ JPA SINGLE_TABLE افتراضيًا. معرفة هذا تمنع قرارات مخطط غير مقصودة.
الاستراتيجية الأولى — SINGLE_TABLE
تُعيَّن جميع فئات التسلسل الهرمي على جدول واحد. تضيف تعليقة @DiscriminatorColumn عمودًا تُخبر قيمتُه Hibernate أيَّ نوع فرعي يمثّله الصف المعني. تُخزَّن أعمدة النوع الفرعي التي لا تنطبق على صف بعينه كـNULL.
ينتج DDL المُولَّد لهذا التسلسل الهرمي جدول payments واحدًا يشمل أعمدة الأنواع الثلاثة جميعها بالإضافة إلى عمود المميّز payment_type. يحمل صف CreditCardPayment قيمة NULL في iban وbank_code؛ ويحمل صف BankTransferPayment قيمة NULL في masked_card_number وcardholder_name.
Payment جملة SELECT واحدة فقط بلا انضمامات. وهي أفضل خيار افتراضي حين يكون التسلسل الهرمي ضحلًا وأعمدة النوع الفرعي قليلة أو تقبل القيم الخالية بطبيعتها. طبّق قيود NOT NULL على مستوى التطبيق (عبر @NotNull في Bean Validation) بدلًا من مستوى قاعدة البيانات حين تختار هذه الاستراتيجية.
الاستراتيجية الثانية — JOINED
تحصل كل فئة في التسلسل الهرمي على جدولها الخاص. يحتفظ الجدول الوالد بالأعمدة المشتركة؛ يحتوي جدول كل نوع فرعي فقط على الأعمدة الفريدة لذلك النوع ويشترك في قيمة المفتاح الأساسي ذاتها مع الصف الوالد. يُنشئ Hibernate انضمام JOIN كلما احتاج إلى إنشاء كائن نوع فرعي كامل.
يُولّد تحميل CreditCardPayment عبر المعرّف:
يستلزم الاستعلام متعدد الأشكال — SELECT p FROM Payment p — انضمام LEFT OUTER JOIN عبر كل جدول نوع فرعي. مع ثلاثة أنواع فرعية يُنشئ Hibernate ثلاثة انضمامات. هذا صحيح لكنه يزداد تكلفةً كلما نما التسلسل الهرمي.
أعمدة المميّز مع JOINED
لا تشترط JOINED افتراضيًا وجود عمود مميّز — يمكن لـHibernate استنتاج النوع الفرعي من جداول الانضمام الموجودة. ومع ذلك، يُنصح بإضافته من أجل وضوح القراءة وللأدوات التي تستعلم عن قاعدة البيانات مباشرةً:
الاستراتيجية الثالثة — TABLE_PER_CLASS
تمتلك كل فئة ملموسة جدولًا مستقلًا تمامًا يُعيد تكرار أعمدة الوالد. لا يوجد جدول والد مشترك ولا انضمام بين الجداول المتجاورة.
لاحظ استخدام GenerationType.TABLE بدلًا من IDENTITY. لعدم وجود جدول والد مشترك، لا تستطيع أعمدة الهوية على مستوى قاعدة البيانات ضمان التفرد عبر جميع جداول الأنواع الفرعية. يُشترط استخدام تسلسل جدول JPA أو مفتاح أساسي من نوع UUID.
يُجبر الاستعلام متعدد الأشكال — SELECT p FROM Payment p — Hibernate على إصدار UNION ALL عبر جميع الجداول الملموسة:
UNION ALL لا تستطيع استخدام الفهارس بفاعلية على الجداول الكبيرة. استخدمها فقط حين لا تحتاج أبدًا إلى الاستعلام عن التسلسل الهرمي بصورة متعددة الأشكال — مثلًا حين تُستعلم الأنواع الفرعية دائمًا مباشرةً عبر نوعها الملموس والتسلسل الهرمي ثابت.
اختيار الاستراتيجية — دليل القرار
- تسلسل هرمي ضحل، أنواع فرعية قليلة، قبول الحقول القابلة للقيم الخالية → SINGLE_TABLE. أعلى أداء للقراءة، أبسط مخطط.
- تسلسل هرمي عميق، نزاهة قوية في قاعدة البيانات مطلوبة، قيود NOT NULL مهمة → JOINED. منظَّم، مرن، يدفع تكلفة الانضمام عند التحميل متعدد الأشكال.
- الأنواع الفرعية تُستعلم دائمًا باستقلالية، لا حاجة لـ JPQL متعدد الأشكال → TABLE_PER_CLASS. تجنّبها إلا إذا كنت متيقّنًا من نمط الاستعلام.
الاستعلامات متعددة الأشكال وطبقة المستودع
يعمل Spring Data JPA بسلاسة مع الاستراتيجيات الثلاث. صرّح بمستودع أساسي على النوع الوالد للاستعلام عن التسلسل الهرمي بأكمله، ومستودعات ملموسة لمعرِّفات النوع الفرعي الخاصة:
الخلاصة
توفّر JPA ثلاث استراتيجيات لتعيين الوراثة لجسر التسلسلات الهرمية الكائنية إلى الجداول العلائقية. تخزّن SINGLE_TABLE كل شيء في جدول واحد مع مميّز — استعلامات أسرع لكن الأعمدة قابلة للقيم الخالية. تُنظّم JOINED كل فئة في جدولها الخاص — أفضل ضمانات للنزاهة، وتدفع تكلفة الانضمام لكل تحميل متعدد الأشكال. تُكرّر TABLE_PER_CLASS أعمدة الوالد في كل جدول ملموس — تجنّب الاستعلامات متعددة الأشكال وتستلزم مُولِّد مفتاح من غير نوع IDENTITY. اختر الاستراتيجية التي تتوافق مع متطلبات نزاهة مخططك ونمط استعلامك السائد، وقاوم تغييرها لاحقًا — فالانتقال بين الاستراتيجيات يستلزم تغييرات DDL على بيانات الإنتاج.