
المشاهدات ، أو المشاهدات ، هي أحد مفاهيم منصة CUBA ، وليست الأكثر شيوعًا في عالم أطر الويب. لفهم ذلك يعني إنقاذ نفسك من الأخطاء الغبية عندما يتوقف التطبيق فجأة عن العمل بسبب عدم اكتمال تحميل البيانات. دعونا نرى ما هي التوضيحات (المقصود بالتورية) ولماذا هي في الواقع مريحة.
مشكلة البيانات غير المحملة
لنأخذ موضوعًا أكثر بساطة وننظر في المشكلة باستخدام مثالها. لنفترض أن لدينا كيان عميل يشير إلى كيان CustomerType في علاقة بين شخصين ، وبعبارة أخرى ، فإن المشتري لديه رابط لنوع معين يصفه: على سبيل المثال ، "بقرة نقدية" ، "snapper" ، إلخ. يحتوي كيان CustomerType على سمة اسم يتم تخزين اسم النوع فيها.
وربما ، جميع الوافدين الجدد (أو حتى المستخدمين المتقدمين) في CUBA تلقوا هذا الخطأ عاجلاً أم آجلاً:
IllegalStateException: Cannot get unfetched attribute [type] from detached object com.rtcab.cev.entity.Customer-e703700d-c977-bd8e-1a40-74afd88915af [detached].

أعترف بذلك ، رأيته أيضا بأم عينيك؟ أنا - نعم ، في مائة موقف مختلف. في هذه المقالة ، سندرس سبب هذه المشكلة ، وسبب وجودها على الإطلاق ، وكيفية حلها.
بالنسبة للمبتدئين ، مقدمة صغيرة لمفهوم الآراء.
ما هي وجهة النظر؟
العرض في CUBA هو في الأساس مجموعة من الأعمدة في قاعدة بيانات يجب تحميلها معًا في استعلام واحد.
افترض أننا نريد إنشاء واجهة مستخدم مع جدول العملاء ، حيث يكون العمود الأول هو اسم العميل ، والثاني هو اسم النوع من سمة customerType (كما في لقطة الشاشة أعلاه). من المنطقي أن نفترض أنه في نموذج البيانات هذا سيكون لدينا جدولين منفصلين في قاعدة البيانات ، أحدهما لكيان العميل ، والآخر لنوع العميل . SELECT * from CEV_CUSTOMER
لنا SELECT * from CEV_CUSTOMER
البيانات من جدول واحد فقط ( name
السمة ، وما إلى ذلك). من الواضح ، للحصول على البيانات أيضًا من جداول أخرى ، سوف نستخدم JOINs.
في حالة استخدام استعلامات SQL الكلاسيكية باستخدام JOIN ، يتسع التسلسل الهرمي للارتباطات (السمات المرجعية) من الرسم البياني إلى قائمة مسطحة.
ملاحظة المترجم: بمعنى آخر ، تم محو العلاقات بين الجداول ، والنتيجة مقدمة في صفيف بيانات واحد يمثل اتحاد الجداول.
في حالة CUBA ، يتم استخدام ORM ، والذي لا يفقد معلومات حول العلاقات بين الكيانات ويعرض نتيجة الاستعلامات في شكل رسم بياني متكامل للبيانات المطلوبة. في هذه الحالة ، يتم استخدام JPQL ، وهو نظير كائن لـ SQL ، كلغة استعلام.
ومع ذلك ، لا يزال يتعين تفريغ البيانات بطريقة ما من قاعدة البيانات وتحويلها إلى رسم بياني للكيان. لهذا ، فإن آلية رسم الخرائط العلائقية للكائنات (التي هي JPA) لديها نهجان رئيسيان للاستعلامات إلى قاعدة البيانات.
كسول التحميل مقابل جلب حريصة
التحميل البطيء والتحميل الجشع هما استراتيجيتان محتملتان للحصول على البيانات من قاعدة البيانات. الفرق الأساسي بين الاثنين هو عندما يتم تحميل البيانات من الجداول المرتبطة. مثال صغير لفهم أفضل:
هل تتذكر المشهد من كتاب "The Hobbit ، أو Round Trip" ، حيث تحاول مجموعة من التماثيل برفقة Gandalf و Bilbo طلب قضاء ليلة في منزل Beorn؟ أمر غاندالف الأقزام بالظهور بشكل صارم بدورهم وفقط بعد أن اتفق بعناية مع Beorn وبدأ في تقديمهم واحدًا تلو الآخر حتى لا يصدم المالك بالحاجة إلى استيعاب 15 ضيفًا في وقت واحد.

