Kotlin مع JPA: كيفية التغلب على مشكلة default constructor

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

تعرف على: "جحيم المُنشئ الفارغ" في Kotlin + JPA

\n\n

إذا سبق لك أن حاولت التوفيق بين Kotlin وJPA (Java Persistence API)، فمن المحتمل أنك واجهت موقفًا يرمي فيه Hibernate استثناءً مثل:

\n\n
org.hibernate.InstantiationException: No default constructor for entity: com.example.User\n
\n\n

مألوف؟ بالتأكيد. هذه هي المشكلة التي جمعت آلاف المشاهدات على Stack Overflow وأُطلق عليها الاسم الكئيب "Kotlin with JPA: default constructor hell".

\n\n

في هذه المقالة، سنشرح بهدوء، خطوة بخطوة:

\n
    \n
  • لماذا يحدث هذا الخطأ أساسًا
  • لماذا Kotlin "لا يريد" إنشاء مُنشئ فارغ
  • ثلاث طرق مجربة لحل المشكلة (من البسيط إلى الاحترافي)
  • أي طريقة تختار لمشروعك
\n\n

هيا بنا!

\n\n

لماذا يتطلب Hibernate مُنشئًا بدون معاملات؟

\n\n

لنبدأ مع Java. في Java التقليدية، اعتدنا على كتابة:

\n\n
public class User {\n    private Long id;\n    private String name;\n\n    // مُنشئ فارغ إلزامي\n    public User() {}\n\n    // دوال الوصول (Getters) والتعديل (Setters)...\n}\n
\n\n

يستخدم Hibernate (وأي تطبيق JPA) الانعكاس (reflection) لإنشاء الكائنات. عندما يقرأ سطرًا من قاعدة البيانات، يحتاج إلى إنشاء مثيل للكلاس User، ثم ملء الحقول عبر الانعكاس. للقيام بذلك، يحتاج إلى مُنشئ بدون معاملات (default constructor).

\n\n

في Java، يقوم المترجم تلقائيًا بإنشاء مُنشئ فارغ إذا لم تحدد أي مُنشئ آخر. لذلك، لا تظهر المشكلة عادةً.

\n\n

وما هي خصوصية Kotlin؟

\n\n

Kotlin هي لغة تركز بشدة على الثبات (immutability) والأمان. انظر إلى كلاس Kotlin النموذجي:

\n\n
data class User(\n    val id: Long = 0,\n    val name: String = ""\n)\n
\n\n

هنا يحدث أمران رئيسيان:

\n
    \n
  • جميع الخصائص مُعلنة في المُنشئ الأساسي (primary constructor)
  • الخصائص مُعلنة كـ val (غير قابلة للتغيير) ولها قيم افتراضية
\n\n

Kotlin لا يُنشئ مُنشئًا فارغًا منفصلاً إذا كان لديك مُنشئ أساسي بمعاملات. حتى لو تم تعيين قيم افتراضية لجميع المعاملات - فهذا لا يُعتبر "مُنشئًا حقيقيًا" بدون معاملات على مستوى bytecode لـ JVM.

\n\n

عندما يحاول Hibernate استدعاء User.class.newInstance() عبر الانعكاس، فإنه لا يجد المُنشئ الفارغ - ويتعطل مع خطأ.

\n\n

الطريقة 1: الأسهل - إضافة مُنشئ ثانوي بشكل صريح

\n\n

أبسط طريق هو إضافة مُنشئ ثانٍ فارغ يدويًا:

\n\n
data class User(\n    val id: Long = 0,\n    val name: String = ""\n) {\n    // مُنشئ ثانوي بدون معاملات\n    constructor() : this()\n}\n
\n\n

هنا constructor() : this() يعني: "استدعِ المُنشئ الأساسي بالقيم الافتراضية".

\n\n

الإيجابيات:

\n
    \n
  • بسيط جدًا وواضح
  • لا يتطلب أي إضافات (plugins) أو إعدادات إضافية
\n\n

السلبيات:

\n
    \n
  • تحتاج إلى كتابة هذا المُنشئ لكل كيان
  • إذا كان لديك 10-15 كيانًا - يصبح الأمر مملًا
  • من السهل نسيان إضافته
\n\n

الطريقة 2: استخدام open class وجعل جميع الخصائص var

\n\n

تقترح العديد من الأمثلة على الإنترنت هذا الحل:

\n\n
open class User {\n    var id: Long = 0\n    var name: String = ""\n}\n
\n\n

هنا نجعل الكلاس open (ليتمكن Hibernate من إنشاء proxy)، والخصائص var (قابلة للتغيير). وبدون مُنشئ أساسي - وبالتالي، سينشئ Kotlin مُنشئًا فارغًا افتراضيًا.

\n\n

الإيجابيات:

\n
    \n

المدونات

توصيات الكتب