@ManyToMany — علاقة كثير بكثير
@ManyToMany — علاقة كثير بكثير
تنشأ علاقة كثير بكثير عندما يستطيع كل صف في الجدول A الارتباط بصفوف متعددة في الجدول B، وكذلك يستطيع كل صف في B الارتباط بصفوف متعددة في A. المثال الكلاسيكي هو العلاقة بين Student وCourse: يلتحق الطالب بمقررات عدة، والمقرر الواحد يضم طلابًا كثيرين. في قواعد البيانات العلائقية يتطلب ذلك جدول وصل (join table) — يُعرف أحيانًا بجدول الجسر أو جدول الترابط — يحتوي على أزواج من المفاتيح الخارجية. يستطيع Hibernate إدارة هذا الجدول تلقائيًا، أو يمكنك التحكم فيه بنفسك عندما تحتاج العلاقة إلى سمات إضافية.
التعيين الأساسي
ضع تعليق @ManyToMany على حقل المجموعة في أحد الطرفين، وأخبر Hibernate بجدول الوصل عبر @JoinTable:
الكيان الذي يُعلن @JoinTable هو الجانب المالك. يستخدم الكيان الآخر mappedBy للإشارة إلى الحقل في المالك. ينتج Hibernate جدول الوصل في DDL على النحو التالي:
@ManyToMany مع List، يستخدم Hibernate استراتيجية الحقيبة (bag): عند حذف عنصر واحد يُصدر أمر DELETE ALL لذلك المالك ثم يُعيد إدراج الصفوف المتبقية. أما Set فيتجنب ذلك بإصدار أمر DELETE واحد يستهدف العنصر المحذوف فحسب. استخدم Set دائمًا في علاقات كثير بكثير.
إدارة الطرفين — دوال المساعدة
في علاقات الاتجاه المزدوج أنت مسؤول عن إبقاء كلا طرفي رسم الكائنات في الذاكرة متسقَين. دالة المساعدة على الجانب المالك هي النمط المتعارف عليه:
نوع الجلب والقيمة الافتراضية
نوع الجلب الافتراضي لـ @ManyToMany هو FetchType.LAZY، وهو ما تريده في الغالب. يؤدي التبديل إلى EAGER إلى تحميل Hibernate لجدول الوصل بأكمله مع كل أصل تلمسه، حتى وإن لم تحتج المجموعة المرتبطة.
عندما يحتوي جدول الوصل على أعمدة إضافية
لنفترض أن التسجيل يخزن أيضًا grade وختم زمني enrolled_at. لا يستطيع @ManyToMany البسيط تمثيل هذا لأنه لا يستطيع تعيين أعمدة إضافية في جدول الوصل. الحل هو ترقية جدول الوصل إلى كيان مستقل:
الآن لدى Student علاقة @OneToMany نحو Enrollment، وكذلك Course. يُعرف هذا النمط بـكيان الوصل (join entity) أو كيان الترابط (association entity) وهو في الغالب الخيار الأفضل على المدى البعيد — إذ تكاد جداول الوصل في الأنظمة الحقيقية تنمو لتكتسب سمات إضافية بمرور الوقت.
@EmbeddedId مؤلَّف من كلا المفتاحَين الخارجيَّين. هذا يعمل لكنه أطول بكثير في التعبير. استخدام مفتاح بديل بسيط مُولَّد بـ @GeneratedValue (كما في المثال أعلاه) أوضح ويُحقق أداءً مماثلًا. اختر المفتاح المركَّب فقط عندما يكون للمفتاح الطبيعي معنى خارج Hibernate.
الحذف والتتالي
بصورة افتراضية، لا ينتشر حفظ أو حذف Student إلى صفوف جدول الوصل أو إلى كيانات Course. يمكنك تفعيل التتالي لعمليات بعينها:
CascadeType.REMOVE في علاقة كثير بكثير أبدًا. سيؤدي تتالي الحذف عبر الجانب المالك إلى حذف الكيانات المرتبطة نفسها (صفوف Course ذاتها) وليس صفوف جدول الوصل فحسب، مما يُدمّر بيانات يشاركها كيانات أخرى. لحذف العلاقة فقط، استدع دالة المساعدة التي تُزيل العنصر من كلتا المجموعتين ودع Hibernate يحذف صف جدول الوصل اليتيم.
الاستعلام
الانضمام عبر علاقة كثير بكثير في JPQL أمر مباشر؛ يُترجم Hibernate اجتياز المجموعة تلقائيًا إلى انضمام عبر جدول الوصل:
لا تحتاج إلى الإشارة إلى جدول الوصل باسمه في JPQL — فـ Hibernate يعرفه من بيانات التعيين الوصفية. وهذا أحد مزايا الإنتاجية عند العمل على مستوى الكائنات بدلًا من مستوى SQL.
الخلاصة
يُعيِّن @ManyToMany علاقة كثير بكثير ثنائية الاتجاه عبر جدول وصل تديره Hibernate تلقائيًا. يُعلن الجانب المالك @JoinTable؛ ويستخدم الجانب العكسي mappedBy. استخدم Set بدلًا من List لنوع المجموعة، وأبقِ كلا جانبَي الرسم في الذاكرة متسقَين عبر دوال المساعدة، ولا تُتلي عملية REMOVE أبدًا عبر هذا الترابط. عندما يحتاج جدول الوصل إلى سمات إضافية، استبدل التعليق بكيان وصل مخصص مدعوم بعلاقتَي @ManyToOne — وهذا هو الخيار الصائب دائمًا تقريبًا في أنظمة الإنتاج.