JPA وHibernate ومدير الكيانات
JPA وHibernate ومدير الكيانات
قبل كتابة تعليق تعريفي واحد للتعيين، تحتاج إلى نموذج ذهني واضح للمكدّس المكوّن من ثلاث طبقات: مواصفة JPA، وتطبيق Hibernate، وواجهة EntityManager التي تجلس بين كودك وقاعدة البيانات. الخلط بين هذه الطبقات هو السبب الجذري لمعظم أخطاء المبتدئين — استيرادات خاطئة، وسلوك مفاجئ، وأخطاء يصعب اكتشافها.
JPA: المواصفة
JPA (Jakarta Persistence API) هي معيار — مجموعة من الواجهات والتعليقات التعريفية والقواعد المُعرَّفة في حزمة jakarta.persistence. لا تحتوي على أي كود تنفيذي؛ بل تُحدّد فقط الشكل الذي يجب أن يبدو عليه ORM. الفئات الرئيسية التي تتعامل معها يوميًا — @Entity، و@Id، وEntityManager، وTypedQuery — هي كلها واجهات أو تعليقات تعريفية تابعة لـ JPA.
بما أن JPA مجرد مواصفة، يمكن لأي مكتبة متوافقة تنفيذها. أكثر تطبيقَين شيوعًا هما Hibernate (الأكثر انتشارًا بفارق كبير) وEclipseLink. يستورد كودك من jakarta.persistence.*، لذا فإن التبديل بين المزوّدين لا يتطلب أي تغييرات في الكود — مجرد استبدال تبعية.
jakarta.persistence.*. إن رأيت javax.persistence.* في شرح ما فهو يستهدف مكدسًا أقدم (Spring Boot 2 / Hibernate 5). لا تخلط بينهما — التعليقات التعريفية تبدو متطابقة لكنها فئات مختلفة ولن يكتشفها ORM.
Hibernate: التطبيق
Hibernate هو المكتبة الفعلية التي تقوم بالعمل: تفحص فئاتك بحثًا عن تعليقات JPA التعريفية، وتولّد SQL، وتدير ذاكرة التخزين المؤقتة من المستوى الأول، وتترجم الاستثناءات، وتتكامل مع تجمّعات الاتصال. كما توفّر مجموعة من الامتدادات الخاصة بـ Hibernate (تعليقات في org.hibernate.annotations.* وواجهة Session) التي تتجاوز مواصفة JPA.
قاعدة عملية: فضّل تعليقات JPA التعريفية لكل ما تغطيه المواصفة، والجأ إلى امتدادات Hibernate الخاصة فقط حين لا تستطيع JPA التعبير عمّا تحتاجه (مثل أنواع SQL المخصصة، أو ضبط الجلب الدفعي). هذا يُبقي كودك قابلًا للنقل وأسهل في الفهم.
spring-boot-starter-data-jpa Hibernate 6 تلقائيًا. لا تحتاج إلى إضافة Hibernate مباشرةً. يُهيّئ المُشغِّل أيضًا DataSource (HikariCP)، وEntityManagerFactory، وإدارة معاملات Spring — كل ذلك من application.properties.
EntityManagerFactory وEntityManager
يُحمَّل وقت تشغيل JPA عبر كائنَين:
- EntityManagerFactory (EMF) — كائن ثقيل آمن للخيوط المتعددة يُنشأ مرة واحدة لكل تطبيق (واحد لكل وحدة إدامة). يقرأ بيانات التعيين الوصفية ويتحقق من المخطط ويُعدّ قوالب SQL. إنشاؤه مكلف؛ لا تنشئه أبدًا لكل طلب.
- EntityManager (EM) — كائن خفيف غير آمن للخيوط المتعددة يمثّل وحدة عمل واحدة. يمتلك سياق الإدامة (ذاكرة التخزين المؤقتة من المستوى الأول). تحصل على EM جديد لكل طلب أو معاملة، تستخدمه، ثم تغلقه.
في تطبيق Spring Boot لا تلمس EMF أو تنشئ EM يدويًا تقريبًا. تديرهما حاوية Spring وتحقن وكيل EM في حبّاتك عبر @PersistenceContext (أو بشكل غير مباشر عبر مستودعات Spring Data).
عمليات EntityManager الأساسية
يعرض EM واجهة برمجية صغيرة ومتعامدة. فهم ما تفعله كل طريقة بقاعدة البيانات وبسياق الإدامة أمر بالغ الأهمية لتجنّب الاستعلامات المفاجئة ومشكلات الأداء.
persist(entity)— يجدول INSERT. ينتقل الكيان إلى حالة مُدار. يُؤجَّل SQL حتى التفريغ (عادةً عند إتمام المعاملة).find(Class, id)— يُعيد الكيان المُدار لمفتاح أساسي محدد، أوnullإن لم يوجد. يتحقق من ذاكرة المستوى الأول قبل الاستعلام عن قاعدة البيانات.getReference(Class, id)— يُعيد وكيلًا (عنصرًا كسولًا) دون لمس قاعدة البيانات. مفيد لتعيين المفاتيح الأجنبية دون تحميل الكائن كاملًا. يرميEntityNotFoundExceptionعند أول وصول للحقل إذا لم يوجد الصف.merge(entity)— ينسخ حالة كيان منفصل إلى نسخة مُدارة ويُعيد النسخة المُدارة. الكائن الأصلي يبقى منفصلًا. مطلوب عند تمرير الكيانات عبر حدود المعاملة أو الجلسة.remove(entity)— يجدول DELETE. يجب أن يكون الكيان مُدارًا (مرّره عبرfindأولًا إن لزم).flush()— يُزامن سياق الإدامة مع قاعدة البيانات فورًا (ينفّذ SQL المعلّق) دون إتمام المعاملة.detach(entity)/clear()— يُزيل كيانًا واحدًا أو جميع الكيانات من سياق الإدامة. مفيد في المعالجة الدفعية لتحرير الذاكرة.
حالات دورة حياة الكيان
يوجد كل كيان JPA في إحدى أربع حالات بالنسبة لـ EntityManager:
- جديد (Transient) — مُنشأ بـ
new، غير مرتبط بأي EM. لا يُتتبّع تغييراته. - مُدار (Managed) — مرتبط بـ EM وسياق الإدامة. تُكتشف جميع التغييرات تلقائيًا وتُفرَّغ.
- منفصل (Detached) — كان مُدارًا لكن أُغلق EM أو استُدعيت
detach(). لا يُتتبّع؛ استخدمmerge()لإعادة الاتصال. - محذوف (Removed) — مجدول للحذف؛ سيُطلَق DELETE عند التفريغ.
LazyInitializationException. الحل هو تحميل البيانات بينما الكيان لا يزال مُدارًا: إما باستخدام استعلام JOIN FETCH، أو استدعاء Hibernate.initialize()، أو اعتماد إسقاط DTO.
ملف persistence.xml والضبط التلقائي في Spring Boot
يتطلب JPA التقليدي ملف META-INF/persistence.xml لتعريف وحدة الإدامة. يستبدل Spring Boot هذا كليًا بضبط تلقائي مدفوع بـ application.properties:
يفحص Spring Boot جميع فئات @Entity على مسار الفئات تلقائيًا — دون الحاجة لإدراج صريح. يُنشئ فول EntityManagerFactory بواسطة HibernateJpaAutoConfiguration ويُعرَّض كـ LocalContainerEntityManagerFactoryBean.
الخلاصة
JPA تُعرِّف العقد وHibernate يُنفّذه. EntityManager هو واجهتك البرمجية الأساسية: احصل على واحد لكل معاملة (يتولى Spring ذلك)، واستخدم persist وfind وmerge وremove لإدارة دورة حياة الكيانات، ودع فحص التغييرات يتولى UPDATEs تلقائيًا. في الدرس التالي ستكتب أول فئة @Entity لديك وترى بالضبط كيف يُعيّن Hibernate حقولها إلى جدول في قاعدة البيانات.