أعمدة محددة زمن التشغيل مع asentinel-orm

asentinel-orm هي أداة ORM خفيفة الوزن مبنية على رأس Spring JDBC، وخاصة JdbcTemplate. وبالتالي، فهي تمتلك معظم الميزات التي يتوقعها المرء من ORM بسيط، مثل توليد SQL، التحميل الكسول، إلخ.

من خلال الاستفادة من JdbcTemplate، فهذا يعني أنها تسمح بالمشاركة في المعاملات المدارة من قبل Spring، ويمكن دمجها بسهولة في أي مشروع يستخدم بالفعل JdbcTemplate كوسيلة للتفاعل مع قاعدة البيانات.

منذ عام 2015، تم استخدام asentinel-orm بنجاح في عدة تطبيقات وتم تحسينها باستمرار حسب احتياجات الأعمال. في صيف عام 2024، أصبحت رسميًا مشروع مفتوح المصدر، وهو ما نعتبره سيعجل من تطورها ويزيد من عدد المساهمين.

في هذه المقالة، يتم بناء تطبيق نموذجي لتوضيح عدة ميزات رئيسية لـ ORM:

  • تكوين بسيط
  • نموذج كيان نطاق مباشر عبر تعليقات مخصصة
  • كتابة سهلة وتنفيذ آمن لعبارات SQL العادية
  • توليد تلقائي لعبارات SQL
  • نموذج ديناميكي (تُغنى الكيانات بخصائص إضافية في وقت التشغيل، وتُحفظ، وتُقرأ دون تغييرات في الكود)

تطبيق

إعداد

  • Java 21
  • Spring Boot 3.4.0
  • asentinel-orm 1.70.0
  • قاعدة بيانات H2

تكوين

لتفاعل مع asentinel-orm واستغلال وظائفه، يلزم وجود مثيل من OrmOperations .

كما هو مذكور في JavaDoc، فإن هذا هو الواجهة المركزية لأداء عمليات ORM، وليس من المقصود أو المطلوب أن يتم تنفيذه بشكل محدد في كود العميل.

يتضمن التطبيق النموذجي كود التكوين لإنشاء كائن من هذا النوع.

Java

 

OrmOperations لديه واجهتان رئيسيتان:

  • SqlBuilderFactory – تقوم بإنشاء مثيلات SqlBuilder التي يمكن استخدامها لاحقًا لإنشاء استعلامات SQL. يتمكن SqlBuilder من توليد أجزاء من الاستعلام تلقائيًا، على سبيل المثال، الجزء الذي يحدد الأعمدة. يمكن إضافة عبارة where، وعبارة order by، وشروط أخرى، والأعمدة الفعلية باستخدام طرق من فئة SqlBuilder أيضًا. في الجزء التالي من هذه القسم، يتم عرض مثال على استعلام تم إنشاؤه بواسطة SqlBuilder.
  • Updater – تُستخدم لحفظ الكيانات في جداول قاعدة البيانات الخاصة بها. يمكن أن تقوم بإدراج أو تحديث اعتمادًا على ما إذا كانت الكيان تم إنشاؤه حديثًا أو كان موجودًا بالفعل. توجد واجهة استراتيجية تسمى NewEntityDetector تستخدم لتحديد ما إذا كانت الكيان جديدة. بشكل افتراضي، يتم استخدام SimpleNewEntityDetector .

تُنفذ جميع الاستعلامات التي يتم إنشاؤها بواسطة ORM باستخدام SqlQueryTemplate مثيل، الذي يحتاج أيضًا إلى JdbcOperations/JdbcTemplate لتعمل. في النهاية، تصل جميع الاستعلامات إلى JdbcTemplate القديم الجيد من خلاله يتم تنفيذها أثناء المشاركة في معاملات Spring، تمامًا كما يحدث في أي JdbcTemplate تنفيذ مباشر.

توفر التراكيب المنطقية وSQL المحددة لقاعدة البيانات من خلال تطبيقات واجهة JdbcFlavor، التي تُحقن بعد ذلك في معظم الحبوب المذكورة أعلاه. في هذه المقالة، وبما أن قاعدة بيانات H2 مستخدمة، يتم تكوين تطبيق H2JdbcFlavor.

