5. الإصدار 1: بنية Spring / JPA
نقترح كتابة تطبيق وحدة تحكم بالإضافة إلى تطبيق رسومي لإنشاء كشوف رواتب لمقدمي خدمات رعاية الأطفال العاملين في "Maison de la petite enfance" في إحدى البلديات. سيكون لهذا التطبيق البنية التالية:
![]() |
5.1. قاعدة البيانات
سيتم تخزين البيانات الثابتة اللازمة لإنشاء كشف الراتب في قاعدة بيانات سنشير إليها باسم dbpam. قد تحتوي قاعدة البيانات هذه على الجداول التالية:
الهيكل:
المفتاح الأساسي | |
رقم الإصدار – يزداد مع كل تعديل للصف | |
رقم الضمان الاجتماعي للموظف – فريد | |
اللقب | |
الاسم الأول | |
عنوانهم | |
مدينته/مدينتها | |
الرمز البريدي الخاص بهم | |
مفتاح خارجي في حقل [ID] في جدول [INDEMNITES] |
قد يكون محتواه كما يلي:

الهيكل:
المفتاح الأساسي | |
رقم الإصدار – يزداد مع كل تعديل للصف | |
النسبة المئوية: المساهمة الاجتماعية العامة + المساهمة في سداد الديون الاجتماعية | |
النسبة المئوية: الاشتراك الاجتماعي العام القابل للخصم | |
النسبة المئوية: الضمان الاجتماعي، الترمل، الشيخوخة | |
النسبة المئوية: المعاش التكميلي + التأمين ضد البطالة |
يمكن أن يكون محتواه كما يلي:
![]()
معدلات اشتراكات الضمان الاجتماعي مستقلة عن الموظف. يحتوي الجدول السابق على صف واحد فقط.
المفتاح الأساسي | |||
رقم الإصدار – يزداد مع كل تعديل للصف | |||
فهرس المعالجة – فريد | |||
السعر الصافي باليورو لساعة واحدة من الخدمة تحت الطلب | |||
البدل اليومي باليورو لكل يوم رعاية | |||
بدل الوجبات باليورو لكل يوم رعاية | |||
بدل الإجازة المدفوعة. وهي نسبة مئوية تُطبق على الراتب الأساسي. | |||
ويمكن أن يكون نصه كما يلي:

يرجى ملاحظة أن البدلات قد تختلف من مقدم رعاية أطفال إلى آخر. فهي مرتبطة بمقدم رعاية أطفال معين من خلال رتبته الوظيفية. وبالتالي، فإن السيدة ماري جوفينال، التي تبلغ رتبتها الوظيفية 2 (جدول الموظفين)، تحصل على أجر ساعي قدره 2.1 يورو (جدول التعويضات).
5.2. طريقة حساب راتب مربية الأطفال
سنعرض الآن طريقة حساب الراتب الشهري لمربية الأطفال. ولا يُقصد من ذلك أن تكون هذه هي الطريقة المستخدمة فعليًا في الممارسة العملية. وكمثال على ذلك، سنستخدم راتب السيدة ماري جوفينال، التي عملت 150 ساعة على مدار 20 يومًا خلال فترة الدفع.
يتم أخذ العوامل التالية في الاعتبار: | | |
الراتب الأساسي لمقدم رعاية الأطفال يُحسب بالصيغة التالية: | ||
يجب خصم مبلغ معين من اشتراكات الضمان الاجتماعي يجب خصمه من هذا الراتب الأساسي : | | |
إجمالي اشتراكات الضمان الاجتماعي: | ||
بالإضافة إلى ذلك، يحق لمقدمة رعاية الأطفال الحصول على بدل معيشة يومي وبدل وجبات عن كل يوم عمل. وبناءً على ذلك، تتلقى البدلات التالية: | ||
في النهاية، يكون الراتب الصافي الذي سيُدفع لمقدم رعاية الأطفال كما يلي: |
5.3. كيفية عمل تطبيق وحدة التحكم
فيما يلي مثال على تشغيل تطبيق وحدة التحكم في نافذة DOS:
سنكتب برنامجًا يتلقى المعلومات التالية:
- رقم الضمان الاجتماعي لمربية الأطفال (254104940426058 في المثال - السطر 1)
- إجمالي عدد ساعات العمل (150 في المثال - السطر 1)
- إجمالي عدد أيام العمل (20 في المثال - السطر 1)
نلاحظ ما يلي:
- الأسطر 9-14: تعرض معلومات عن الموظف الذي تم تقديم رقم الضمان الاجتماعي الخاص به
- الأسطر 17-20: تعرض معدلات الاشتراكات المختلفة
- الأسطر 23-26: تعرض البدلات المرتبطة بدرجة راتب الموظف (هنا، الدرجة 2)
- الأسطر 29–33: تعرض مكونات الراتب المقرر دفعه
يقوم التطبيق بتمييز أي أخطاء:
استدعاء بدون معلمات:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar
Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés
استدعاء ببيانات غير صحيحة:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150x 20x
Le nombre d'heures travaillées [150x] est erroné
Le nombre de jours travaillés [20x] est erroné
اتصال برقم ضمان اجتماعي غير صحيح:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar xx 150 20
L'erreur suivante s'est produite : L'employé de n°[xx] est introuvable
5.4. كيف يعمل التطبيق الرسومي
يحسب التطبيق الرسومي رواتب مقدمي خدمات رعاية الأطفال باستخدام نموذج Swing:
![]() |
- يتم الآن إدخال المعلومات التي تم تمريرها كمعلمات إلى برنامج وحدة التحكم باستخدام حقول الإدخال [1، 2، 3].
- يبدأ الزر [4] عملية حساب الراتب
- يعرض النموذج مكونات الراتب المختلفة حتى الراتب الصافي المقرر دفعه [5]
لا تعرض القائمة المنسدلة [1، 6] أرقام الضمان الاجتماعي للموظفين، بل أسمائهم الأولى والأخيرة. نفترض هنا أنه لا يوجد موظفان يحملان نفس الاسم الأول والأخير.
5.5. إنشاء قاعدة البيانات
نقوم بتشغيل WampServer ونستخدم أداة PhpMyAdmin [1]:
![]() |
- في [2]، حدد خيار [قواعد البيانات]،
![]() |
- في [3]، قم بإنشاء قاعدة بيانات [dbpam_hibernate]،
- في [4]، تم إنشاء قاعدة البيانات. حددها،
![]() |
- في [5]، نريد استيراد نص SQL،
- في [6]، استخدم زر [Browse] لتحديد الملف،
![]() |
- في [7،8]، حدد البرنامج النصي SQL،
- في [9]، نقوم بتنفيذه،
![]() |
- في [10]، تم إنشاء الجداول. ومحتوياتها هي كما يلي:
جدول الموظفين

جدول التعويضات

