Hibernate وتخطيط الكيانات

المفاتيح الأساسية و @GeneratedValue

18 دقيقة الدرس 4 من 13

المفاتيح الأساسية و @GeneratedValue

لكل كيان JPA مفتاح أساسي — القيمة التي تُعرّف الصف بصورة فريدة في جدوله المُربوط. كيفية توليد ذلك المفتاح عند تنفيذ INSERT هي من أولى القرارات التي تتخذها عند ربط نموذج المجال، والاختيار الخاطئ يظهر لاحقًا على شكل اختناق في الأداء أو خطأ في التزامن. يغطّي هذا الدرس آليات @Id وكل استراتيجية متاحة ضمن @GeneratedValue مع المفاضلات العملية التي ينبغي أن تقود اختيارك.

الحد الأدنى: @Id

أي حقل ثابت مُعلَّق بـ @Id يصبح مفتاحًا أساسيًا. يمكنك وضع التعليق التوضيحي على الحقل نفسه (وصول الحقل) أو على الـ getter (وصول الخاصية)؛ يستخدم Hibernate الأسلوب الذي تختاره لجميع التعيينات الأخرى في ذلك الكيان. تُفضّل الممارسات الحديثة بشدة وصول الحقل.

import jakarta.persistence.*; @Entity @Table(name = "orders") public class Order { @Id private Long id; // التطبيق مسؤول عن تعيين هذه القيمة private String status; // الـ getters والـ setters ... }

بدون @GeneratedValue، يجب على التطبيق تعيين id قبل استدعاء persist(). هذا مقصود أحيانًا (المفاتيح الطبيعية، أو معرّفات UUID المُولَّدة في الكود)، لكن بالنسبة لمفاتيح الأعداد الصحيحة البديلة فهو غير عملي.

@GeneratedValue والاستراتيجيات الأربع

تعليق الحقل @Id بـ @GeneratedValue يُفوّض توليد المفتاح إلى مزوّد JPA. تقبل سمة strategy أربع قيم من تعداد GenerationType.

IDENTITY — دع عمود قاعدة البيانات يُولَّد تلقائيًا

GenerationType.IDENTITY يُعيَّن على عمود قاعدة البيانات من نوع AUTO_INCREMENT (MySQL/MariaDB) أو GENERATED ALWAYS AS IDENTITY (PostgreSQL 10+، SQL Server). تُخصّص قاعدة البيانات المفتاح عند الإدراج ويقرأه Hibernate فورًا.

@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;

DDL الذي يُولّده Spring Boot لهذا على MySQL يبدو كالتالي:

CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT, status VARCHAR(50), PRIMARY KEY (id) );
IDENTITY تُعطّل إدراجات الدُفعات في Hibernate. لأن المفتاح لا يُعرف إلا بعد تنفيذ INSERT، لا يستطيع Hibernate تأجيل INSERT إلى نهاية المعاملة وتجميع صفوف متعددة. إذا أدرجت آلاف الصفوف في حلقة، فإن IDENTITY تفرض رحلة ذهاب وإياب واحدة لكل صف. للأعمال الكثيفة الكتابة بالجملة، انتقل إلى SEQUENCE أو TABLE.

SEQUENCE — كائن تسلسل قاعدة البيانات

GenerationType.SEQUENCE يُفوّض إلى كائن تسلسل قاعدة بيانات مُسمَّى (مدعوم بشكل أصلي في PostgreSQL وOracle وH2 وSQL Server 2012+؛ غير متاح في MySQL قبل الإصدار 8). يستدعي Hibernate NEXT VALUE FOR sequence_name قبل INSERT، لذا يعرف المفتاح قبل كتابة الصف ويمكنه تجميع الإدراجات بحرية.

@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "order_seq") @SequenceGenerator(name = "order_seq", sequenceName = "order_id_seq", allocationSize = 50) private Long id;

معامل allocationSize بالغ الأهمية للأداء. مع allocationSize = 50، يجلب Hibernate قيمة تسلسل واحدة من قاعدة البيانات ثم يزيد في الذاكرة لعمليات الإدراج الـ49 التالية قبل أن يتواصل مع قاعدة البيانات مجددًا. يجب أن يزيد تسلسل قاعدة البيانات بنفس المقدار (INCREMENT BY 50).

اختر allocationSize بعناية. القيمة الصغيرة جدًا (مثل 1) تعني رحلة ذهاب وإياب لقاعدة البيانات مع كل إدراج — مطابق لـ IDENTITY. القيمة الكبيرة جدًا تُهدر مساحة المعرّفات عند إعادة تشغيل التطبيق (يُتجاهَل الجزء الموجود في الذاكرة). 50 هي الافتراضي في JPA ونقطة بداية معقولة؛ ارفعها إلى 100–500 لخدمات الإدراج بالجملة.

DDL المقابل:

CREATE SEQUENCE order_id_seq START WITH 1 INCREMENT BY 50;

AUTO — المزوّد يختار الاستراتيجية