التكوين الكامل لـ ORM كجزء من تطبيق العينة هو OrmConfig.

التنفيذ

النموذج التجريبي للنطاق الذي تعرضه تطبيق العينة هو بسيط ويتكون من كيانين – مصنعي السيارات و نماذج السيارات. يمثل ما تدل عليه أسماؤهم بالضبط، العلاقة بينهما واضحة: قد يمتلك مصنع سيارات واحد عدة نماذج سيارات.

بالإضافة إلى اسمه، يتم إثراء مصنع السيارات بسمات (أعمدة) يتم إدخالها من قبل مستخدم التطبيق ديناميكيًا في وقت التشغيل. الحالة التي تم توضيحها هي مباشرة:

  • يُطلب من المستخدم تقديم الأسماء والأنواع المستهدفة للسمات الديناميكية
  • تم إنشاء زوج من مصنعي السيارات بقيم ملموسة للسمات الديناميكية المضافة مسبقًا، ثم
  • يتم تحميل الكيانات الموصوفة بواسطة السمات الأولية والمحددة في وقت التشغيل

يتم رسم الكيانات الأولية باستخدام جداول قاعدة البيانات أدناه:

SQL

 

تتم تزيين الفئات المجالية المقابلة بتعليقات خاصة بـ ORM لتكوين التعيينات إلى الجداول قاعدة البيانات أعلاه. 

Java

 

Java

 

بعض الاعتبارات:

  • @Table – يعين (يقارن) الفئة بجدول قاعدة بيانات
  • @PkColumn – يعين الـ id (المعرف الفريد) إلى مفتاح الجدول الأساسي
  • @Column – يعين عضو فئة إلى عمود جدول 
  • @Child – يعرف العلاقة مع كيان آخر
  • أعضاء محددة بـ @Child – مُكونة ليتم تحميلها بكسل
  • عمود جدول type – مُعين إلى حقل enumCarType

لكي تدعم فئة CarManufacturer السمات المحددة في وقت التشغيل (معينة إلى أعمدة الجدول المحددة في وقت التشغيل)، يتم تحديد فئة فرعية كما هو مبين أدناه:

Java

 

تُخزن هذه الفئة السمات (الحقول) المحددة في وقت التشغيل في Map. يتم تحقيق التفاعل بين قيم الحقول في وقت التشغيل و ORM من خلال تنفيذ واجهة DynamicColumnEntity .

Java

 

  • setValue() – يُستخدم لتعيين قيمة العمود المحدد في وقت التشغيل عند قراءته من الجدول
  • getValue() – يُستخدم لاسترجاع قيمة العمود المحدد في وقت التشغيل عند حفظه في الجدول

يُعالج DynamicColumn السمات المحددة في وقت التشغيل إلى أعمدتها المقابلة بطريقة مشابهة لتعيين @Column الذي يعالج الأعضاء المعروفة في وقت الترجمة.

عند تشغيل التطبيق، يتم تنفيذ CfRunner. يُطلب من المستخدم إدخال أسماء وأنواع للسمات الديناميكية المخصصة المرغوبة التي تُثري كيان CarManufacturer  (لتبسيط الأمر، يتم دعم أنواع int و varchar ). 

لكل زوج من الاسم والنوع، يتم تنفيذ أمر DML حتى يمكن إضافة الأعمدة الجديدة إلى جدول قاعدة بيانات CarManufacturer . الطريقة التالية (المعرفة في CarService) تنفذ العملية.

Java

 

يتم تسجيل كل سمة مدخلة كـ DefaultDynamicColumn، وهو تنفيذ مرجعي لـ DynamicColumn .

بمجرد تعريف جميع السمات، تتم إضافة مصنعين للسيارات إلى قاعدة البيانات، حيث يوفر المستخدم قيمًا لكل سمة من هذه السمات.

Java

 

الطريقة أدناه (المعرفة في CarService) تقوم فعليًا بإنشاء الكيان عبر ORM.

Java

 

يتم استدعاء النسخة ذات المعاملين من OrmOperations update()، والتي تسمح بتمرير مثيل UpdateSettings والتواصل مع ORM عند التنفيذ بأن هناك قيم محددة في وقت التشغيل يجب الاحتفاظ بها.

