المعاملات (Transactions)
المعاملات (Transactions)
المعاملة (Transaction) هي وحدة عمل يجب أن تنجح بالكامل أو تُترك قاعدة البيانات دون أي تغيير. تمنحك JDBC تحكّمًا دقيقًا في المعاملات من خلال ثلاثة مفاهيم مترابطة: علامة autoCommit، واستدعاءات commit() وrollback() الصريحة، ومستويات العزل التي تحدّد ما تستطيع المعاملات المتزامنة رؤيته من عمل بعضها البعض.
لماذا تهمّ المعاملات؟
تخيّل تحويل أموال: تخصم من حساب A وتُضيف إلى حساب B. إذا تعطّل التطبيق بين العبارتين، تحتاج قاعدة البيانات إلى التراجع عن عملية الخصم تلقائيًا. توفّر المعاملات ضمانات ACID الأربع التي تجعل ذلك آمنًا:
- الذرية (Atomicity) — تُنفَّذ كل العبارات معًا أو لا تُنفَّذ أي منها.
- الاتساق (Consistency) — تنتقل قاعدة البيانات من حالة صالحة إلى أخرى.
- العزل (Isolation) — لا تتداخل المعاملات المتزامنة مع بعضها (بدرجة قابلة للتهيئة).
- الديمومة (Durability) — بعد الإيداع (commit)، تبقى البيانات محفوظة حتى في حالة الأعطال.
autoCommit: السلوك الافتراضي
تضبط JDBC افتراضيًا autoCommit = true على كل اتصال جديد. كل عبارة SQL تُنفَّذ تُودَع فورًا في قاعدة البيانات — دون أي فرصة للتراجع عنها.
لكي تتحكّم في المعاملات بنفسك، يجب تعطيل هذا الإعداد قبل إصدار أي عبارة DML:
conn.executeUpdate(...) مع تفعيل autoCommit، يصبح التغيير دائمًا لا رجعة فيه. لا يوجد شيء للتراجع عنه. اضبط دائمًا autoCommit(false) في بداية أي عملية متعددة العبارات تريدها ذرية.
commit() و rollback()
بمجرد إيقاف autoCommit، تُخبر قاعدة البيانات بجعل كل التغييرات المعلّقة دائمة باستخدام conn.commit()، أو بتجاهلها باستخدام conn.rollback().
النمط الاصطلاحي يُغلّف العملية في كتلة try/catch/finally:
true — أو دع إعداد connectionInitSql في المجموعة يفعل ذلك نيابة عنك.
نقاط الحفظ (Savepoints)
تتيح نقطة الحفظ التراجع إلى نقطة محددة داخل المعاملة دون التخلّي عن كل شيء. هذا مفيد حين تمرّ عملية كبيرة بعدة مراحل وتفشل المرحلة الأخيرة فقط.
DatabaseMetaData.supportsSavepoints() عند بدء التشغيل إذا كانت قابلية النقل مهمة.
مستويات العزل (Isolation Levels)
حين تعمل معاملات متعددة في وقت واحد يمكن أن تتداخل. يحدّد معيار SQL أربعة شذوذات تزامن وأربعة مستويات عزل تمنعها تدريجيًا:
- القراءة القذرة (Dirty Read) — قراءة بيانات كتبتها معاملة غير مُودَعة قد تتراجع لاحقًا.
- القراءة غير القابلة للتكرار (Non-Repeatable Read) — صف قرأته مرة يحمل قيمًا مختلفة عند قراءته مجددًا (معاملة أخرى أودعت تحديثًا في الأثناء).
- القراءة الشبحية (Phantom Read) — استعلام نطاق يُعيد صفوفًا مختلفة عند إعادة تشغيله (معاملة أخرى أدرجت صفوفًا أو حذفتها).
- التحديث الضائع (Lost Update) — معاملتان متزامنتان تقرآن ثم تكتبان على نفس الصف؛ تُضيَّع إحدى الكتابتين بصمت.
تعرض JDBC أربعة مستويات عزل كثوابت على Connection:
TRANSACTION_READ_UNCOMMITTED— يسمح بالقراءة القذرة. نادر الاستخدام؛ أعلى إنتاجية.TRANSACTION_READ_COMMITTED— يمنع القراءة القذرة. الافتراضي في معظم قواعد البيانات (PostgreSQL، SQL Server، Oracle). القراءة غير القابلة للتكرار والأشباح لا تزال ممكنة.TRANSACTION_REPEATABLE_READ— يمنع القراءة القذرة والقراءة غير القابلة للتكرار. افتراضي MySQL InnoDB. الأشباح لا تزال ممكنة في بعض المحركات.TRANSACTION_SERIALIZABLE— يمنع جميع الشذوذات. تتصرّف القراءات كما لو أنّ المعاملات نُفِّذت واحدة تلو الأخرى. أعلى صواب، أدنى إنتاجية.
ضبط مستوى العزل قبل بدء العمل:
SERIALIZABLE تنافسًا شديدًا على الأقفال وحالات توقّف (deadlocks) في ظل التزامن العالي. اجعل READ_COMMITTED افتراضك وارفع المستوى فقط للعمليات التي يُسبّب فيها الشذوذ خللًا فعليًا (إعادة الحسابات المالية، فحوصات المخزون إلخ).
اختيار المستوى المناسب عمليًا
قاعدة بديهية واقعية:
- READ_COMMITTED — معظم عمليات CRUD، استعلامات التقارير، معالجات واجهة برمجة الويب.
- REPEATABLE_READ — العمليات التي تقرأ قيمة ثم تستخدمها في كتابة (دورات القراءة-التعديل-الكتابة، مثل زيادة عداد).
- SERIALIZABLE — التحويلات المالية، أنظمة الحجز حيث يجب أن يكون التخصيص المزدوج مستحيلًا؛ استخدمها فقط في المعاملة المحددة التي تحتاجها، لا عالميًا.
الخلاصة
تتمحور المعاملات في JDBC حول ثلاثة عناصر تحكّم: setAutoCommit(false) للتحكّم اليدوي، وcommit() / rollback() لإنهاء العمل أو التراجع عنه، وsetTransactionIsolation() للإعلان عن مدى رؤية المعاملات المتزامنة لعمل بعضها البعض. تضيف نقاط الحفظ نقاط تفتيش داخل المعاملة للسير المعقّدة. المهارة الهندسية الأساسية هي مطابقة مستوى العزل مع الشذوذ الذي تحتاج فعلًا إلى منعه، بدلًا من الاعتماد افتراضيًا على المستوى الأكثر تقييدًا في كل مكان.