لذا ، غاندالف والتماثيل في منزل Beorn ... ربما لا يكون هذا هو أول ما يتبادر إلى الذهن عند التفكير في التنزيلات البطيئة والجشعة ، ولكن هناك بالتأكيد أوجه تشابه. تصرف غاندالف بحكمة هنا ، لأنه كان على علم بالقيود. يمكن القول أنه يختار بوعي التحميل البطيء للتماثيل ، لأنه فهم أن تنزيل جميع البيانات في وقت واحد سيكون عملية ثقيلة جدًا لقاعدة البيانات هذه. ومع ذلك ، بعد القزم الثامن ، تحولت غاندالف إلى التحميل الجشع وحمّلت مجموعة من التماثيل المتبقية ، لأنه لاحظ أن الوصول المتكرر إلى قاعدة البيانات يبدأ في جعلها عصبية لا تقل.
المعنوي هو أن لكل من التحميل البطيء والجشع إيجابياته وسلبياته. ما يجب تطبيقه في كل حالة محددة ، تقرر.
مشكلة طلب N + 1
غالبًا ما تنشأ مشكلة استعلام N + 1 إذا كنت تستخدم بدون تفكير التحميل الكسول أينما ذهبت. للتوضيح ، دعنا نلقي نظرة على قطعة من كود Grails. هذا لا يعني أنه يتم تحميل كل شيء في Grails بتكاسل (في الواقع ، تختار طريقة التمهيد بنفسك). في Grails ، يقوم الاستعلام إلى قاعدة البيانات افتراضيًا بإرجاع مثيلات الكيان مع جميع السمات من جدولها. بشكل أساسي ، يتم تنفيذ SELECT * FROM Pet
.
إذا كنت ترغب في التعمق في العلاقات بين الكيانات ، فيجب عليك القيام بذلك بعد الولادة. هنا مثال:
function getPetOwnerNamesForPets(String nameOfPet) { def pets = Pet.findAll(sort:"name") { name == nameOfPet } def ownerNames = [] pets.each { ownerNames << it.owner.name } return ownerNames.join(", ") }
يتم it.owner.name
الرسم البياني هنا بخط واحد: it.owner.name
. المالك هو علاقة لم يتم تحميلها في الطلب الأصلي ( Pet.findAll
). وبالتالي ، في كل مرة يتم فيها استدعاء هذا الخط ، سيفعل GORM شيئًا مثل SELECT * FROM Person WHERE id='…'
. تحميل كسول الماء النقي.
إذا قمت بحساب العدد الإجمالي لاستعلامات SQL ، فستحصل على N (مالك واحد لكل مكالمة it.owner
) + 1 (لـ Pet.findAll
الأصلي). إذا كنت تريد التعمق في الرسم البياني للكيانات ذات الصلة ، فمن المحتمل أن تجد قاعدة بياناتك حدودها بسرعة.
بصفتك مطورًا ، من غير المحتمل أن تلاحظ ذلك ، لأنك من وجهة نظرك تتجول فقط في الرسم البياني للكائنات. هذا التعشيش الخفي في سطر واحد قصير يسبب ألمًا حقيقيًا لقاعدة البيانات ويجعل التحميل الكسول خطيرًا في بعض الأحيان.
عند تطوير تشابه للهواية ، يمكن أن تظهر مشكلة N + 1 على النحو التالي: تخيل أن Gandalf غير قادر على تخزين أسماء التماثيل في ذاكرته. لذلك ، عند تقديم الأقزام واحدًا تلو الآخر ، يضطر إلى التراجع إلى مجموعته ويسأل القزم عن اسمه. بهذه المعلومات ، يعود إلى بورن ويمثل ثورين. ثم يكرر هذه المناورة من أجل بيفور وبوفور وفيلي وكيلي ودوري ونوري وأوري وأوين وغلوين وبالين ودفالين وبومبور.