أخيرًا، يتم إنشاء نموذجين للسيارات، يتوافقان مع أحد الشركات المصنعة للسيارات التي تمت إضافتها مسبقًا.

Java

 

تقوم الطريقة أدناه (المعرفة في CarService) بإنشاء الكيانات عبر ORM، هذه المرة باستخدام طريقة OrmOperations update() للاحتفاظ بالكيانات بدون سمات ديناميكية. لسهولة الاستخدام، يتم إنشاء عدة كيانات في مكالمة واحدة.

Java

 

كخطوة أخيرة، يتم تحميل أحد الشركات المصنعة التي تم إنشاؤها مرة أخرى بواسطة اسمها باستخدام استعلام تم إنشاؤه بواسطة ORM.

Java

 

تستحق بعض الشروحات المتعلقة بالطريقة المعرفة أعلاه القيام بها.

تقوم طريقة OrmOperations newSqlBuilder() بإنشاء مثيل SqlBuilder، وكما يشير الاسم، يمكن استخدام هذا لإنشاء استعلامات SQL. تقوم طريقة SqlBuilder select() بإنشاء جزء select from table من الاستعلام، في حين يجب إضافة الباقي (where, order by). يمكن تخصيص جزء الاستعلام select عن طريق تمرير مثيلات EntityDescriptorNodeCallback (قد تكون التفاصيل المتعلقة بـ EntityDescriptorNodeCallback موضوع مقال مستقبلي).

لكي نعلم الـ ORM أن الخطة هي اختيار وتعيين الأعمدة المعرفة أثناء وقت التشغيل، يجب تمرير DynamicColumnsEntityNodeCallback . مع ذلك، يتم توفير AutoEagerLoader حتى يفهم الـ ORM أنه يجب تحميل قائمة CarModels المتعلقة بالشركة المصنعة بشكل متعجل. ومع ذلك، لا علاقة لهذا بالسمات المعرفة أثناء وقت التشغيل، ولكنه يظهر كيف يمكن تحميل عضو فرعي بشكل متعجل.

الخاتمة

بينما قد تكون هناك طرق أخرى للعمل مع الأعمدة المعرفة أثناء وقت التشغيل عندما يتم تخزين البيانات في قواعد البيانات العلائقية، فإن النهج المقدم في هذه المقالة له ميزة استخدام أعمدة قاعدة البيانات القياسية التي تتم قراءتها/كتابتها باستخدام استعلامات SQL القياسية التي يتم إنتاجها مباشرة بواسطة الـ ORM.

لم يكن من النادر عندما أتيحت لنا الفرصة لمناقشة في “المجتمع” asentinel-orm، الأسباب التي دفعتنا لتطوير مثل هذه الأداة. عادة، من النظرة الأولى، أثبت المطورون أنهم مترددون ومحتاطون عندما يتعلق الأمر بـ ORM مصنوعة حسب الطلب، متسائلين لماذا لا نستخدم Hibernate أو غيرها من تطبيقات JPA.

في حالتنا، كان الدافع الرئيسي هو الحاجة إلى طريقة سريعة ومرنة وسهلة للعمل مع عدد كبير أحياناً من السمات المعرفة أثناء وقت التشغيل (الأعمدة) للكيانات التي هي جزء من مجال العمل. بالنسبة لنا، أثبتت أنها الطريقة الصحيحة. التطبيقات تعمل بسلاسة في الإنتاج، العملاء راضون عن السرعة والأداء المحقق، والمطورون مرتاحون ومبدعون مع واجهة برمجة التطبيقات البديهية.

مع فتح مشروع المصدر الحالي، من السهل جدًا على أي شخص مهتم أن يلقي نظرة عليه، ويشكل رأيًا موضوعيًا حوله، و، لما لا، ينشئ نسخة منه، ويُقدم طلب سحب، ويساهم.

الموارد

  • مشروع ORM مفتوح المصدر متوفر هنا.
  • ويمكن العثور على شفرة المصدر لتطبيق العينة هنا.

Source:
https://dzone.com/articles/runtime-defined-columns-with-asentinel-orm