GenerationType.AUTO هو الافتراضي عند حذف سمة strategy. يفحص Hibernate اللهجة ويختار ما يراه مناسبًا لقاعدة البيانات المستهدفة. على PostgreSQL يختار SEQUENCE؛ وعلى MySQL القديم يعود إلى تسلسل مشترك قائم على جدول TABLE.

@Id @GeneratedValue // مثل strategy = GenerationType.AUTO private Long id;
AUTO هدف متحرّك. تغيّرت الاستراتيجية التي يختارها Hibernate لـ AUTO بين Hibernate 5 و Hibernate 6، ويمكن أن تتغيّر مجددًا في الإصدارات الرئيسية المستقبلية. في كود الإنتاج، حدّد الاستراتيجية دائمًا بصراحة حتى لا يتمكّن ترقية Hibernate من تغيير سلوك توليد المفتاح بصمت.

TABLE — بديل للتنقلية

GenerationType.TABLE يُحاكي تسلسلًا باستخدام جدول قاعدة بيانات عادي. يعمل على كل قاعدة بيانات بما في ذلك MySQL، لكنه يتطلب قفلًا تشاؤميًا على مستوى الصف عند كل إدراج — SELECT … FOR UPDATE إضافية بالإضافة إلى UPDATE واحدة لكل طلب مفتاح. إنها الاستراتيجية الأبطأ ونادرًا ما تكون الاختيار الصحيح للمشاريع الجديدة.

@Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "order_table_gen") @TableGenerator(name = "order_table_gen", table = "id_generator", pkColumnName = "gen_name", valueColumnName = "gen_val", pkColumnValue = "order_id", allocationSize = 50) private Long id;
متى لا تزال TABLE مفيدة: مجموعات الاختبار متعددة قواعد البيانات التي يجب أن تعمل بصورة متطابقة على MySQL (بدون تسلسلات) وPostgreSQL، أو المخططات القديمة حيث لا يمكنك إضافة كائن تسلسل. خارج هذه الحالات الاستثنائية، فضّل SEQUENCE أو IDENTITY.

استخدام UUID كمفتاح أساسي

يضيف Hibernate 6 وJPA 3.1 دعمًا أصليًا للـ UUID. صرّح عن الحقل كـ java.util.UUID واستخدم GenerationType.UUID (أو @UuidGenerator الخاص بـ Hibernate). يُولّد مزوّد JPA UUID قبل INSERT، مانحًا إياك نفس حرية التجميع كـ SEQUENCE.

import jakarta.persistence.*; import java.util.UUID; @Entity public class Product { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; private String name; }

تكون UUIDs مفيدة عندما يجب إنشاء كيانات عبر شظايا قاعدة بيانات متعددة أو نماذج خدمة بدون تسلسل مركزي. التكلفة هي صفحات فهرسة أكبر (16 بايت مقابل 8 بايت لـ BIGINT) وفهارس B-tree مُجزَّأة للغاية مع UUIDs العشوائية. بالنسبة للجداول كثيفة الكتابة على قواعد البيانات العلائقية، لا يزال BIGINT + SEQUENCE أسرع.

دليل اختيار الاستراتيجية

  • PostgreSQL / Oracle / SQL Server (الحديث): SEQUENCE مع allocationSize مُضبوط. أفضل أداء، يدعم التجميع.
  • MySQL / MariaDB: IDENTITY هو الخيار العملي. اقبل أن إدراجات الدُفعات مُعطَّلة، أو استخدم SEQUENCE فقط إذا كنت على MySQL 8+ ومستعدًا لإنشاء كائنات التسلسل يدويًا (متاحة لكنها ليست الافتراضي في DDL).
  • الأنظمة الموزعة / متعددة الشظايا: UUID. يُلغي الحاجة للتنسيق على حساب حجم الفهرس.
  • الكود القديم المتنقّل: TABLE كملاذ أخير.
  • مشاريع Spring Boot الجديدة (PostgreSQL): SEQUENCE + allocationSize = 50 هو الافتراضي الاصطلاحي.

المفاتيح المركّبة

يدعم JPA أيضًا المفاتيح الأساسية المركّبة عبر @IdClass أو @EmbeddedId. يُغطَّى هذا بالتفصيل في درس لاحق حول الكائنات المُدمجة (Embeddables). تجنّب المفاتيح المركّبة في الجداول الجديدة — المفاتيح البديلة الصحيحة أو UUID أبسط في التعامل وأفضل أداءً في عمليات الانضمام.

الخلاصة

يُعلّم @Id حقل المفتاح الأساسي؛ بينما يُفوّض @GeneratedValue تعيين المفتاح إلى المزوّد. IDENTITY هي الاستراتيجية الأبسط لكنها تحجب إدراجات الدُفعات. SEQUENCE هي الأكثر مرونة وأداءً — اضبط allocationSize لتقليل رحلات قاعدة البيانات. AUTO مريحة في العروض التجريبية لكنها غير متوقعة عبر إصدارات Hibernate؛ كن دائمًا صريحًا في الإنتاج. UUID تحل توليد المعرّف الموزع بتكلفة كفاءة الفهرسة. اختر بناءً على قاعدة بياناتك ونمط الكتابة، ثم تابع — بقية تعيين الكيان لا تهتم بالاستراتيجية التي اخترتها.