من السهل أن نتخيل أنه من غير المحتمل أن يولد مثل هذا السيناريو: ما الذي يريده المستلم في انتظار المعلومات المطلوبة لفترة طويلة؟ لذلك ، لا يجب عليك استخدام هذا النهج بدون تفكير والاعتماد بشكل أعمى على الإعدادات الافتراضية لرسم الخرائط المستمر.
حل مشكلة استعلامات N + 1 باستخدام طرق عرض CUBA
في CUBA ، من المحتمل ألا تواجه أبدًا مشكلة استعلام N + 1 ، حيث قررت المنصة عدم استخدام التحميل البطيء المخفي على الإطلاق. بدلاً من ذلك ، قدم CUBA مفهوم التمثيلات. طرق العرض هي وصف السمات التي يجب تحديدها وتحميلها مع مثيلات الكيان. شيء مثل
SELECT pet.name, person.name FROM Pet pet JOIN Person person ON pet.owner == person.id
من ناحية ، يصف العرض الأعمدة التي يجب تحميلها من الجدول الرئيسي ( Pet ) (بدلاً من تحميل جميع السمات من خلال *) ، من ناحية أخرى ، يصف الأعمدة التي يجب تحميلها من جداول c-JOIN.
يمكنك تخيل عرض CUBA كعرض SQL لـ OR-Mapper: مبدأ التشغيل هو نفسه تقريبًا.
في منصة CUBA ، لا يمكنك استدعاء استعلام من خلال DataManager دون استخدام طريقة العرض. تقدم الوثائق مثال:
@Inject private DataManager dataManager; private Book loadBookById(UUID bookId) { LoadContext<Book> loadContext = LoadContext.create(Book.class) .setId(bookId).setView("book.edit"); return dataManager.load(loadContext); }
هنا نريد تنزيل الكتاب بمعرفه. تشير طريقة setView("book.edit")
، عند إنشاء سياق تحميل ، إلى أي طريقة عرض يجب تحميل الكتاب من قاعدة البيانات. في حالة عدم تمرير أي ملف شخصي ، يستخدم مدير البيانات أحد طرق العرض القياسية الثلاثة التي يمتلكها كل كيان: طريقة العرض _local . يشير المحلي هنا إلى السمات التي لا تشير إلى جداول أخرى ، كل شيء بسيط.
حل المشكلة مع IllegalStateException من خلال طرق العرض
الآن بعد أن فهمنا قليلاً لمفهوم التمثيلات ، دعنا نعود إلى المثال الأول من بداية المقالة ونحاول منع الاستثناء.
الرسالة IllegalStateException: لا يمكن الحصول على سمة غير مجردة [] من كائن منفصل يعني فقط أنك تحاول عرض بعض السمات التي لم يتم تضمينها في طريقة العرض التي تم تحميل الكيان بها.
كما ترى ، في واصف شاشة التصفح ، استخدمت طريقة العرض _ المحلية ، وهذه هي المشكلة برمتها:
<dsContext> <groupDatasource id="customersDs" class="com.rtcab.cev.entity.Customer" view="_local"> <query> <![CDATA[select e from cev$Customer e]]> </query> </groupDatasource> </dsContext>
للتخلص من الخطأ ، تحتاج أولاً إلى تضمين نوع العميل في العرض. نظرًا لأنه لا يمكننا تغيير العرض الافتراضي لـ _local ، يمكننا إنشاء عرضنا الخاص. في Studio ، يمكن القيام بذلك ، على سبيل المثال ، على النحو التالي (انقر بزر الماوس الأيمن على الكيانات> إنشاء طريقة عرض):

