التدقيق التلقائي مع @CreatedDate
التدقيق التلقائي مع @CreatedDate
تستفيد كل جدول قاعدة بيانات في الإنتاج من معرفة متى أُنشئ الصف ومتى جرى تعديله آخر مرة، وكثيرًا ما تحتاج أيضًا إلى معرفة من أجرى تلك التعديلات. ضبط تلك الأعمدة يدويًا في كل دالة خدمة أمر ممل ومعرَّض للأخطاء. تُزيح ميزة التدقيق (Auditing) في Spring Data JPA هذه المسؤولية عن كود التطبيق كليًا وتنقلها إلى البنية التحتية للإطار — فتُضبط الطوابع الزمنية بشكل متسق وتلقائي دون تلويث منطق الأعمال.
كيف يعمل التدقيق في Spring Data
يُبنى التدقيق في Spring Data فوق دورة حياة JPA (@PrePersist، @PreUpdate). عند تفعيل التدقيق، يسجّل Spring مُستمعًا باسم AuditingEntityListener يعترض هذه الاستدعاءات ويملأ الحقول التي وسمتها بـ @CreatedDate أو @LastModifiedDate أو @CreatedBy أو @LastModifiedBy. النتيجة النهائية أن تلك الحقول الأربعة تُملأ تلقائيًا قبل كل عملية INSERT أو UPDATE دون سطر واحد في طبقة الخدمة.
الخطوة الأولى: تفعيل التدقيق في JPA
أضف @EnableJpaAuditing إلى فئة التطبيق الرئيسية أو إلى أي فئة @Configuration:
يُخبر هذا التوصيف الواحد Spring بتنشيط AuditingEntityListener بشكل عام. بدونه لا يكون لأي توصيف تدقيق على الكيانات أي أثر.
الخطوة الثانية: توصيف حقول الكيان
طبّق توصيفات التدقيق على الحقول التي تريد أن تُملأ تلقائيًا. يجب أيضًا تسجيل AuditingEntityListener على الكيان، إما مباشرةً عبر @EntityListeners، أو — الأكثر ملاءمةً — على فئة أساسية مشتركة من نوع @MappedSuperclass:
تمتدّ الكيانات الفعلية من هذه الفئة الأساسية:
Instant بدلًا من LocalDateTime؟ Instant نقطة على محور الزمن بتوقيت UTC دون أي غموض في المنطقة الزمنية. إذا عمل تطبيقك في مناطق زمنية متعددة أو عبر مناطق سحابية مختلفة، فإن Instant يضمن أن الطوابع الزمنية قابلة للمقارنة دائمًا. استخدم LocalDateTime فقط إذا أردت عن قصد تخزين التوقيت المحلي للساعة في المنطقة الزمنية الحالية لـ JVM.
القيد updatable = false
السمة updatable = false على createdAt بالغة الأهمية. تُخبر Hibernate بتضمين هذا العمود في جملة INSERT لكن استبعاده من كل جملة UPDATE لاحقة. بدونها، قد تُعيد استدعاء خاطئ لـ save() على كيان موجود كتابة طابع وقت الإنشاء الأصلي — خطأ سلامة بيانات يصعب اكتشافه في الاختبارات.
الخطوة الثالثة: تدقيق المؤلف — @CreatedBy و@LastModifiedBy
لالتقاط من نفّذ الإجراء، نفّذ واجهة AuditorAware واعرضها كـ bean. يستدعي Spring Data الدالة getCurrentAuditor() قبيل كل persist أو update:
أخبر Spring Data بأي bean يستخدم عبر تمرير اسمه إلى @EnableJpaAuditing:
ثم أضف حقول المؤلف إلى Auditable:
اختبار سلوك التدقيق
مشكلة شائعة: لا تُحمّل شريحة @DataJpaTest الفئة @SpringBootApplication، لذا يكون @EnableJpaAuditing غائبًا ويعمل التدقيق بصمت. الحل هو تضمين فئة إعداد صغيرة في الشريحة الاختبارية:
createdAt تساوي قيمة بعينها تمامًا، أدخل bean من نوع DateTimeProvider يُعيد Instant ثابتة ومرّره إلى @EnableJpaAuditing(dateTimeProviderRef = "fixedClock"). هذا يجعل الاختبارات الحساسة للوقت محددة النتائج.
اعتبارات الأداء
يعتمد التدقيق في Spring Data على دورة حياة JPA، مما يعني أن Hibernate يجب أن يحمّل الكيان في ذاكرة التخزين المؤقت للمستوى الأول قبل أن يتمكن من استدعاء @PreUpdate. بالنسبة للتحديثات الجماعية التي تُنفَّذ عبر JPQL (باستخدام @Modifying @Query)، لا تُستدعى استدعاءات دورة الحياة، لذا لن يُحدَّث updatedAt تلقائيًا. إذا كانت دقة التدقيق مطلوبة في مسارات الجملة الجماعية، فإما أن تحدّث الطابع الزمني يدويًا في جملة JPQL أو تتجنب التحديثات الجماعية على الكيانات المدققة.
UPDATE Order o SET o.status = 'ARCHIVED' WHERE o.createdAt < :cutoff ستتجاوز AuditingEntityListener بصمت. إما أدرج SET o.updatedAt = :now في الاستعلام، أو انتقل إلى نهج الحفظ الدُفعي إذا كانت سجلات التدقيق إلزامية.
الخلاصة
يُزيل التدقيق في Spring Data JPA إدارة الطوابع الزمنية الاعتيادية من طبقة الخدمة. فعّله بـ @EnableJpaAuditing، وسجّل AuditingEntityListener على @MappedSuperclass، ووسّم الحقول بـ @CreatedDate و@LastModifiedDate. استخدم Instant للسلامة من فوارق التوقيت وupdatable = false لحماية طوابع الإنشاء. لتتبّع المؤلف، نفّذ AuditorAware. تذكّر أن التحديثات الجماعية عبر JPQL تتجاوز المُستمع وتحتاج معالجة updatedAt بشكل صريح.