جدول الاشتراكات
![]()
5.6. تنفيذ JPA
5.6.1. طبقة JPA / Hibernate
سنقوم بتكوين طبقة JPA في البيئة التالية:
![]() |
سيعمل برنامج وحدة التحكم مع قاعدة البيانات. للقيام بذلك، تحتاج إلى:
- قاعدة بيانات،
- برنامج تشغيل JDBC لنظام إدارة قواعد البيانات (DBMS)، وهو في هذه الحالة MySQL،
- تنفيذ طبقة JPA باستخدام Hibernate،
- كتابة برنامج وحدة التحكم.
نقوم بإنشاء مشروع Maven [mv-pam-jpa-hibernate] [1]:
![]() |
في بنية تطبيقنا، نحتاج إلى العناصر التالية:
- قاعدة البيانات،
- برنامج تشغيل JDBC لنظام إدارة قواعد البيانات MySQL،
- طبقة JPA/Hibernate (الكيانات والتكوين)،
- برنامج وحدة التحكم في الاختبار.
5.6.1.1. قاعدة البيانات
أولاً، لنقم بإنشاء قاعدة بيانات فارغة. نقوم بتشغيل WampServer واستخدام أداة PhpMyAdmin [1]:
![]() |
- في [2]، حدد خيار [Databases]،
![]() |
- في [3]، قم بإنشاء قاعدة بيانات باسم [dbpam_hibernate]،
- في [4]، تم إنشاء قاعدة البيانات.
5.6.1.2. تكوين طبقة JPA
يتم تكوين الاتصال بين طبقة JDBC وقاعدة البيانات في ملف [persistence.xml]، الذي يقوم بتكوين طبقة JPA. يمكن إنشاء هذا الملف باستخدام NetBeans:
![]() |
- في علامة التبويب [Services] [1]، قم بالاتصال بقاعدة البيانات باستخدام برنامج تشغيل MySQL JDBC [2]،
- في [3]، اسم قاعدة البيانات التي تريد الاتصال بها.
- في [4]، عنوان URL لـ JDBC الخاص بقاعدة البيانات،
- في [5]، قم بتسجيل الدخول كجذر بدون كلمة مرور،
- في [6]، يمكنك اختبار الاتصال،
- في [7]، تم الاتصال بنجاح.
![]() |
- يظهر الرابط في [8] و[9]،
- في [10]، أضف عنصرًا جديدًا إلى المشروع،
![]() |
- في [11] حدد فئة [Persistence] وفي [12] عنصر [Persistence Unit]،
- في [13]، قم بتسمية وحدة الاستمرارية هذه،
- في [14]، نختار تطبيق Hibernate،
- في [15]، حدد الاتصال الذي أنشأناه للتو بقاعدة بيانات MySQL،
- في [16]، حدد أنه عند إنشاء مثيل لطبقة JPA، يجب أن تقوم بإنشاء الجداول المطابقة لكيانات JPA الخاصة بالمشروع.
في نهاية المعالج، يتم إنشاء ملف [persistence.xml]:
![]() |
- يظهر الملف في فرع جديد من المشروع، في مجلد [META-INF] [1]،
- والذي يتوافق مع مجلد [src/main/resources] الخاص بالمشروع [2,3].
ومحتواه كما يلي:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
- السطر 3: اسم وحدة الاستمرارية ونوع المعاملة. يشير RESOURCE_LOCAL إلى أن المشروع يدير المعاملات بنفسه. في هذه الحالة، سيكون برنامج وحدة التحكم مسؤولاً عن القيام بذلك،
- السطر 4: تنفيذ JPA المستخدم هو Hibernate،
- الأسطر 6-9: خصائص JDBC لاتصال قاعدة البيانات،
- السطر 11: يطلب إنشاء الجداول المطابقة لكيانات JPA. في الواقع، يقوم NetBeans بإنشاء تكوين غير صحيح هنا. يجب أن يكون التكوين كما يلي:
<property name="hibernate.hbm2ddl.auto" value="create"/>
باستخدام الخيار create، يقوم Hibernate، عند إنشاء مثيل لطبقة JPA، بحذف الجداول المطابقة لكيانات JPA ثم إنشاءها. يقوم الخيار create-drop بنفس الشيء، ولكنه يحذف جميع الجداول في نهاية دورة حياة طبقة JPA. هناك خيار آخر:
<property name="hibernate.hbm2ddl.auto" value="update"/>
يقوم هذا الخيار بإنشاء الجداول إذا لم تكن موجودة، ولكنه لا يحذفها إذا كانت موجودة بالفعل.
سنضيف ثلاث خصائص أخرى إلى تكوين Hibernate:
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
توجه هذه الإعدادات Hibernate لعرض عبارات SQL التي يرسلها إلى قاعدة البيانات. وبالتالي، يكون الملف الكامل كما يلي:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
</properties>
</persistence-unit>
</persistence>
5.6.1.3. التبعيات
لنعد إلى بنية المشروع:
![]() |
قمنا بتكوين طبقة JPA عبر ملف [persistence.xml]. وكان التنفيذ المختار هو Hibernate. وقد أدى ذلك إلى إدخال تبعيات في المشروع:
![]() |
تعود هذه التبعيات إلى تضمين Hibernate في المشروع. نحتاج إلى إضافة تبعية أخرى: برنامج تشغيل MySQL JDBC، الذي ينفذ طبقة JDBC في البنية. نقوم بتحديث ملف [pom.xml] على النحو التالي:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
</dependency>
...
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>4.0.1.Final</version>
</dependency>
</dependencies>
تضيف الأسطر 8–12 التبعية لمحرك MySQL JDBC.
5.6.1.4. كيانات JPA
![]() |
السؤال: باتباع النهج الوارد في المثال في القسم 4.4، قم بإنشاء الكيانات [Cotisation، Indemnite، Employe].
ملاحظات:
- ستكون الكيانات جزءًا من حزمة باسم [jpa]،
- وسيكون لكل كيان رقم إصدار،
- إذا تم ربط كيانين بعلاقة، فسيتم إنشاء العلاقة الأساسية @ManyToOne فقط. ولن يتم إنشاء العلاقة العكسية @OneToMany.
5.6.1.5. كود الفئة الرئيسية
نقوم بتضمين كيانات JPA التي تم تطويرها مسبقًا [1] في المشروع:
![]() |
ثم نضيف [2]، الفئة [main.Main] التالية:
package main;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Main {
public static void main(String[] args) {
// creating the Entity Manager is enough to build the JPA layer
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-hibernatePU");
EntityManager em=emf.createEntityManager();
// resource release
em.close();
emf.close();
}
}
- السطر 10: نقوم بإنشاء EntityManagerFactory لوحدة الاستمرارية المسماة [mv-pam-jpa-hibernatePU]. يأتي هذا الاسم من ملف [persistence.xml]:
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
...
</persistence-unit>
- السطر 12: يتم إنشاء EntityManager. يؤدي هذا إلى إنشاء طبقة JPA. سيتم استخدام ملف [persistence.xml]، وبالتالي سيتم إنشاء جداول قاعدة البيانات.
- السطران 14-15: يتم تحرير الموارد.
5.6.1.6. الاختبارات
لنعد إلى بنية مشروعنا:
![]() |
تم تنفيذ جميع الطبقات. نقوم بتشغيل المشروع [2].
![]() |
إخراج وحدة التحكم كما يلي:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
تحتوي وحدة التحكم على سجلات Hibernate فقط، لأن البرنامج الذي تم تنفيذه لا يقوم بأي شيء سوى إنشاء مثيل لطبقة JPA. لاحظ النقاط التالية:
- السطر 43: يحاول Hibernate حذف المفتاح الخارجي من جدول [EMPLOYEES]،
- الأسطر 51–55: حذف الجداول الثلاثة،
- السطر 57: إنشاء جدول [COTISATIONS]،
- السطر 67: إنشاء جدول [EMPLOYEES]،
- السطر 80: إنشاء جدول [INDEMNITIES]،
- السطر 91: إنشاء المفتاح الخارجي لجدول [EMPLOYEES].
في NetBeans، يمكنك عرض الجداول في الاتصال الذي تم إنشاؤه مسبقًا:
![]() |
تعتمد الجداول التي تم إنشاؤها على كل من تطبيق طبقة JPA المستخدم ونظام إدارة قواعد البيانات (DBMS) المستخدم. وبالتالي، يمكن لتطبيق JPA/EclipseLink مع نفس قاعدة البيانات أن يولد جداول مختلفة. وهذا ما سننظر فيه الآن.
5.6.2. طبقة JPA / EclipseLink
سنقوم بإنشاء مشروع Maven جديد في البيئة التالية:
![]() |
سنتبع الخطوات الواردة في القسم السابق:
- إنشاء قاعدة بيانات MySQL [dbpam_eclipselink]. سنستخدم البرنامج النصي [dbpam_eclipselink.sql] لإنشائها،
- إنشاء ملف [persistence.xml] الخاص بالمشروع. استخدم تطبيق EclipseLink JPA 2.0،
- أضف تبعية برنامج تشغيل MySQL JDBC إلى التبعيات التي تم إنشاؤها،
- إضافة كيانات JPA وبرنامج وحدة التحكم،
- تشغيل الاختبارات.
سيكون ملف [persistence.xml] كما يلي:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="pam-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="eclipselink.target-database" value="MySQL"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
- تم إنشاء الخصائص 9–13 بواسطة معالج NetBeans،
- السطر 14: تسمح لنا هذه الخاصية بتعيين مستوى تسجيل EclipseLink. يسمح لنا المستوى FINE برؤية عبارات SQL التي سيقوم EclipseLink بتنفيذها على قاعدة البيانات،
- السطر 15: عند إنشاء مثيل لطبقة JPA/EclipseLink، سيتم حذف جداول كيانات JPA ثم إنشاؤها.
إخراج وحدة التحكم كما يلي:
- الأسطر 26-30: الاتصال بقاعدة بيانات MySQL،
- الأسطر 31-34: تأكيد نجاح الاتصال،
- السطر 36: حذف المفتاح الخارجي من جدول [EMPLOYEES]،
- السطر 37: حذف الجدول [COTISATIONS]،
- السطر 38: إنشاء جدول [CONTRIBUTIONS]. تجدر الإشارة إلى أن معرف المفتاح الأساسي لا يحتوي على سمة auto_increment في MySQL. وهذا يعني أن MySQL لا يقوم بإنشاء قيم المفتاح الأساسي،
- السطر 39: حذف جدول [EMPLOYEES]،
- السطر 40: إنشاء جدول [EMPLOYEES]. لا يحتوي معرف المفتاح الأساسي الخاص به على سمة auto_increment في MySQL،
- السطر 41: حذف جدول [INDEMNITIES]،
- السطر 42: إنشاء جدول [INDEMNITIES]. لا يحتوي معرف المفتاح الأساسي الخاص به على سمة auto_increment في MySQL،
- السطر 43: إنشاء مفتاح خارجي من جدول [EMPLOYEES] إلى جدول [BENEFITS]،
- السطر 44: إنشاء جدول [SEQUENCE]. سيتم استخدامه لتوليد المفاتيح الأساسية للجداول الثلاثة السابقة،
- السطر 47: تحدث استثناء لأن هذا الجدول موجود بالفعل،
- الأسطر 51–53: تهيئة الجدول [SEQUENCE].
يمكن التحقق من وجود الجداول التي تم إنشاؤها في NetBeans [1]:
![]() |
وبالتالي، استنادًا إلى نفس كيانات JPA، لا تُنشئ تطبيقات Hibernate و EclipseLink JPA نفس الجداول. في بقية هذا المستند، عندما يكون تطبيق JPA المستخدم هو:
- Hibernate، سنستخدم قاعدة البيانات [dbpam_hibernate]،
- EclipseLink، سنستخدم قاعدة البيانات [dbpam_eclipselink].
5.6.3. العمل المطلوب
باتباع نفس الإجراء السابق،
- قم بإنشاء واختبار مشروع [mv-pam-jpa-hibernate-oracle] باستخدام تطبيق Hibernate JPA ونظام إدارة قواعد البيانات Oracle،
- قم بإنشاء واختبار مشروع [mv-pam-jpa-hibernate-mssql] باستخدام تطبيق Hibernate JPA ونظام إدارة قواعد البيانات SQL Server،
- قم بإنشاء واختبار مشروع [mv-pam-jpa-eclipselink-oracle] باستخدام تطبيق EclipseLink JPA ونظام إدارة قواعد البيانات Oracle،
- إنشاء واختبار مشروع [mv-pam-jpa-eclipselink-mssql] باستخدام تطبيق EclipseLink JPA ونظام إدارة قواعد البيانات SQL Server،
5.6.4. Lazy أم Eager؟
لنعد إلى تعريف محتمل لكيان [Employee]:
package jpa;
...
@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
@Column(name="VERSION",nullable=false)
private int version;
@Column(name="SS", nullable=false, unique=true, length=15)
private String SS;
@Column(name="NOM", nullable=false, length=30)
private String nom;
@Column(name="PRENOM", nullable=false, length=20)
private String prenom;
@Column(name="ADRESSE", nullable=false, length=50)
private String adresse;
@Column(name="VILLE", nullable=false, length=30)
private String ville;
@Column(name="CP", nullable=false, length=5)
private String codePostal;
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
...
}
تحدد الأسطر 27–29 المفتاح الخارجي من جدول [EMPLOYEES] إلى جدول [INDEMNITIES]. تحدد سمة fetch في السطر 27 استراتيجية الاسترجاع لحقل التعويض في السطر 29. هناك وضعان:
- FetchType.LAZY: عند الاستعلام عن موظف، لا يتم استرداد التعويض المقابل. سيتم استرداده عند الإشارة إلى حقل [Employee].indemnity لأول مرة.
- FetchType.EAGER: عند البحث عن موظف، يتم استرداد البدل المقابل. هذا هو الوضع الافتراضي عندما لا يتم تحديد أي وضع.
لفهم فائدة خيار FetchType.LAZY، انظر المثال التالي. يتم عرض قائمة بالموظفين بدون تعويضاتهم على صفحة ويب تحتوي على رابط [Details]. يؤدي النقر فوق هذا الرابط إلى عرض تعويض الموظف المحدد. نلاحظ ما يلي:
- لعرض الصفحة الأولى، لا نحتاج إلى بيانات الموظفين ومزاياهم. ولذلك، فإن وضع FetchType.LAZY هو الأنسب؛
- لعرض الصفحة الثانية مع التفاصيل، يجب إجراء استعلام إضافي إلى قاعدة البيانات لاسترداد مزايا الموظف المحدد.
يمنع وضع FetchType.LAZY استرداد الكثير من البيانات التي لا يحتاجها التطبيق على الفور. لنلقِ نظرة على مثال.
تم نسخ مشروع [mv-pam-jpa-hibernate]:
![]() |
- في [1]، يتم نسخ المشروع،
- في [2]، نحدد المجلد للنسخة، وفي [3] نحدد اسمها،
- في [4]، يحمل المشروع الجديد نفس اسم المشروع القديم. نقوم بتغيير هذا:
![]() |
- في [1]، نغير اسم المشروع،
- في [2]، نغير اسم المشروع و artifactId الخاص به،
- في [3]، المشروع الجديد.
نقوم بتعديل برنامج [Main.java] على النحو التالي:
package main;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import jpa.Employe;
public class Main {
// the JPQL query below brings back an employee
// the foreign key [Employe].indemnite is in FetchType.LAZY
public static void main(String[] args) {
// creating the Entity Manager is enough to build the JPA layer
EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-hibernatePU");
// first attempt
EntityManager em = emf.createEntityManager();
Employe employe = (Employe) em.createQuery("select e from Employe e where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
em.close();
// we display the employee
try {
System.out.println(employe);
} catch (Exception ex) {
System.out.println(ex);
}
// second test
em = emf.createEntityManager();
employe = (Employe) em.createQuery("select e from Employe e left join fetch e.indemnite where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
// free up resources
em.close();
// we display the employee
try {
System.out.println(employe);
} catch (Exception ex) {
System.out.println(ex);
}
// resource release
emf.close();
}
}
- السطر 15: نقوم بإنشاء EntityManagerFactory لطبقة JPA،
- السطر 17: نحصل على EntityManager، الذي يسمح لنا بالتفاعل مع طبقة JPA،
- السطر 18: نسترد الموظف المسمى Jouveinal،
- السطر 19: نغلق EntityManager. يؤدي هذا إلى إغلاق سياق الاستمرارية.
- السطر 22: نعرض الموظف الذي تم استرداده.
فئة [Employee] هي كما يلي:
package jpa;
...
@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
@Column(name="VERSION",nullable=false)
private int version;
@Column(name="SS", nullable=false, unique=true, length=15)
private String SS;
@Column(name="NOM", nullable=false, length=30)
private String nom;
@Column(name="PRENOM", nullable=false, length=20)
private String prenom;
@Column(name="ADRESSE", nullable=false, length=50)
private String adresse;
@Column(name="VILLE", nullable=false, length=30)
private String ville;
@Column(name="CP", nullable=false, length=5)
private String codePostal;
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
/**
* Returns a string representation of the object. This implementation constructs
* that representation based on the id fields.
* @return a string representation of the object.
*/
@Override
public String toString() {
return "jpa.Employe[id=" + getId()
+ ",version="+getVersion()
+",SS="+getSS()
+ ",nom="+getNom()
+ ",prenom="+getPrenom()
+ ",adresse="+getAdresse()
+",ville="+getVille()
+",code postal="+getCodePostal()
+",indice="+getIndemnite().getIndice()
+"]";
}
...
}
- السطر 27: يتم جلب حقل indemnite في الوضع LAZY،
- السطر 47: يستخدم حقل indemnite. إذا تم استدعاء طريقة toString بينما لم يتم استرداد حقل indemnite بعد، فسيتم استرداده في تلك اللحظة. ما لم يتم إغلاق سياق الاستمرارية، كما في المثال.
لنعد إلى كود [Main]:
- الأسطر 21–25: يجب أن نحصل على استثناء. وذلك لأن طريقة toString سيتم استدعاؤها. وستستخدم حقل indemnite. سيتم البحث عن هذا الحقل. وبما أن سياق الاستمرارية قد تم إغلاقه، فإن كيان [Employee] الذي تم استرداده لم يعد موجودًا، ومن ثم يحدث الاستثناء.
- السطر 27: نقوم بإنشاء EntityManager جديد،
- السطر 28: نسترد الموظف Jouveinal عن طريق طلب البدل المرتبط به صراحةً في استعلام JPQL. هذا الطلب الصريح ضروري لأن وضع الاسترداد لهذا البدل هو LAZY،
- السطر 30: نغلق EntityManager،
- الأسطر 32-36: نعرض الموظف مرة أخرى. لا ينبغي أن يكون هناك استثناء.
لتشغيل المشروع، تحتاج إلى قاعدة بيانات تحتوي على بيانات. ستقوم بإنشائها باتباع الخطوات الواردة في القسم 5.5. بالإضافة إلى ذلك، يجب تعديل ملف [persistence.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
</properties>
</persistence-unit>
</persistence>
- لقد أزلنا الخيار الذي كان ينشئ الجداول. قاعدة البيانات موجودة بالفعل ومملوءة،
- وقد أزلنا الخيارات التي كانت تجعل Hibernate يسجل عبارات SQL التي أرسلها إلى قاعدة البيانات.
يؤدي تشغيل المشروع إلى ظهور الرسالتين التاليتين في وحدة التحكم:
- السطر 1: الاستثناء الذي حدث عند محاولة استرداد التعويض المفقود أثناء إغلاق الجلسة. يمكننا أن نرى أن التعويض لم يتم استرداده بسبب وضع LAZY،
- السطر 2: الموظف مع بدلته تم استرداده عبر استعلام تجاوز وضع LAZY.
5.6.5. المهام المطلوب إنجازها
باتباع إجراء مشابه للإجراء الموصوف للتو، قم بإنشاء مشروع [mv-pam-pa-eclipselink-lazy] يوضح سلوك EclipseLink مع وضع LAZY.
تم الحصول على النتائج التالية:
في الوضع LAZY، أعاد كلا الاستعلامين الأجر مع الموظف. عند البحث عن هذه الحالة الشاذة عبر الإنترنت، نكتشف أن التعليق التوضيحي [FetchType.LAZY] (السطر 1):
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
ليس شرطًا بل اقتراحًا. ولا يُلزم مُنفِّذ JPA باتباعه. وبالتالي، يمكننا أن نرى أن الكود يصبح أحيانًا معتمدًا على تنفيذ JPA المستخدم. ومن الممكن تكوين EclipseLink ليعمل بالشكل المتوقع في الوضع LAZY.
5.6.6. المضي قدمًا
تكون بنية التطبيق المراد بناؤه كما يلي:
![]() |
في بقية هذا المستند، سنقوم بنسخ مشروع Maven [mv-pam-jpa-hibernate] إلى مشروع [mv-pam-spring-hibernate] [1، 2، 3]:
![]() |
- ثم سنقوم بتغيير اسم المشروع الجديد [4، 5، 6].
سنقوم بتغيير التبعيات الخاصة بالمشروع الجديد. سيصبح ملف [pom.xml] كما يلي:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-pam-spring-hibernate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-spring-hibernate</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>swing-layout</id>
<layout>default</layout>
<name>Repository for library Library[swing-layout]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
</project>
- الأسطر 25–31: التبعية لاختبارات JUnit،
- الأسطر 32–41: التبعيات لمجمع اتصالات Apache DBCP،
- الأسطر 42–65: التبعيات الخاصة بإطار عمل Spring،
- الأسطر 67–71: التبعيات لتنفيذ JPA/Hibernate،
- الأسطر 72–76: التبعية لمحرك MySQL JDBC،
- الأسطر 77–81: التبعية لواجهة Swing. يتم إضافة هذا تلقائيًا بواسطة NetBeans عند إضافة واجهة Swing إلى المشروع.
بالإضافة إلى ذلك، سنقوم بإنشاء قاعدتي بيانات MySQL:
- [dbpam_hibernate] من البرنامج النصي [dbpam_hibernate.sql]،
- [dbpam_eclipselink] من البرنامج النصي [dbpam_eclipselink.sql]،
5.7. واجهة im s لطبقات [business] و [DAO]
لنعد إلى بنية التطبيق:
![]() |
في البنية أعلاه، ما هي الواجهة التي يجب أن توفرها طبقة [DAO] لطبقة [الأعمال]، وما هي الواجهة التي يجب أن توفرها طبقة [الأعمال] لطبقة [واجهة المستخدم]؟ تتمثل الطريقة الأولى لتعريف واجهات الطبقات المختلفة في دراسة حالات الاستخدام المتنوعة للتطبيق. لدينا هنا حالتان، اعتمادًا على واجهة المستخدم المختارة: وحدة التحكم أو النموذج الرسومي.
دعونا ندرس كيفية استخدام تطبيق وحدة التحكم:
يتلقى التطبيق ثلاث معلومات من المستخدم (انظر السطر 1 أعلاه)
- رقم الضمان الاجتماعي لمقدم رعاية الأطفال
- عدد ساعات العمل في الشهر
- عدد أيام العمل في الشهر
بناءً على هذه المعلومات والبيانات الأخرى المخزنة في ملفات التكوين، يعرض التطبيق المعلومات التالية:
- الأسطر 4–6: القيم التي تم إدخالها
- الأسطر 8-10: المعلومات المتعلقة بالموظف الذي تم تقديم رقم الضمان الاجتماعي الخاص به
- الأسطر 12-14: معدلات مختلف اشتراكات الضمان الاجتماعي
- السطور 16-17: المخصصات المختلفة المدفوعة لمقدم رعاية الأطفال
- الأسطر 19-24: البنود الواردة في كشف راتب مقدم رعاية الأطفال
يجب أن تقدم الطبقة [التجارية] قدرًا معينًا من المعلومات إلى الطبقة [UI]:
- المعلومات المتعلقة بمقدم رعاية الأطفال المحدد برقم الضمان الاجتماعي الخاص به. توجد هذه المعلومات في جدول [EMPLOYEES]. وهذا يسمح بعرض الأسطر 6-8.
- مبالغ معدلات الاشتراكات المختلفة في الضمان الاجتماعي التي سيتم خصمها من الراتب الإجمالي. توجد هذه المعلومات في جدول [COTISATIONS]. وهذا يسمح بعرض الأسطر 10-12.
- مبالغ البدلات المختلفة المتعلقة بوظيفة مقدم رعاية الأطفال. توجد هذه المعلومات في جدول [INDEMNITES]. وهذا يسمح بعرض الأسطر 14-15.
- مكونات الراتب المعروضة في الأسطر 18-22.
من هذا، يمكننا اتخاذ قرار بشأن التنفيذ الأولي لواجهة [IMetier] التي تقدمها طبقة [metier] إلى طبقة [ui]:
- السطر 1: يتم وضع عناصر طبقة [business] في حزمة [business]
- السطر 5: تأخذ طريقة [calculatePaystub] كمعلمات المعلومات الثلاث التي تم الحصول عليها من طبقة [ui] وتُرجع كائنًا من النوع [Paystub] يحتوي على المعلومات التي ستعرضها طبقة [ui] على وحدة التحكم. يمكن أن تكون فئة [ Paystub] كما يلي:
- السطر 9: الموظف المشمول بكشوف الراتب - المعلومات رقم 1 المعروضة بواسطة طبقة [ui]
- السطر 10: معدلات الاشتراكات المختلفة - المعلومات رقم 2 المعروضة بواسطة طبقة [ui]
- السطر 11: البدلات المختلفة المرتبطة بمؤشر الموظف - المعلومات رقم 3 المعروضة بواسطة طبقة [ui]
- السطر 12: مكونات راتبه - المعلومات رقم 4 المعروضة بواسطة طبقة [ui]
تظهر حالة استخدام ثانية لطبقة [business] مع الواجهة الرسومية:
![]() |
كما هو موضح أعلاه، تعرض القائمة المنسدلة [1, 2] جميع الموظفين. يجب طلب هذه القائمة من طبقة [الأعمال]. ثم تتطور واجهة ace لهذه الطبقة على النحو التالي:
- السطر [10]: الطريقة التي ستسمح لطبقة [UI] بطلب قائمة بجميع الموظفين من طبقة [Business].
لا يمكن لطبقة [الأعمال] تهيئة حقول [الموظف، والمساهمة، والبدل] الخاصة بكائن [كشوف المرتبات] أعلاه إلا من خلال الاستعلام عن طبقة [DAO]، حيث إن هذه المعلومات مخزنة في جداول قاعدة البيانات. وينطبق الأمر نفسه على استرداد قائمة بجميع الموظفين. يمكننا إنشاء واجهة [DAO] واحدة لإدارة الوصول إلى الكيانات الثلاثة [الموظف، المساهمة، البدل]. ومع ذلك، فقد قررنا هنا إنشاء واجهة [DAO] واحدة لكل كيان.
ستكون واجهة [DAO] للوصول إلى كيانات [Contribution] في جدول [CONTRIBUTIONS] كما يلي:
- السطر 6: تدير واجهة [ICotisationDao] الوصول إلى كيان [Cotisation] وبالتالي إلى جدول [COTISATIONS] في قاعدة البيانات. لا يحتاج تطبيقنا سوى إلى الطريقة [findAll] في السطر 16، والتي تسترد جميع محتويات جدول [COTISATIONS]. هنا، أردنا معالجة حالة أكثر عمومية حيث يتم تنفيذ جميع عمليات CRUD (إنشاء، قراءة، تحديث، حذف) على الكيان.
- السطر 8: تقوم طريقة [create] بإنشاء كيان [Cotisation] جديد
- السطر 10: تعمل طريقة [edit] على تعديل كيان [Cotisation] موجود
- السطر 12: تقوم طريقة [destroy] بحذف كيان [Cotisation] موجود
- السطر 14: تسترد طريقة [find] كيان [Cotisation] موجودًا باستخدام معرّفه
- السطر 16: تعرض طريقة [findAll] قائمة بجميع كيانات [Membership] الموجودة
دعونا نلقي نظرة فاحصة على توقيع طريقة [create]:
تحتوي طريقة create على معلمة cotisation من نوع Cotisation. يجب أن يتم حفظ معلمة cotisation، أي تخزينها في جدول [COTISATIONS]. قبل هذا الحفظ، تحتوي معلمة cotisation على معرف id بدون قيمة. بعد الحفظ، يحتوي حقل id على قيمة تمثل المفتاح الأساسي للسجل المضاف إلى جدول [COTISATIONS]. وبالتالي، فإن معلمة cotisation هي معلمة إدخال/إخراج لطريقة create. لا يبدو من الضروري أن تقوم طريقة create بإرجاع معلمة cotisation كنتيجة إضافية. نظرًا لأن الطريقة المستدعية تحتفظ بمرجع إلى كائن [Cotisation cotisation]، فإنه في حالة تعديله، تتمتع الطريقة المستدعية بإمكانية الوصول إلى الكائن المعدل لأنها تحتفظ بمرجع إليه. وبالتالي، يمكنها معرفة القيمة التي عينتها طريقة create لحقل id في كائن [Cotisation cotisation]. وبالتالي، يمكن تبسيط توقيع الطريقة إلى:
عند كتابة واجهة، من المهم تذكر أنه يمكن استخدامها في سياقين مختلفين: محلي وعن بُعد . في السياق المحلي، يتم تنفيذ الطريقة المستدعية والطريقة المستدعاة في نفس JVM:
![]() |
إذا استدعت طبقة [الأعمال] طريقة create في طبقة [DAO]، فإنها تمتلك بالفعل مرجعًا إلى المعلمة [Membership membership] التي تمررها إلى الطريقة.
في السياق البعيد، يتم تنفيذ الطريقة المستدعية والطريقة المستدعاة في JVMs مختلفة:
![]() |
في المثال أعلاه، تعمل طبقة [business] في JVM 1 وطبقة [DAO] في JVM 2 على جهازين مختلفين. لا تتواصل الطبقتان مباشرة. بينهما توجد طبقة سنسميها طبقة الاتصال [1]. تتكون هذه الطبقة من طبقة إرسال [2] وطبقة استقبال [3]. لا يتعين على المطور عمومًا كتابة طبقات الاتصال هذه. يتم إنشاؤها تلقائيًا بواسطة أدوات برمجية. تُكتب طبقة [الأعمال] كما لو كانت تعمل في نفس JVM التي تعمل فيها طبقة [DAO]. لذلك، لا يلزم إجراء أي تغييرات في الكود.
آلية الاتصال بين طبقة [الأعمال] وطبقة [DAO] هي كما يلي:
- تستدعي طبقة [الأعمال] طريقة create الخاصة بطبقة [DAO]، وتمرر لها المعلمة [Contribution contribution1]
- يتم تمرير هذه المعلمة فعليًا إلى طبقة الإرسال [2]. ستقوم هذه الطبقة بإرسال قيمة المعلمة cotisation1 عبر الشبكة، وليس مرجعها. يعتمد الشكل الدقيق لهذه القيمة على بروتوكول الاتصال المستخدم.
- تسترد طبقة الاستقبال [3] هذه القيمة وتستخدمها لإعادة بناء كائن [Cotisation cotisation2] يعكس المعلمة الأولية المرسلة من طبقة [business]. لدينا الآن كائنان متطابقان (من حيث المحتوى) في جهازي JVM مختلفين: cotisation1 و cotisation2.
- ستقوم طبقة العرض بتمرير كائن `contribution2` إلى طريقة `create` في طبقة [DAO]، والتي ستحفظه في قاعدة البيانات. بعد هذه العملية، تم تهيئة حقل `id` في كائن `contribution2` بالمفتاح الأساسي للسجل المضاف إلى جدول [COTISATIONS]. ليس هذا هو الحال بالنسبة للكائن `contribution1`، الذي تشير إليه طبقة [business]. إذا أردنا أن تشير طبقة [business] إلى الكائن cotisation2، فيجب علينا تمريره إلى الطبقة. لذلك، نحتاج إلى تغيير توقيع طريقة create في طبقة [DAO]:
- مع هذا التوقيع الجديد، ستُرجع طريقة create الكائن الثابت contribution2. يتم إرجاع هذه النتيجة إلى الطبقة المستقبلة [3] التي استدعت طبقة [DAO]. ستُرجع طبقة [DAO] قيمة (وليس مرجع) contribution2 إلى الطبقة المرسلة [2].
- ستسترد الطبقة المرسلة [2] هذه القيمة وتستخدمها لإعادة بناء كائن [Membership membership3] يعكس النتيجة التي أعادتها طريقة create في طبقة [DAO].
- يتم إرجاع الكائن [Contribution contribution3] إلى الطريقة في طبقة [business] التي أدى استدعاءها لطريقة create في طبقة [DAO] إلى بدء هذه الآلية بأكملها. وبالتالي، يمكن لطبقة [business] تحديد قيمة المفتاح الأساسي المخصصة للكائن [Contribution contribution1] الذي طلبت استمراريته: هذه هي قيمة حقل id في contribution3.
البنية السابقة ليست الأكثر شيوعًا. في أغلب الأحيان، توجد طبقات [business] و[DAO] في نفس JVM:
![]() |
في هذه البنية، يجب أن تكون طرق طبقة [الأعمال] هي التي تُرجع النتائج، وليس طرق طبقة [DAO]. ومع ذلك، فإن التوقيع التالي لطريقة create في طبقة [DAO]:
يسمح لنا بتجنب وضع افتراضات حول البنية الفعلية الموجودة. استخدام التوقيعات التي ستعمل بغض النظر عن البنية المختارة —سواء كانت محلية أو بعيدة— يعني أنه إذا قامت طريقة مستدعاة بتعديل بعض معلماتها:
- يجب أن تكون هذه أيضًا جزءًا من نتيجة الدالة التي تم استدعاؤها
- يجب أن تستخدم الطريقة المستدعية نتيجة الطريقة المستدعاة وليس المراجع إلى المعلمات المعدلة التي مررتها إلى الطريقة المستدعاة.
وهذا يسمح لنا بالانتقال من بنية محلية إلى بنية بعيدة دون تعديل الكود. دعونا نعيد النظر في واجهة [ICotisationDao] في ضوء ذلك:
- السطر 8: تمت معالجة حالة طريقة create
- السطر 10: تستخدم طريقة edit المعلمة [Cotisation cotisation1] لتحديث السجل في جدول [COTISATIONS] الذي له نفس المفتاح الأساسي لكائن cotisation. وتُرجع كائن cotisation2، الذي يمثل السجل المعدل. لا يتم تعديل المعلمة contribution1 نفسها. يجب أن تُرجع الطريقة contribution2 كنتيجة، سواء في بنية بعيدة أو محلية.
- السطر 12: تحذف طريقة destroy السجل من جدول [COTISATIONS] الذي له نفس المفتاح الأساسي لكائن contribution الذي تم تمريره كمعلمة. لا يتم تعديل كائن contribution. لذلك، لا يلزم إرجاعه.
- السطر 14: لا يتم تعديل المعلمة id للطريقة find بواسطة الطريقة. ولا يلزم تضمينها في النتيجة.
- السطر 16: لا تحتوي طريقة findAll على أي معلمات. لذلك، لا داعي لفحصها.
في النهاية، لا يلزم سوى تكييف توقيع طريقة create لتكون قابلة للاستخدام ضمن بنية بعيدة. ينطبق المنطق المذكور أعلاه على واجهات [DAO] الأخرى. لن نكرره هنا، بل سنستخدم بدلاً من ذلك توقيعات قابلة للاستخدام في كل من البنى البعيدة والمحلية.
ستكون واجهة [DAO] للوصول إلى كيانات [Indemnite] في جدول [INDEMNITES] كما يلي:
- السطر 6: تدير واجهة [IIndemniteDao] الوصول إلى كيان [Indemnite] وبالتالي إلى جدول [INDEMNITES] في قاعدة البيانات. لا يحتاج تطبيقنا سوى إلى الطريقة [findAll] في السطر 16، والتي تسترد محتويات جدول [INDEMNITES] بالكامل. هنا، أردنا معالجة حالة أكثر عمومية حيث يتم تنفيذ جميع عمليات CRUD (إنشاء، قراءة، تحديث، حذف) على الكيان.
- السطر 8: تقوم طريقة [create] بإنشاء كيان [Indemnite] جديد
- السطر 10: تعمل طريقة [edit] على تعديل كيان [Indemnite] موجود
- السطر 12: تحذف طريقة [destroy] كيان [Indemnite] موجود
- السطر 14: تسترد طريقة [find] كيان [Indemnite] موجود باستخدام معرّفه
- السطر 16: تعرض طريقة [findAll] قائمة بجميع كيانات [Indemnite] الموجودة
ستكون واجهة [DAO] للوصول إلى كيانات [Employe] في جدول [EMPLOYES] كما يلي:
- السطر 6: تدير واجهة [IEmployeDao] الوصول إلى كيان [Employee] وبالتالي إلى جدول [EMPLOYEES] في قاعدة البيانات. لا يحتاج تطبيقنا سوى إلى الأسلوب [findAll] في السطر 16، الذي يسترد جميع محتويات الجدول [EMPLOYEES]. هنا، أردنا معالجة حالة أكثر عمومية حيث يتم تنفيذ جميع عمليات CRUD (إنشاء، قراءة، تحديث، حذف) على الكيان.
- السطر 8: تقوم طريقة [create] بإنشاء كيان [Employee] جديد
- السطر 10: تعمل طريقة [edit] على تعديل كيان [Employee] موجود
- السطر 12: تقوم طريقة [destroy] بحذف كيان [Employee] موجود
- السطر 14: تسترد طريقة [find] كيان [Employee] موجودًا عبر معرّفه
- السطر 16: تسترد طريقة [find(String SS)] كيان [Employee] موجود باستخدام رقم SS الخاص به. وقد رأينا أن هذه الطريقة كانت ضرورية لتطبيق وحدة التحكم.
- السطر 18: تعرض طريقة [findAll] قائمة بجميع كيانات [Employee] الموجودة. وقد رأينا أن هذه الطريقة كانت ضرورية لتطبيق الرسوميات.
5.8. فئة [PamException]
ستعمل طبقة [DAO] مع واجهة برمجة تطبيقات JDBC في Java. ترمي واجهة برمجة التطبيقات هذه استثناءات [SQLException] خاضعة للرقابة، والتي لها عيبان:
- إنها تزيد من حجم الكود، الذي يجب أن يتعامل مع هذه الاستثناءات باستخدام كتل try/catch.
- يجب الإعلان عنها في توقيعات طرق واجهة [IDao] باستخدام "throws SQLException". وهذا يمنع تنفيذ هذه الواجهة بواسطة الفئات التي قد ترمي استثناءً خاضعًا للرقابة من نوع غير [SQLException].
لحل هذه المشكلة، ستقوم طبقة [DAO] فقط بـ"نشر" الاستثناءات غير المحددة من النوع [PamException].
![]() |
- تقوم طبقة [JDBC] بإلقاء استثناءات من النوع [SQLException]
- تقوم طبقة [JPA] بإلقاء استثناءات خاصة بتنفيذ JPA المستخدم
- تقوم طبقة [DAO] بإلقاء استثناءات غير ملتقطة من النوع [PamException]
وهذا له نتيجتان:
- لن تكون طبقة [business] مطالبة بمعالجة الاستثناءات من طبقة [DAO] باستخدام كتل try/catch. يمكنها ببساطة السماح لها بالانتشار حتى طبقة [UI].
- لا تحتاج أساليب واجهة [IDao] إلى تحديد طبيعة [PamException] في توقيعاتها، مما يترك المجال مفتوحًا لتنفيذ هذه الواجهة باستخدام فئات من شأنها إلقاء نوع آخر من الاستثناءات غير الملتقطة.
سيتم وضع فئة [PamException] في حزمة [exception] لمشروع NetBeans:
![]() |
وفيما يلي شفرة البرمجة الخاصة بها:
- السطر 4: [PamException] مشتق من [RuntimeException]. ولذلك فهو نوع من الاستثناءات التي لا يطلب منا المُجمِّع معالجتها باستخدام كتلة try/catch أو تضمينها في توقيعات الطرق. ولهذا السبب، لا يتم تضمين [PamException] في توقيعات طرق واجهة [IDao]. وهذا يسمح بتنفيذ الواجهة بواسطة فئة ترمي نوعًا مختلفًا من الاستثناءات، بشرط أن تكون مشتقة أيضًا من [RuntimeException].
- للتمييز بين الأخطاء التي قد تحدث، نستخدم رمز الخطأ في السطر 7. المنشئات الثلاثة في الأسطر 14 و19 و24 هي منشئات الفئة الأصلية [RuntimeException]، التي أضفنا إليها معلمة: رمز الخطأ الذي نريد تعيينه للاستثناء.
سيكون سلوك التطبيق، من منظور الاستثناءات، كما يلي:
- ستقوم طبقة [DAO] بتغليف أي استثناء يتم مواجهته داخل [PamException] وإعادة إلقائه إلى طبقة [business].
- ستسمح طبقة [business] للاستثناءات التي ترميها طبقة [DAO] بالانتشار إلى الأعلى. وستقوم بتغليف أي استثناء يحدث في طبقة [business] في [PamException] وإعادة رميها إلى طبقة [UI].
- تقوم طبقة [UI] باعتراض جميع الاستثناءات التي تنتقل من طبقتي [business] و[DAO]. وستقوم ببساطة بعرض الاستثناء على وحدة التحكم أو واجهة المستخدم الرسومية.
دعونا الآن نفحص تنفيذ طبقتي [DAO] و[business] بالترتيب.
5.9. طبقة [DAO] لتطبيق [PAM]
نحن نعمل ضمن البنية التالية:
![]() |
5.9.1. التنفيذ
قراءة موصى بها: القسم 3.1.3 من [المرجع 1]
السؤال: باستخدام تكامل Spring/JPA، اكتب الفئات [CotisationDao، IndemniteDao، EmployeDao] لتنفيذ الواجهات [ICotisationDao، IIndemniteDao، IEmployeDao]. ستقوم كل طريقة في الفئة بالتقاط أي استثناء وتغليفه في [PamException] مع رمز خطأ خاص بالاستثناء الذي تم التقاطه.
ستكون فئات التنفيذ جزءًا من حزمة [dao]:
![]() |
5.9.2. التكوين
قراءة موصى بها: القسم 3.1.5 من [المرجع 1]
يتم تكوين تكامل DAO/JPA بواسطة ملف Spring [spring-config-dao.xml] وملف JPA [persistence.xml]:
![]() |
السؤال: اكتب محتويات هذين الملفين. سنفترض أن قاعدة البيانات المستخدمة هي قاعدة بيانات MySQL5 [dbpam_hibernate] التي تم إنشاؤها بواسطة البرنامج النصي SQL [dbpam_hibernate.sql]. سيحدد ملف Spring الفئات الثلاثة التالية: employeDao من النوع EmployeDao، و indemniteDao من النوع IndemniteDao، و cotisationDao من النوع CotisationDao. علاوة على ذلك، سيكون تنفيذ JPA المستخدم هو Hibernate.
5.9.3. الاختبارات
قراءة موصى بها: الأقسام 3.1.6 و 3.1.7 من [ref1]
الآن بعد أن تمت كتابة طبقة [DAO] وتهيئتها، يمكننا اختبارها. ستكون بنية الاختبار كما يلي:
![]() |
5.9.4. InitDB
سنقوم بإنشاء برنامجين اختباريين لطبقة [DAO]. سيتم وضعهما في الحزمة [dao] [2] التابعة لفرع [Test Packages] [1] في مشروع NetBeans. لا يتم تضمين هذا الفرع في المشروع الذي يتم إنشاؤه بواسطة خيار [Build project]، مما يضمن عدم تضمين البرامج الاختبارية التي نضعها هناك في ملف .jar النهائي للمشروع.
![]() |
تتمتع الفئات الموضوعة في فرع [Test Packages] بإمكانية الوصول إلى الفئات الموجودة في فرع [Source Packages] وكذلك إلى مكتبات فئات المشروع. إذا كانت الاختبارات تتطلب مكتبات بخلاف تلك الموجودة في المشروع، فيجب الإعلان عنها في فرع [Test Libraries] [2].
تستخدم فئات الاختبار أداة اختبار الوحدات JUnit:
- لا يقوم [JUnitInitDB] بإجراء أي اختبارات. بل يقوم بتعبئة قاعدة البيانات ببعض السجلات ثم يعرضها على وحدة التحكم.
- [JUnitDao] يقوم بإجراء سلسلة من الاختبارات والتحقق من نتائجها.
هيكل فئة [JUnitInitDB] هو كما يلي:
- يتم تنفيذ طريقة [init] قبل بدء مجموعة الاختبارات (التعليق التوضيحي @BeforeClass). وهي تقوم بإنشاء مثيل لطبقة [DAO].
- يتم تنفيذ الطريقة [clean] قبل كل اختبار (التعليق التوضيحي @Before). وهي تقوم بمسح قاعدة البيانات.
- طريقة [initDB] هي اختبار (التعليق التوضيحي @Test). وهي الطريقة الوحيدة. يجب أن يحتوي الاختبار على عبارات تأكيد Assert.assertCondition. هنا، لن يكون هناك أي منها. وبالتالي، فإن الطريقة هي اختبار وهمي. والغرض منها هو ملء قاعدة البيانات ببضعة صفوف ثم عرض محتويات قاعدة البيانات على وحدة التحكم. تُستخدم هنا طريقتا create و findAll من طبقات [DAO].
السؤال: أكمل الكود الخاص بفئة [JUnitInitDB]. استخدم المثال الوارد في القسم 3.1.6 من [ref1] كدليل. سيُنتج الكود الناتج الموضح في القسم 5.1.
5.9.5. تنفيذ الاختبار
نحن الآن جاهزون لتشغيل [InitDB]. نصف الإجراء باستخدام نظام إدارة قواعد البيانات MySQL5:
![]() |
- تم إعداد الفئات [1] وملفات التكوين [2] وفئات الاختبار الخاصة بطبقة [DAO] [3]،
![]() |
- يتم إنشاء المشروع [4]
- يتم تنفيذ فئة [JUnitInitDB] [5]. يتم تشغيل نظام إدارة قواعد البيانات MySQL5 مع قاعدة بيانات [dbpam_hibernate] موجودة،
- تشير نافذة [نتائج الاختبار] [6] إلى نجاح الاختبارات. هذه الرسالة ليست مهمة هنا، حيث لا يحتوي برنامج [JUnitInitDB] على أي عبارات تأكيد من نوع Assert.assertCondition قد تتسبب في فشل الاختبار. ومع ذلك، فهي توضح عدم حدوث أي استثناءات أثناء تنفيذ الاختبار.
تحتوي نافذة [Output] على سجلات التنفيذ، بما في ذلك تلك الخاصة بـ Spring والاختبار نفسه. الإخراج الذي تم إنشاؤه بواسطة فئة [JUnitInitDB] هو كما يلي:
تم ملء الجداول [EMPLOYEES، ALLOWANCES، CONTRIBUTIONS]. يمكن التحقق من ذلك عن طريق توصيل NetBeans بقاعدة البيانات [dbpam_hibernate].
![]() |
- في [1]، في علامة التبويب [services]، يمكنك عرض البيانات من جدول [employees] الخاص بالاتصال [dbpam_hibernate] [2]،
- في [3] النتيجة.
5.9.6. JUnitD ao
سنلقي الآن نظرة على فئة اختبار ثانية [JUnitDao]:
![]() |
سيكون الهيكل الأساسي للفئة كما يلي:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
في فئة الاختبار السابقة، يتم مسح قاعدة البيانات قبل كل اختبار.
السؤال: اكتب الطرق التالية:
1 - test02: استنادًا إلى test01
2 - test03: لدى الموظف حقل من النوع Indemnity. لذلك، قم بإنشاء كيان Indemnity وكيان Employee
3 - test04.
بالمضي بنفس الطريقة المتبعة في فئة الاختبار [JUnitInitDB]، نحصل على النتائج التالية:
![]() |
- في [1]، نقوم بتشغيل فئة الاختبار
- في [2]، تظهر نتائج الاختبار في نافذة [نتائج الاختبار]
دعونا نُحدث خطأً لنرى كيف يتم الإبلاغ عنه في صفحة النتائج:
السطر 13: سيؤدي هذا الافتراض إلى حدوث خطأ، لأن قيمة Csgrds تساوي 3.49 (السطر 8). ويؤدي تشغيل فئة الاختبار إلى النتائج التالية:
![]() |
- تُظهر صفحة النتائج [1] الآن أن بعض الاختبارات قد فشلت.
- في [2]، ملخص للاستثناء الذي تسبب في فشل الاختبار. يتضمن رقم السطر في كود Java الذي حدث فيه الاستثناء.
5.10. طبقة [الأعمال] لتطبيق [PAM]
الآن بعد أن تمت كتابة طبقة [DAO]، ننتقل إلى دراسة طبقة الأعمال [2]:
![]() |
5.10.1. واجهة Java [IMetier]
وقد تم وصفها في القسم 5.7. ونذكرها أدناه:
سيتم تنفيذ طبقة [الأعمال] في حزمة [الأعمال]:
![]() |
ستتضمن حزمة [الأعمال]، بالإضافة إلى واجهة [IMetier] وتنفيذها [Metier]، فئتين أخريين: [Payroll] و[PayrollItems]. تم تقديم فئة [Payroll] بإيجاز في القسم 5.7. سنعود إليها الآن.
5.10.2. فئة [Payroll]
تُرجع طريقة [calculatePayStub] الخاصة بواجهة [IMetier] كائنًا من نوع [PayStub] يمثل العناصر المختلفة لكشوفة الراتب. وفيما يلي تعريفها:
- السطر 7: تنفذ الفئة واجهة Serializable لأن مثيلاتها قد يتم تبادلها عبر الشبكة.
- السطر 9: الموظف المشمول في كشف الراتب
- السطر 10: معدلات الاشتراكات المختلفة
- السطر 11: المخصصات المختلفة المرتبطة بمؤشر الموظف
- السطر 12: مكونات راتبهم
- الأسطر 14-22: منشئا الفئة
- الأسطر 25-27: طريقة [toString] التي تحدد كائن [PayStub] معين
- السطر 29 وما بعده: أدوات الوصول العامة إلى الحقول الخاصة بالفئة
تحتوي فئة [ElementsSalaire] المشار إليها في السطر 11 من فئة [FeuilleSalaire] أعلاه على العناصر التي تشكل كشف الراتب. وتعريفها كما يلي:
- السطر 3: تنفذ الفئة واجهة Serializable لأنها مكون من فئة PayrollClass، التي يجب أن تكون قابلة للتسلسل.
- السطر 6: الراتب الأساسي
- السطر 7: اشتراكات الضمان الاجتماعي المدفوعة على هذا الراتب الأساسي
- السطر 8: مدفوعات إعالة الأطفال اليومية
- السطر 9: بدلات وجبات الأطفال اليومية
- السطر 10: الراتب الصافي الذي سيُدفع لمقدم رعاية الأطفال
- السطور 12-24: منشئات الفئات
- الأسطر 27-31: طريقة [toString] التي تحدد كائن [ElementsSalaire] معين
- السطر 34 وما بعده: أدوات الوصول العامة إلى الحقول الخاصة بالفئة
5.10.3. فئة التنفيذ [Metier] للطبقة [business]
يمكن أن تكون فئة التنفيذ [Metier] للطبقة [business] كما يلي:
- السطر 5: تضمن علامة Spring @Transactional أن كل طريقة في الفئة تعمل ضمن معاملة.
- السطران 9-10: إشارات إلى طبقات [DAO] لكيانات [Cotisation، Employe، Indemnite]
- الأسطر 14–17: طريقة [calculatePayroll]
- الأسطر 20–22: طريقة [findAllEmployees]
- السطر 24 وما بعده: أدوات الوصول العامة للحقول الخاصة بالفئة
السؤال: اكتب كود طريقة [findAllEmployees].
السؤال: اكتب كود الأسلوب [calculatePayroll].
لاحظ النقاط التالية:
- تم شرح طريقة حساب الراتب في القسم 5.2.
- إذا لم تتطابق المعلمة [SS] مع أي موظف (أرجعت طبقة [DAO] مؤشرًا فارغًا)، فستقوم الطريقة بإلقاء استثناء [PamException] مع رمز خطأ مناسب.
5.10.4. اختبار طبقة [business]
نقوم بإنشاء برنامجين للاختبار:
![]() |
يتم إنشاء فئات الاختبار [3] في حزمة [metier] [2] ضمن فرع [Test Packages] [1] للمشروع.
قد تبدو فئة [JUnitMetier_1] كما يلي:
لا توجد أي تأكيدات Assert.assertCondition في الفئة. نحن نحاول ببساطة حساب بعض الرواتب حتى نتمكن بعد ذلك من التحقق منها يدويًا. فيما يلي إخراج الشاشة الناتج عن تشغيل الفئة السابقة:
- السطر 4: كشف راتب جوستين لافيرتي
- السطر 5: كشف راتب ماري جوفينال
- السطر 6: الاستثناء الناتج عن عدم وجود موظف برقم الضمان الاجتماعي "xx".
السؤال: يستخدم السطر 17 من [JUnitMetier_1] حبة Spring المسماة metier. قدم تعريف هذه الحبة في الملف [spring-config-metier-dao.xml].
يمكن أن تكون الفئة [JUnitMetier_2] كما يلي:
تُعد فئة [JUnitMetier_2] نسخة من فئة [JUnitMetier_1]، باستثناء أن عمليات التحقق قد أُدرجت هذه المرة في الدالة test01.
السؤال: اكتب طريقة test01.
عند تنفيذ الفئة [JUnitMetier_2]، يتم الحصول على النتائج التالية إذا سارت الأمور على ما يرام:

5.11. طبقة [ui] لتطبيق [PAM] – إصدار وحدة التحكم
الآن بعد أن تمت كتابة طبقة [business]، لا يزال يتعين علينا كتابة طبقة [ui] [1]:
![]() |
سنقوم بإنشاء نسختين مختلفتين من طبقة [ui]: نسخة تعمل على وحدة التحكم ونسخة تعمل على واجهة المستخدم الرسومية Swing:
![]() |
5.11.1. فئة [ ui.console.Main]
سنركز أولاً على تطبيق وحدة التحكم الذي تم تنفيذه بواسطة فئة [ui.console.Main] أعلاه. وقد تم وصف طريقة عملها في القسم 5.3. يمكن أن يكون الهيكل الأساسي لفئة [Main] كما يلي:
السؤال: أكمل الكود أعلاه.
5.11.2. التنفيذ
لتشغيل فئة [ui.console.Main]، اتبع الخطوات التالية:
![]() |
- في [1]، حدد خصائص المشروع،
- في [2]، حدد خاصية [Run] للمشروع،
- استخدم الزر [3] لتحديد الفئة (المعروفة باسم الفئة الرئيسية) المراد تشغيلها،
- حدد الفئة [4]،
- تظهر الفئة في [5]. تتطلب هذه الفئة ثلاث معلمات للتشغيل (رقم الضمان الاجتماعي، عدد ساعات العمل، عدد أيام العمل). يتم إدخال هذه المعلمات في [6]،
- وبمجرد الانتهاء من ذلك، يمكن تنفيذ المشروع [7]. يعني التكوين السابق أن فئة [ui.console.Main] سيتم تنفيذها.
يتم عرض نتائج التنفيذ في نافذة [output]:
![]() | ![]() |
5.12. طبقة [ui] لتطبيق [PAM] – النسخة الرسومية
سنقوم الآن بتنفيذ طبقة [ui] بواجهة مستخدم رسومية:
![]() |
![]() |
- في [1]، فئة [PamJFrame] للواجهة الرسومية
- في [2]: واجهة المستخدم الرسومية
5.12.1. دليل تعليمي سريع
لإنشاء واجهة المستخدم الرسومية، اتبع الخطوات التالية:
![]() |
- [1]: أنشئ ملفًا جديدًا باستخدام الزر [1] [ملف جديد...]
- [2]: حدد فئة الملف [Swing GUI Forms]، أي النماذج الرسومية
- [3]: حدد النوع [JFrame Form]، وهو نوع نموذج فارغ
![]() |
- [5]: قم بتسمية النموذج؛ وسيكون هذا أيضًا اسم الفئة
- [6]: ضع النموذج في حزمة
- [8]: تمت إضافة النموذج إلى شجرة المشروع
- [9]: يمكن الوصول إلى النموذج عبر عرضين: [التصميم] [9]، الذي يسمح لك بتصميم المكونات المختلفة للنموذج، و[المصدر] [10 أدناه]، الذي يوفر الوصول إلى كود Java الخاص بالنموذج. في النهاية، النموذج هو فئة Java مثل أي فئة أخرى. طريقة العرض [التصميم] هي أداة لتصميم النموذج. في كل مرة تتم فيها إضافة مكون في وضع [التصميم]، يتم إضافة كود Java في طريقة العرض [المصدر] لتعويض ذلك.
![]() |
- [11]: يمكن العثور على قائمة مكونات Swing المتاحة للنموذج في نافذة [Palette].
- [12]: تعرض نافذة [Inspector] الهيكل الشجري لمكونات النموذج. توجد المكونات ذات التمثيل المرئي في فرع [JFrame]؛ أما المكونات الأخرى فتوجد في فرع [Other Components].
![]() |
- في [13]، نختار مكون [JLabel] بنقرة واحدة
- في [14]، نقوم بإفلاته على النموذج في وضع [Design]
- في [15]، نحدد خصائص JLabel (النص، الخط).
![]() |
- في [16]، النتيجة.
- في [17]، نطلب معاينة للنموذج
- في [18]، النتيجة
- في [19]، تمت إضافة التسمية [JLabel1] إلى شجرة المكونات في نافذة [Inspector]
![]() |
- في [20] و[21]: في عرض [Source] للنموذج، تمت إضافة كود Java للتعامل مع JLabel المضافة.
يتوفر دليل تعليمي حول إنشاء النماذج باستخدام NetBeans على الرابط [http://www.netbeans.org/kb/trails/matisse.html].
5.12.2. واجهة المستخدم الرسومية [PamJFrame]
سنقوم بإنشاء واجهة المستخدم الرسومية التالية:
![]() |
- في [1]، واجهة المستخدم الرسومية
- في [2]، هيكل الشجرة لمكوناتها: عنصر JLabel وستة حاويات JPanel
JLabel1
![]() |
JPanel1
![]() | ![]() |
JPanel2
![]() | ![]() |
JPanel3
![]() | ![]() |
JPanel4
![]() | ![]() |
JPanel5
![]() | ![]() |
تمرين عملي: قم بإنشاء الواجهة الرسومية السابقة باستخدام البرنامج التعليمي [http://www.netbeans.org/kb/trails/matisse.html].
5.12.3. أحداث واجهة المستخدم الرسومية
قراءة موصى بها: الفصل [واجهات المستخدم الرسومية] في [ref2].
سنقوم بمعالجة النقر على زر [jButtonSalaire]. لإنشاء الطريقة التي تعالج هذا الحدث، يمكننا اتباع الخطوات التالية:
![]() |
يتم إنشاء معالج النقر على زر [JButtonSalaire]:
يتم أيضًا إنشاء كود Java الذي يربط الطريقة السابقة بالنقر على زر [JButtonSalaire]:
تحدد الأسطر 2-5 أن النقر (evt من النوع ActionPerformed) على الزر [jButtonSalaire] (السطر 2) يجب أن تتم معالجته بواسطة الطريقة [jButtonSalaireActionPerformed] (السطر 4).
سنقوم أيضًا بمعالجة حدث [caretUpdate] (حركة المؤشر) في حقل الإدخال [jTextFieldHT]. لإنشاء معالج لهذا الحدث، نتبع نفس الخطوات السابقة:
![]() |
يتم إنشاء معالج الحدث [caretUpdate] في حقل الإدخال [jTextFieldHT]:
يتم أيضًا إنشاء كود Java الذي يربط الطريقة السابقة بحدث [caretUpdate] في حقل النص [jTextFieldHT]:
تشير الأسطر 1-4 إلى أن الحدث [caretUpdate] (السطر 2) على الزر [jTextFieldHT] (السطر 1) يجب أن تتم معالجته بواسطة الطريقة [jTextFieldHTCaretUpdate] (السطر 3).
5.12.4. تهيئة واجهة المستخدم الرسومية
لنعد إلى بنية تطبيقنا:
![]() |
تحتاج طبقة [ui] إلى مرجع إلى طبقة [business]. دعونا نتذكر كيف تم الحصول على هذا المرجع في تطبيق وحدة التحكم:
الطريقة هي نفسها في تطبيق واجهة المستخدم الرسومية. عند تهيئة تطبيق واجهة المستخدم الرسومية، يجب أيضًا تهيئة مرجع [IMetier metier] من السطر 3 أعلاه. الرمز الذي تم إنشاؤه لواجهة المستخدم الرسومية هو حاليًا كما يلي:
- الأسطر 29–35: الطريقة الثابتة [main] التي تطلق التطبيق
- السطر 32: يتم إنشاء مثيل لواجهة المستخدم الرسومية [PamJFrame] وإظهاره.
- الأسطر 7-9: منشئ واجهة المستخدم الرسومية.
- السطر 8: استدعاء الطريقة [initComponents] المحددة في السطر 17. يتم إنشاء هذه الطريقة تلقائيًا بناءً على العمل المنجز في وضع [Design]. لا تقم بتعديلها.
- السطر 21: الطريقة التي ستتولى تحريك مؤشر الإدخال في حقل [jTextFieldHT]
- السطر 25: الطريقة التي ستتولى معالجة النقر على زر [jButtonSalaire]
لإضافة التهيئات الخاصة بنا إلى الكود السابق، يمكننا المتابعة على النحو التالي:
- السطر 4: نستدعي طريقة مخصصة لتنفيذ عمليات التهيئة الخاصة بنا. يتم تعريف هذه العمليات بواسطة الكود الموجود في الأسطر 10–42
السؤال: باستخدام التعليقات كدليل، أكمل الكود الخاص بإجراء [doMyInit].
5.12.5. معالجات الأحداث
السؤال: اكتب الأسلوب [jTextFieldHTCaretUpdate]. يجب أن يضمن هذا الأسلوب أنه إذا لم تكن البيانات الموجودة في الحقل [jTextFieldHT] عددًا حقيقيًا >=0، فيجب تعطيل الزر [jButtonSalaire].
السؤال: اكتب الأسلوب [jButtonSalaireActionPerformed]، الذي يجب أن يعرض كشف الراتب للموظف المحدد في [jComboBoxEmployes].
5.12.6. تشغيل واجهة المستخدم الرسومية
لتشغيل واجهة المستخدم الرسومية، قم بتعديل تكوين [Run] للمشروع:
![]() |
- في [1]، أدخل فئة واجهة المستخدم الرسومية
يجب أن يكون المشروع كاملاً مع ملفات التكوين الخاصة به (persistence.xml، spring-config-metier-dao.xml) وفئة واجهة المستخدم الرسومية. قم بتشغيل نظام إدارة قواعد البيانات المستهدف قبل تشغيل المشروع.
5.13. تنفيذ طبقة JPA باستخدام EclipseLink
نحن مهتمون بالبنية التالية حيث يتم الآن تنفيذ طبقة JPA بواسطة EclipseLink:
![]() |
5.13.1. مشروع NetBeans
يتم إنشاء مشروع NetBeans الجديد عن طريق نسخ المشروع السابق:
![]() |
- في [1]: بعد النقر بزر الماوس الأيمن على مشروع Hibernate، حدد "نسخ"
- باستخدام الزر [2]، حدد المجلد الأصلي للمشروع الجديد. يظهر اسم المجلد في [3].
- في [4]، قم بتسمية المشروع الجديد
- في [5]، اسم مجلد المشروع
![]() |
- في [1]، تم إنشاء المشروع الجديد. يحمل نفس اسم المشروع الأصلي،
- في [2] و[3]، قم بتغيير اسمه إلى [mv-pam-spring-eclipselink].
يجب تعديل المشروع في موضعين لتكييفه مع طبقة JPA / EclipseLink الجديدة:
- في [4]، يجب تعديل ملفات تكوين Spring. هذا هو المكان الذي توجد فيه تكوينات طبقة JPA.
- في [5]، يجب تعديل مكتبات المشروع: يجب استبدال مكتبات Hibernate بمكتبات EclipseLink.
لنبدأ بالنقطة الأخيرة. سيكون ملف [pom.xml] للمشروع الجديد كما يلي:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-pam-spring-eclipselink</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-spring-eclipselink</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>swing-layout</id>
<layout>default</layout>
<name>Repository for library Library[swing-layout]</name>
</repository>
<repository>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
<id>eclipselink</id>
<layout>default</layout>
<name>Repository for library Library[eclipselink]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
</project>
- الأسطر 73–82: التبعيات لتنفيذ EclipseLink JPA،
- الأسطر 19–24: مستودع Maven لـ EclipseLink.
يجب تعديل ملفات تكوين Spring للإشارة إلى أن تطبيق JPA قد تغير. في كلا الملفين، يتغير فقط القسم الذي يهيئ طبقة JPA. على سبيل المثال، في [spring-config-metier-dao.xml] لدينا:
تقوم الأسطر 19–36 بتكوين طبقة JPA. يتم استخدام Hibernate لتنفيذ JPA (السطر 22). بالإضافة إلى ذلك، فإن قاعدة البيانات المستهدفة هي [dbpam_hibernate] (السطر 41).
للتبديل إلى تطبيق JPA/EclipseLink، يتم استبدال الأسطر 19–35 أعلاه بالأسطر التالية:
- السطر 5: تطبيق JPA المستخدم هو EclipseLink
- السطر 9: تحدد الخاصية databasePlatform نظام إدارة قواعد البيانات المستهدف، وهو في هذه الحالة MySQL
- السطر 11: لإنشاء جداول قاعدة البيانات عند إنشاء مثيل لطبقة JPA. هنا، تم تعليق الخاصية.
- السطر 7: لعرض عبارات SQL الصادرة عن طبقة JPA على وحدة التحكم. هنا، تم تعليق الخاصية.
بالإضافة إلى ذلك، تصبح قاعدة البيانات المستهدفة [dbpam_eclipselink] (السطر 4 أدناه):
5.13.2. تشغيل الاختبارات
قبل اختبار التطبيق بأكمله، يُنصح بالتحقق من نجاح اختبارات JUnit مع تطبيق JPA الجديد. قبل تشغيلها، سنبدأ بحذف الجداول من قاعدة البيانات. للقيام بذلك، في علامة التبويب [Runtime] في NetBeans، قم بإنشاء اتصال بقاعدة البيانات dbpam_eclipselink / MySQL5، إذا لزم الأمر. بمجرد الاتصال بقاعدة البيانات dbpam_eclipselink / MySQL5، يمكنك المضي قدمًا في حذف الجداول كما هو موضح أدناه:
- [1]: قبل الحذف
- [2]: بعد الحذف
![]() |
بمجرد الانتهاء من ذلك، يمكنك تشغيل الاختبار الأول على طبقة [DAO]: InitDB، الذي يقوم بتعبئة قاعدة البيانات. للتأكد من أن الجداول التي تم حذفها مسبقًا يتم إعادة إنشاؤها بواسطة التطبيق، تأكد من أن السطر التالي موجود في تكوين Spring JPA / EclipseLink:
موجودًا وغير معلق عليه.
نقوم ببناء المشروع ثم نُشغّل اختبار [JUnit InitDB]:
![]() |
- في [1]، يتم تشغيل اختبار InitDB.
- في [2]، يفشل الاختبار. يتم إلقاء الاستثناء بواسطة Spring وليس بواسطة الاختبار الذي فشل.
سبب الخطأ: org.springframework.beans.factory.BeanCreationException: خطأ في إنشاء bean باسم 'entityManagerFactory' المحدد في مورد مسار الفئة [spring-config-DAO.xml]: فشل استدعاء طريقة init؛ الاستثناء المتداخل هو java.lang.IllegalStateException: يجب بدء التشغيل باستخدام وكيل Java لاستخدام InstrumentationLoadTimeWeaver. انظر وثائق Spring.
يشير Spring إلى وجود مشكلة في التكوين. الرسالة غير واضحة. تم شرح سبب الاستثناء في القسم 3.1.9 من [ref1]. لكي يعمل تكوين Spring/EclipseLink، يجب تشغيل JVM الذي يقوم بتشغيل التطبيق باستخدام معلمة محددة، وهي وكيل Java. تنسيق هذه المعلمة هو كما يلي:
[spring-agent.jar] هو وكيل Java المطلوب من قبل JVM لإدارة تكوين Spring/EclipseLink.
عند تشغيل مشروع، يمكن تمرير معلمات إلى JVM:
![]() |
- في [1]، يمكنك الوصول إلى خصائص المشروع
- في [2]، خصائص التشغيل
- في [3]، قم بتمرير المعلمة -javaagent إلى JVM
5.13.3. InitDB
الآن نحن جاهزون لاختبار [InitDB] مرة أخرى. هذه المرة، كانت النتائج كما يلي:
![]() |
- في [1]، نجح الاختبار
- في [2]، في علامة التبويب [Services]، نقوم بتحديث اتصال NetBeans بقاعدة البيانات [dbpam_eclipselink]
- في [3]، تم إنشاء أربعة جداول
![]() |
- في [5]، نعرض محتويات جدول [employees]
- في [6]، النتيجة.
5.13.4. JUnitDao
قد يفشل تنفيذ فئة الاختبار [JUnitDao]، على الرغم من نجاحها مع تطبيق JPA/Hibernate. لفهم السبب، دعونا نحلل مثالاً.
الطريقة التي يتم اختبارها هي طريقة IndemniteDao.create التالية:
- الأسطر 15–22: الطريقة قيد الاختبار
طريقة الاختبار هي كما يلي:
package dao;
...
public class JUnitDao {
// layers DAO
static private IEmployeDao employeDao;
static private IIndemniteDao indemniteDao;
static private ICotisationDao cotisationDao;
@BeforeClass
public static void init() {
// log
log("init");
// application configuration
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
// layers DAO
employeDao = (IEmployeDao) ctx.getBean("employeDao");
indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
}
@Before()
public void clean() {
// empty the base
for (Employe employe : employeDao.findAll()) {
employeDao.destroy(employe);
}
for (Cotisation cotisation : cotisationDao.findAll()) {
cotisationDao.destroy(cotisation);
}
for (Indemnite indemnite : indemniteDao.findAll()) {
indemniteDao.destroy(indemnite);
}
}
// logs
private static void log(String message) {
System.out.println("----------- " + message);
}
// tests
….
@Test
public void test05() {
log("test05");
// we create two allowances with the same index
// violates index uniqueness constraint
boolean erreur = true;
Indemnite indemnite1 = null;
Indemnite indemnite2 = null;
Throwable th = null;
try {
indemnite1 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
indemnite2 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
erreur = false;
} catch (PamException ex) {
th = ex;
// checks
Assert.assertEquals(31, ex.getCode());
} catch (Throwable th1) {
th = th1;
}
// checks
Assert.assertTrue(erreur);
// exception chain
System.out.println("Chaîne des exceptions --------------------------------------");
System.out.println(th.getClass().getName());
while (th.getCause() != null) {
th = th.getCause();
System.out.println(th.getClass().getName());
}
// the 1st allowance had to be continued
Indemnite indemnite = indemniteDao.find(indemnite1.getId());
// check
Assert.assertNotNull(indemnite);
Assert.assertEquals(1, indemnite.getIndice());
Assert.assertEquals(1.93, indemnite.getBaseHeure(), 1e-6);
Assert.assertEquals(2, indemnite.getEntretienJour(), 1e-6);
Assert.assertEquals(3, indemnite.getRepasJour(), 1e-6);
Assert.assertEquals(12, indemnite.getIndemnitesCP(), 1e-6);
// the second indemnity should not have persisted
List<Indemnite> indemnites = indemniteDao.findAll();
int nbIndemnites = indemnites.size();
Assert.assertEquals(nbIndemnites, 1);
}
...
}
السؤال: اشرح وظيفة الاختبار test05 ووضح النتائج المتوقعة.
النتائج التي تم الحصول عليها باستخدام طبقة JPA/Hibernate هي كما يلي:
اجتاز الاختبار، مما يعني أن التأكيدات تم التحقق منها ولم يتم إلقاء أي استثناء من طريقة الاختبار.
السؤال: اشرح ما حدث.
النتائج التي تم الحصول عليها باستخدام طبقة JPA/EclipseLink هي كما يلي:
كما هو الحال مع Hibernate سابقًا، ينجح الاختبار، مما يعني أن التأكيدات تم التحقق منها ولم يتم إلقاء أي استثناء من طريقة الاختبار.
السؤال: اشرح ما حدث.
سؤال: من هذين المثالين، ما الذي يمكننا استنتاجه بشأن قابلية تبادل تطبيقات JPA؟ هل الأمر كامل هنا؟
5.13.5. الاختبارات الأخرى
بمجرد اختبار طبقة [DAO] واعتبارها صحيحة، يمكننا الانتقال إلى اختبار طبقة [الأعمال] والمشروع نفسه في نسخته النصية أو الرسومية. لا يؤثر تغيير تطبيق JPA على طبقات [الأعمال] و[واجهة المستخدم]؛ لذلك، إذا كانت هذه الطبقات تعمل مع Hibernate، فستعمل مع EclipseLink مع بعض الاستثناءات: يوضح المثال السابق أن الاستثناءات التي تطلقها طبقات [DAO] قد تختلف. وبالتالي، في حالة الاختبار، يرمي Spring / JPA / Hibernate استثناء [PamException]، وهو استثناء خاص بتطبيق [pam]، في حين يرمي Spring / JPA / EclipseLink استثناء [TransactionSystemException]، وهو استثناء من إطار عمل Spring. إذا كانت طبقة [ui]، في حالة الاختبار، تتوقع استثناء [PamException] لأنها بُنيت باستخدام Hibernate، فلن تعمل بعد ذلك عند التبديل إلى EclipseLink.
5.13.6. العمل المطلوب
مهمة عملية: إعادة اختبار تطبيقات وحدة التحكم و Swing باستخدام أنظمة إدارة قواعد البيانات المختلفة: MySQL5 و Oracle XE و SQL Server.





















































