إما مباشرة في واصف views.xml لتطبيقنا :
<view class="com.rtcab.cev.entity.Customer" extends="_local" name="customer-view"> <property name="type" view="_minimal"/> </view>
بعد ذلك ، نغير رابط العرض في شاشة التصفح ، مثل هذا:
<groupDatasource id="customersDs" class="com.rtcab.cev.entity.Customer" view="customer-view"> <query> <![CDATA[select e from cev$Customer e]]> </query> </groupDatasource>
هذا يحل المشكلة تمامًا ، والآن يتم عرض بيانات الارتباط في شاشة عرض العميل.
_Minimal view واسم المثيل
ما هو آخر جدير بالذكر في سياق وجهات النظر هو عرض _minimal . تحتوي طريقة العرض المحلية على تعريف واضح للغاية: فهي تتضمن جميع سمات الكيان ، وهي سمات مباشرة للجدول (وهي ليست مفاتيح خارجية).
تعريف الحد الأدنى للتمثيل ليس واضحًا جدًا ، ولكنه واضح أيضًا.
لدى CUBA مفهوم اسم مثيل الكيان - اسم المثيل. اسم المثيل مكافئ لطريقة toString()
في جافا القديمة الجيدة. هذا تمثيل سلسلة لكيان للعرض على واجهة المستخدم وللاستخدام في الارتباطات. يتم تعيين اسم المثيل باستخدام التعليق التوضيحي لكيان NamePattern .
يتم استخدامه مثل هذا: @NamePattern("%s (%s)|name,code")
. لدينا نتيجتان:
يعرّف اسم المثيل تعيين الكيان لواجهة المستخدم
بادئ ذي بدء ، يحدد اسم المثيل الترتيب الذي سيتم عرضه في واجهة المستخدم وبأي ترتيب إذا كان الكيان يشير إلى كيان آخر (كما يشير العميل إلى نوع العميل ).
في حالتنا ، سيتم عرض نوع العميل كاسم مثيل CustomerType ، والذي تتم إضافة الرمز إليه بين قوسين. إذا لم يتم تحديد اسم المثيل ، فسيتم عرض اسم فئة الكيان ومعرف المثيل المحدد - وافق على أن هذا ليس على الإطلاق ما يرغب المستخدم في رؤيته. راجع لقطات الشاشة السابقة واللاحقة أدناه للحصول على أمثلة لكلتا الحالتين.


يحدد اسم المثيل الحد الأدنى من سمات العرض
الشيء الثاني الذي يؤثر على التعليق التوضيحي لـ NamePattern هو: جميع السمات المحددة بعد أن يقوم الشريط العمودي تلقائيًا بتكوين عرض _minimal . للوهلة الأولى ، يبدو هذا واضحًا ، لأنه يجب عرض البيانات في شكل ما في واجهة المستخدم ، مما يعني أنه يجب عليك تنزيلها أولاً من قاعدة البيانات. على الرغم من أن نكون صادقين ، نادرا ما أفكر في هذه الحقيقة.
من المهم أن نلاحظ هنا أن الحد الأدنى للتمثيل ، إذا قورنت بالتمثيل المحلي ، قد يحتوي على إشارات إلى كيانات أخرى. على سبيل المثال ، بالنسبة للمشتري من المثال أعلاه ، قمت بتعيين اسم مثيل يتضمن سمة محلية واحدة لكيان العميل ( name
) وسمة مرجعية واحدة ( type
):
@NamePattern("%s - %s|name,type")
يمكن استخدام الحد الأدنى للتمثيل بشكل متكرر: (Customer [Instance Name] -> CustomerType [Instance Name])
ملاحظة المترجم: منذ نشر المقال ، ظهرت طريقة عرض نظام أخرى - طريقة عرض _base
، والتي تتضمن جميع السمات والسمات المحلية غير النظامية المحددة في التعليق التوضيحيNamePattern (أي في الواقع _minimal
+ _local
).
الخلاصة
في الختام ، نلخص أهم موضوع. بفضل الآراء ، يمكننا في CUBA الإشارة بوضوح إلى ما يجب تحميله من قاعدة البيانات. تحدد المشاهدات ما سيتم تحميله بجشع ، في حين أن معظم الأطر الأخرى تؤدي بشكل خفي التحميل البطيء.
قد تبدو التمثيلات كآلية مرهقة ، لكنها تبرر نفسها على المدى الطويل.
آمل أن أكون قد تمكنت من أن أشرح بطريقة سهلة ما هي هذه الآراء الغامضة في الواقع. بالطبع ، هناك سيناريوهات أكثر تقدمًا لاستخدامها ، بالإضافة إلى المزالق في العمل مع التمثيلات بشكل عام وبأقل تمثيلات على وجه الخصوص ، لكنني سأكتب عن هذا في منشور منفصل بطريقة أو بأخرى.