Skip to content

4. خدمة الويب J2EE الخاصة بالمواعيد

لنعد إلى بنية التطبيق المراد إنشاؤه:

نركز في هذا الجزء على إنشاء خدمة الويب J2EE [1] التي يتم تشغيلها على خادم Sun / Glassfish.

4.1. قاعدة البيانات

قاعدة البيانات التي سنسميها [dbrdvmedecins] هي قاعدة بيانات MySQL5 تحتوي على أربع جداول:

Image

4.1.1. الجدول [MEDECINS]

تحتوي على معلومات عن الأطباء الذين يديرهم التطبيق [RdvMedecins].

  • ID: الرقم الذي يحدد هوية الطبيب - المفتاح الأساسي للجدول
  • VERSION: الرقم الذي يحدد إصدار السطر في الجدول. يتم زيادة هذا الرقم بمقدار 1 في كل مرة يتم فيها إجراء تعديل على السطر.
  • NOM: اسم الطبيب
  • PRENOM: اسمه الأول
  • TITRE: لقبه (الآنسة، السيدة، السيد)

4.1.2. الجدول [CLIENTS]

يتم تسجيل مرضى الأطباء المختلفين في الجدول [CLIENTS]:

  • ID: الرقم التعريفي للعميل - المفتاح الأساسي للجدول
  • VERSION: الرقم الذي يحدد إصدار السطر في الجدول. يتم زيادة هذا الرقم بمقدار 1 في كل مرة يتم فيها إجراء تعديل على السطر.
  • NOM: اسم العميل
  • PRENOM: اسمه الأول
  • TITRE: لقبه (الآنسة، السيدة، السيد)

4.1.3. الجدول [CRENEAUX]

تسرد هذه الجدول الفترات الزمنية التي يمكن فيها استخدام RV:

  • ID: الرقم الذي يحدد الفترة الزمنية - المفتاح الأساسي للجدول (السطر 8)
  • VERSION: الرقم الذي يحدد إصدار السطر في الجدول. يتم زيادة هذا الرقم بمقدار 1 في كل مرة يتم فيها إجراء تعديل على السطر.
  • ID_MEDECIN: الرقم الذي يحدد الطبيب الذي ينتمي إليه هذا الموعد – مفتاح خارجي في العمود MEDECINS (ID).
  • HDEBUT: وقت بدء الفترة الزمنية
  • MDEBUT: الدقائق الأولى للفترة
  • HFIN: ساعة انتهاء الفترة الزمنية
  • MFIN: الدقائق التي تنتهي فيها الفترة الزمنية

يشير السطر الثاني من الجدول [CRENEAUX] (انظر [1] أعلاه)، على سبيل المثال، إلى أن الفترة رقم 2 تبدأ في الساعة 8:20 وتنتهي في الساعة 8:40، وتخص الطبيبة رقم 1 (السيدة ماري PELISSIER).

4.1.4. الجدول [RV]

تسرد الجدول RV المواعيد المحددة لكل طبيب:

  • ID: الرقم الذي يحدد RV بشكل فريد – المفتاح الأساسي
  • JOUR: يوم RV
  • ID_CRENEAU: الفترة الزمنية لـ RV – مفتاح خارجي في الحقل [ID] من الجدول [CRENEAUX] – يحدد في آن واحد الفترة الزمنية والطبيب المعني.
  • ID_CLIENT: رقم العميل الذي تم الحجز لصالحه – مفتاح خارجي في الحقل [ID] في الجدول [CLIENTS]

يحتوي هذا الجدول على قيد « » يضمن تفرد قيم الأعمدة المرتبطة (JOUR، ID_CRENEAU):

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

إذا كان أحد صفوف الجدول [RV] يحتوي على القيمة (JOUR1، ID_CRENEAU1) للأعمدة (JOUR، ID_CRENEAU)، فلا يمكن أن تظهر هذه القيمة في أي مكان آخر. وإلا، فهذا يعني أنه تم تسجيل قيمتين لـ RV في نفس الوقت لنفس الطبيب. من منظور برمجة Java، يقوم برنامج التشغيل JDBC الخاص بقاعدة البيانات بتشغيل برنامج SQLException عند حدوث هذه الحالة.

السطر الذي يحتوي على القيمة 3 لـ id (انظر [1] أعلاه) يعني أنه تم حجز موعد RV للفترة رقم 20 والعميل رقم 4 في 23/08/2006. يُظهر الجدول [CRENEAUX] أن الموعد رقم 20 يتوافق مع الفترة الزمنية من الساعة 16:20 إلى 16:40، وهو مخصص للطبيبة رقم 1 (السيدة ماري PELISSIER). توضح لنا الجدولة [CLIENTS] أن العميل رقم 4 هي الآنسة بريجيت BISTROU.

4.2. إنشاء قاعدة البيانات

قم بإنشاء قاعدة البيانات MySql [dbrdvmedecins] باستخدام الأداة التي تختارها. لإنشاء الجداول وتعبئتها، يمكنك استخدام البرنامج النصي [createbd.sql] الذي سيتم تزويدك به. ومحتواه كما يلي:

create table CLIENTS (
        ID bigint not null auto_increment,
        VERSION integer not null,
        TITRE varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(30) not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    create table CRENEAUX (
        ID bigint not null auto_increment,
        VERSION integer not null,
        HDEBUT integer not null,
        MDEBUT integer not null,
        HFIN integer not null,
        MFIN integer not null,
        ID_MEDECIN bigint not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    create table MEDECINS (
        ID bigint not null auto_increment,
        VERSION integer not null,
        TITRE varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(30) not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    create table RV (
        ID bigint not null auto_increment,
        JOUR date not null,
        ID_CLIENT bigint not null,
        ID_CRENEAU bigint not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    alter table CRENEAUX 
        add index FK9BD7A197FE16862 (ID_MEDECIN), 
        add constraint FK9BD7A197FE16862 
        foreign key (ID_MEDECIN) 
        references MEDECINS (ID);

    alter table RV 
        add index FKA4494D97AD2 (ID_CLIENT), 
        add constraint FKA4494D97AD2 
        foreign key (ID_CLIENT) 
        references CLIENTS (ID);

    alter table RV 
        add index FKA441A673246 (ID_CRENEAU), 
        add constraint FKA441A673246 
        foreign key (ID_CRENEAU) 
        references CRENEAUX (ID);

INSERT INTO CLIENTS ( VERSION, NOM, PRENOM, TITRE) VALUES (1, 'MARTIN', 'Jules', 'Mr');
...

INSERT INTO MEDECINS ( VERSION, NOM, PRENOM, TITRE) VALUES (1, 'PELISSIER', 'Marie', 'Mme');
...

INSERT INTO CRENEAUX ( VERSION, ID_MEDECIN, HDEBUT, MDEBUT, HFIN, MFIN) VALUES (1, 1, 8, 0, 8, 20);
...

INSERT INTO RV ( JOUR, ID_CRENEAU, ID_CLIENT) VALUES ('2006-08-22', 1, 2);
...

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

COMMIT WORK;

4.3. عناصر بنية جانب الخادم

لنعد إلى بنية التطبيق المراد إنشاؤه:

على جانب الخادم، سيتكون التطبيق من:

  1. من طبقة JPA تتيح العمل مع BD عن طريق كائنات
  1. وEJB مكلفة بإدارة العمليات مع طبقة JPA
  2. خدمة ويب مسؤولة عن عرض واجهة EJB للعملاء البعيدين في شكل خدمة ويب.

العنصران (ب) و(ج) ينفذان طبقة [dao] الموضحة في المخطط السابق. ومن المعروف أن التطبيق يمكنه الوصول إلى كائن EJB بعيد عبر بروتوكولي RMI وJNDI. في الواقع العملي، يقتصر ذلك على عملاء Java. تستخدم خدمة الويب بروتوكول اتصال موحدًا تنفذه لغات برمجة متنوعة: .NET، وPHP، وC++، ... وهذا ما نريد إظهاره هنا باستخدام عميل .NET.

للحصول على مقدمة موجزة عن خدمات الويب، يمكن قراءة الدرس [ref1]، الفقرة 14، الصفحة 109.

يمكن تنفيذ خدمة الويب بطريقتين:

  • عن طريق فئة مُعلَّمة بـ @WebService تُنفَّذ في حاوية ويب
  • عن طريق كائن EJB مُعلَّم بـ @WebService والذي يتم تنفيذه في حاوية EJB

سنستخدم هنا الحل الأول:

في الدورة التدريبية [ref1]، الفقرة 14، الصفحة 109، يوجد مثال يستخدم الحل الثاني.

4.4. تكوين Hibernate لـ s على خادم Glassfish

اعتمادًا على إصداره، قد لا يحتوي خادم Glassfish V2 المرفق مع NetBeans على مكتبات Hibernate التي تحتاجها طبقة JPA/Hibernate. إذا اكتشفت خلال متابعة هذا البرنامج التعليمي أن خادم Glassfish لا يوفر لك تنفيذ JPA/Hibernate، أو إذا ظهرت استثناء عند نشر الخدمات يشير إلى عدم العثور على مكتبات Hibernate، فيجب عليك إضافة المكتبات إلى المجلد [<glassfish>/domains/domain1/lib/ext] ثم إعادة تشغيل خادم Glassfish:

  • إلى [1]، والمجلد <glassfish>/.../lib/ext
  • إلى [2]، ومكتبات Hibernate بالإضافة إلى بعض برامج تشغيل Jdbc
  • إلى [3]، برنامج تشغيل Jdbc من MySQL

توجد مكتبات Hibernate في ملف zip المرفق بالبرنامج التعليمي.

4.5. أدوات الإنشاء التلقائي في NetBeans

لنعد إلى البنية التي يتعين علينا إنشاؤها:

باستخدام NetBeans، يمكن إنشاء الطبقة [JPA] والطبقة [Ejb] التي تتحكم في الوصول إلى الكيانات JPA التي تم إنشاؤها تلقائيًا. من المفيد التعرف على طرق الإنشاء التلقائي هذه لأن الكود الذي يتم إنشاؤه يقدم إرشادات قيّمة حول كيفية كتابة الكيانات JPA أو كود EJB الذي يستخدمها.

نصف الآن بعض أدوات الإنشاء التلقائي هذه. لفهم الكود الذي تم إنشاؤه، يجب أن يكون لدى القارئ معرفة جيدة بالكيانات JPA و[ref1] وEJB و[ref2].

إنشاء اتصال NetBeans بقاعدة البيانات

  • قم بتشغيل SGBD و MySQL 5 حتى يصبح BD متاحًا
  • إنشاء اتصال NetBeans بقاعدة البيانات [dbrdvmedecins]
  • في علامة التبويب [Files]، ضمن الفرع [Databases] [1]، حدد برنامج التشغيل Jdbc MySQL [2]
  • ثم حدد الخيار [3] «Connect Using» الذي يتيح إنشاء اتصال بقاعدة بيانات MySQL
  • في [4]، أدخل المعلومات المطلوبة
  • ثم قم بالتأكيد في [5]
  • في [6]، يتم إنشاء الاتصال. ويمكن رؤية الجداول الأربعة لقاعدة البيانات المتصلة.

إنشاء مشروع EJB

  • إلى [1]، قم بإنشاء تطبيق جديد، ووحدة EJB
  • في [2]، اختر الفئة [Java EE] وفي [3] اختر النوع [EJB Module]
  • في [4] اختر مجلدًا للمشروع وفي [5] قم بتسميته - ثم أكمل المعالج
  • في [6] المشروع الذي تم إنشاؤه

إضافة مورد JDBC إلى خادم Glassfish

سنقوم بإضافة مورد JDBC إلى خادم Glassfish.

  • في علامة التبويب [Services]، قم بتشغيل خادم Glassfish [2, 3]
  • في علامة التبويب [Projects]، انقر بزر الماوس الأيمن على مشروع Ejb وفي [5] حدد الخيار [New / Other] الذي يسمح بإضافة عنصر إلى المشروع.

Image

  • في [6]، حدد الفئة [Glassfish]، ثم في [7] حدد أنك تريد إنشاء مورد JDBC عن طريق تحديد النوع [JDBC Resource]
  • في [8]، حدد أن هذا المورد JDBC سيستخدم مجموعة اتصالاته الخاصة
  • في [9]، قم بتسمية المورد JDBC
  • في [10]، انتقل إلى الخطوة التالية
  • في [11]، يتم تحديد خصائص مجموعة اتصالات المورد JDBC
  • في [12]، قم بتسمية مجموعة الاتصالات
  • في [13]، اختر اتصال NetBeans [dbrdvmedecins] الذي تم إنشاؤه مسبقًا
  • في [14]، انتقل إلى الخطوة التالية
  • في [15]، لا يوجد عادةً ما يستدعي التغيير في هذه الصفحة. تم استخلاص خصائص الاتصال بقاعدة البيانات MySQL [dbrdvmedecins] من خصائص اتصال NetBeans [dbrdvmedecins] الذي تم إنشاؤه مسبقًا
  • في [16]، انتقل إلى الخطوة التالية
  • إلى [17]، واحتفظ بالقيم الافتراضية المقترحة
  • إلى [18]، ثم أكمل المعالج. يقوم المعالج بإنشاء الملف [sun-resources.xml] و[19] الذي يكون محتواه كما يلي:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
  <jdbc-resource enabled="true" jndi-name="jdbc/dbrdvmedecins" object-type="user" pool-name="dbrdvmedecinsPool">
    <description/>
  </jdbc-resource>
  <jdbc-connection-pool ...">
    <property name="URL" value="jdbc:mysql://localhost:3306/dbrdvmedecins"/>
    <property name="User" value="root"/>
    <property name="Password" value="()"/>
  </jdbc-connection-pool>
</resources>

يحتوي الملف أعلاه على جميع المعلومات التي تم إدخالها في المعالج بتنسيق XML. وسيتم استخدامه بواسطة IDE NetBeans لمطالبة خادم Glassfish بإنشاء المورد "jdbc/dbrdvmedecins" المحدد في السطر 4.

إنشاء وحدة ثبات

تقوم وحدة الاستمرارية [persistence.xml] بتكوين الطبقة JPA: فهي تحدد التنفيذ JPA المستخدم (Toplink، Hibernate، ...) وتقوم بتكوينه.

  • في [1]، انقر بزر الماوس الأيمن على مشروع Ejb واختر [New / Other] في [2]
  • في [3]، حدد الفئة [Persistence] ثم في [4]، حدد أنك تريد إنشاء وحدة استمرارية JPA
  • في [5]، قم بتسمية وحدة الاستمرارية التي تم إنشاؤها
  • في [6]، اختر [Hibernate] كتنفيذ لـ JPA
  • في [7]، حدد مورد Glassfish "jdbc/dbrdvmedecins" الذي تم إنشاؤه للتو
  • في [8]، حدد أنه لا يجب اتخاذ أي إجراء على قاعدة البيانات عند إنشاء مثيل الطبقة JPA
  • قم بإنهاء المعالج
  • في [9]، الملف [persistence.xml] الذي أنشأه المساعد

محتواه كما يلي:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
  <persistence-unit name="serveur-ejb-dao-jpa-hibernate-generePU" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties/>
  </persistence-unit>
</persistence>

مرة أخرى، يعيد الملف XML المعلومات التي تم إدخالها في المساعد. هذا الملف غير كافٍ للعمل مع قاعدة البيانات MySQL5 "dbrdvmedecins". يجب أن نحدد لـ Hibernate نوع ملف SGBD المطلوب إدارته. وسيتم ذلك لاحقًا.

إنشاء الكيانات JPA

 
  • في [1]، انقر بزر الماوس الأيمن على المشروع، ثم في [2] اختر الخيار [New / Other]
  • في [3]، حدد الفئة [Persistence] ثم في [4]، حدد أنك تريد إنشاء كيانات JPA من قاعدة بيانات موجودة.
  • في [5]، حدد المصدر JDBC "jdbc/dbrdvmedecins" الذي أنشأناه
  • في [6]، الجداول الأربعة لقاعدة البيانات المرتبطة
  • في [7,8]، قم بتضمينها جميعًا في عملية إنشاء الكيانات JPA
  • في [9]، ومتابعة المعالج
  • في [10]، الكيانات JPA التي سيتم إنشاؤها
  • إلى [11]، قم بتسمية حزمة الكيانات JPA
  • إلى [12]، واختيار نوع Java الذي سيقوم بتغليف قوائم الكائنات التي تُرجعها الطبقة JPA
  • إنهاء المعالج
  • في [13]، الكيانات الأربعة JPA التي تم إنشاؤها، واحد لكل جدول في قاعدة البيانات.

فيما يلي، على سبيل المثال، كود الكيان [Rv] الذي يمثل سطرًا من الجدول [rv] في قاعدة البيانات [dbrdvmedecins].

package jpa;
...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "ID")
  private Long id;
  @Basic(optional = false)
  @Column(name = "JOUR")
  @Temporal(TemporalType.DATE)
  private Date jour;
  @JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Creneaux idCreneau;
  @JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Clients idClient;

  public Rv() {
  }

...
}

إنشاء طبقة EJB للوصول إلى الكيانات JPA

  • في [1]، انقر بزر الماوس الأيمن على المشروع وفي [2]، حدد الخيار [New / Other]
  • في [3]، حدد الفئة [Persistence] ثم في [4] النوع [Session Beans for Entity Classes]
  • في [5]، تظهر الكيانات JPA التي تم إنشاؤها مسبقًا
  • في [6]، حددها جميعًا
  • في [7]، تم تحديدها
  • في [8]، تابع المعالج
  • في [9]، قم بتسمية حزمة EJB التي سيتم إنشاؤها
  • في [10]، حدد أن EJBs يجب أن تنفذ واجهة محلية وواجهة بعيدة في آن واحد
  • إنهاء المساعد
  • في [11]، EJB التي تم إنشاؤها

فيما يلي، على سبيل المثال، كود EJB الذي يدير الوصول إلى الكيان [Rv]، وبالتالي إلى الجدول [rv] في قاعدة البيانات [dbrdvmedecins]:

package ejb;
...
@Stateless
public class RvFacade implements RvFacadeLocal, RvFacadeRemote {
  @PersistenceContext
  private EntityManager em;

  public void create(Rv rv) {
    em.persist(rv);
  }

  public void edit(Rv rv) {
    em.merge(rv);
  }

  public void remove(Rv rv) {
    em.remove(em.merge(rv));
  }

  public Rv find(Object id) {
    return em.find(Rv.class, id);
  }

  public List<Rv> findAll() {
    return em.createQuery("select object(o) from Rv as o").getResultList();
  }

}

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

4.6. مشروع NetBeans للوحدة النمطية EJB

نقوم بإنشاء وحدة EJB جديدة فارغة (انظر الفقرة 4.5):

 
  • تضم الحزمة [rdvmedecins.entites] كيانات طبقة JPA
  • الحزمة [rdvmedecins.dao] تُنفِّذ EJB للطبقة [dao]
  • الحزمة [rdvmedecins.exceptions] تُنفِّذ فئة استثناء خاصة بالتطبيق

فيما يلي، نفترض أن القارئ قد اتبع جميع الخطوات الواردة في الفقرة 4.5. وسيتعين عليه إعادة بعضها.

4.6.1. تكوين الطبقة JPA

دعونا نستعرض بنية تطبيقنا العميل/الخادم:

مشروع NetBeans:

 

يتم تكوين الطبقة [JPA] بواسطة الملفين [persistence.xml] و [sun-resources.xml] المذكورين أعلاه. يتم إنشاء هذين الملفين بواسطة المعالجات التي سبق أن تناولناها:

  • تم وصف عملية إنشاء الملف [sun-resources.xml] في الفقرة 4.5.
  • وقد تم وصف إنشاء الملف [persistence.xml] في الفقرة 4.5.

يجب تعديل الملف [persistence.xml] الذي تم إنشاؤه على النحو التالي:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
  <persistence-unit name="dbrdvmedecins" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
    <properties>
       <!-- اللهجة -->
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
    </properties>
  </persistence-unit>
</persistence>
  • السطر 3: نوع المعاملات هو JTA: ستتم إدارة المعاملات بواسطة حاوية Ejb3 في Glassfish
  • السطر 4: يتم استخدام تطبيق JPA / Hibernate. ولهذا الغرض، تمت إضافة مكتبة Hibernate إلى خادم Glassfish (انظر الفقرة 4.4).
  • السطر 5: مصدر البيانات JTA الذي تستخدمه طبقة JPA يحمل الاسم JNDI «jdbc/dbrdvmedecins».
  • السطر 8: لا يتم إنشاء هذا السطر تلقائيًا. يجب إضافته يدويًّا. وهو يُشير إلى Hibernate بأن SGBD المستخدم هو MySQL5.

يتم تكوين مصدر البيانات "jdbc/dbrdvmedecins" في الملف [sun-resources.xml] التالي:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
  <jdbc-resource enabled="true" jndi-name="jdbc/dbrdvmedecins" object-type="user" pool-name="dbrdvmedecinsPool">
    <description/>
  </jdbc-resource>
  <jdbc-connection-pool ...>
    <property name="URL" value="jdbc:mysql://localhost/dbrdvmedecins"/>
    <property name="User" value="root"/>
    <property name="Password" value="()"/>
  </jdbc-connection-pool>
</resources>
  • الأسطر 8-10: خصائص Jdbc لمصدر البيانات (عنوان URL لقاعدة البيانات، واسم المستخدم وكلمة المرور). قاعدة البيانات MySQL dbrdvmedecins هي تلك الموصوفة في الفقرة 4.1.
  • السطر 7: خصائص مجموعة الاتصالات المرتبطة بمصدر البيانات هذا

4.6.2. كيانات الطبقة JPA

لنتذكر بنية تطبيقنا العميل/الخادم:

مشروع NetBeans:

تقوم الحزمة [rdvmedecins.entites] بتنفيذ الطبقة [Jpa].

لقد رأينا في الفقرة 4.5 كيفية إنشاء كيانات JPA تلقائيًا لتطبيق ما. لن نستخدم هذه التقنية هنا، بل سنقوم بتعريف الكيانات بأنفسنا. ومع ذلك، ستتضمن هذه الكيانات جزءًا كبيرًا من الكود الذي تم إنشاؤه في الفقرة 4.5. ونرغب هنا في أن تكون الكيانات [Medecin] و [Client] فئات فرعية لفئة [Personne].

تُستخدم فئة «Personne» لتمثيل الأطباء والعملاء:

package rdvmedecins.entites;
...
@MappedSuperclass
public class Personne implements Serializable {
   // خصائص الشخص

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "ID")
  private Long id;
  @Version
  @Column(name = "VERSION", nullable = false)
  private Integer version;

  @Column(name = "TITRE", length = 5, nullable = false)
  private String titre;
  @Column(name = "NOM", length = 30, nullable = false)
  private String nom;
  @Column(name = "PRENOM", length = 30, nullable = false)
  private String prenom;

   // منشئ افتراضي
  public Personne() {
  }

   // المُنشئ مع المعلمات
  public Personne(String titre, String nom, String prenom) {
     // يتم ذلك عبر مُعيِّنات القيم
...
  }

   // منشئ بالنسخ
  public Personne(Personne personne) {
     // المرور عبر متغيرات التعيين
 ...
  }

   // toString
  @Override
  public String toString() {
    return "[" + titre + "," + prenom + "," + nom + "]";
  }

// دالات الاسترجاع والتعيين
....
}
  • السطر 3: تجدر الإشارة إلى أن الفئة [Personne] ليست كيانًا (@Entity) بحد ذاتها. ستكون الفئة الأم للكيانات. يشير التعليق التوضيحي @MappedSuperClass إلى هذه الحالة.

تغلف الكيان [Client] صفوف الجدول [clients]. وهي مشتقة من الفئة السابقة [Personne]:

package rdvmedecins.entites;
....
@Entity
@Table(name = "CLIENTS")
public class Client extends Personne implements Serializable {

   // منشئ افتراضي
  public Client() {
  }

   // منشئ مع معلمات
  public Client(String titre, String nom, String prenom) {
     // الوالد
    super(titre, nom, prenom);
  }

   // منشئ النسخ
  public Client(Client client) {
     // الأصل
    super(client);
  }
}
  • السطر 3: الفئة [Client] هي كيان JPA
  • السطر 4: وهي مرتبطة بالجدول [clients]
  • السطر 5: وهي مشتقة من الفئة [Personne]

الكيان [Medecin] الذي يغلف صفوف الجدول [medecins] يتبع نفس النموذج:

package rdvmedecins.entites;
...
@Entity
@Table(name = "MEDECINS")
public class Medecin extends Personne implements Serializable {

   // منشئ افتراضي
  public Medecin() {
  }

   // منشئ مع معلمات
  public Medecin(String titre, String nom, String prenom) {
     // الأصل
    super(titre, nom, prenom);
  }

   // منشئ النسخ
  public Medecin(Medecin medecin) {
     // الأصل
    super(medecin);
  }
}

تضم الكيان [Creneau] صفوف الجدول [creneaux]:

package rdvmedecins.entites;
....
@Entity
@Table(name = "CRENEAUX")
public class Creneau implements Serializable {

   // خصائص فترة زمنية من RV
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "ID")
  private Long id;
  @Version
  @Column(name = "VERSION", nullable = false)
  private Integer version;
  @ManyToOne
  @JoinColumn(name = "ID_MEDECIN", nullable = false)
  private Medecin medecin;
  @Column(name = "HDEBUT", nullable = false)
  private Integer hdebut;
  @Column(name = "MDEBUT", nullable = false)
  private Integer mdebut;
  @Column(name = "HFIN", nullable = false)
  private Integer hfin;
  @Column(name = "MFIN", nullable = false)
  private Integer mfin;

   // المصنع الافتراضي
  public Creneau() {

  }

   // منشئ مع المعلمات
  public Creneau(Medecin medecin, Integer hDebut,Integer mDebut, Integer hFin, Integer mFin) {
     // يتم التمرير عبر مُعيِّنات القيم
...
  }

   // منشئ عن طريق النسخ
  public Creneau(Creneau creneau) {
     // المرور عبر متغيرات التعيين
...
  }

   // toString
  @Override
  public String toString() {
    return "[" + getId() + "," + getVersion() + "," + getMedecin() + "," + getHdebut() + ":" + getMdebut() + "," + getHfin() + ":" + getMfin() + "]";
  }

   // دالات التعيين - دالات الاسترجاع
...
}
  • الصفوف 15-17 تمثل العلاقة «واحد إلى عدة» الموجودة بين الجدول [creneaux] والجدول [medecins] في قاعدة البيانات.

تضم الكيان [Rv] الصفوف الموجودة في الجدول [rv]:

package rdvmedecins.entites;
...
@Entity
@Table(name = "RV")
public class Rv implements Serializable {
   // الخصائص

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "ID")
  private Long id;
  @Column(name = "JOUR", nullable = false)
  @Temporal(TemporalType.DATE)
  private Date jour;
  @ManyToOne
  @JoinColumn(name = "ID_CLIENT", nullable = false)
  private Client client;
  @ManyToOne
  @JoinColumn(name = "ID_CRENEAU", nullable = false)
  private Creneau creneau;

   // منشئ افتراضي
  public Rv() {
  }

   // منشئ مع معلمات
  public Rv(Date jour, Client client, Creneau creneau) {
     // نستخدم دالات التعيين
...
  }

   // منشئ بالنسخ
  public Rv(Rv rv) {
     // يتم التمرير عبر متغيرات التعيين
...
  }

   // toString
  @Override
  public String toString() {
    return "[" + getId() + "," + new SimpleDateFormat("dd/MM/yyyy").format(getJour()) + "," + getClient() + "," + getCreneau() + "]";
  }

// دالات القراءة والكتابة
...
}
  • الصفوف 15-17 تمثل العلاقة «واحد إلى عدة» الموجودة بين الجدول [rv] والجدول [clients] في قاعدة البيانات، بينما تمثل الصفوف 18-20 العلاقة «واحد إلى عدة» القائمة بين الجدول [rv] والجدول [creneaux]

4.6.3. فئة الاستثناء

فئة الاستثناء [RdvMedecinsException] الخاصة بالتطبيق هي كما يلي:

package rdvmedecins.exceptions;

import javax.ejb.ApplicationException;

@ApplicationException(rollback=true)
public class RdvMedecinsException extends RuntimeException {

  private static final long serialVersionUID = 1L;

   // الحقول الخاصة
  private int code = 0;

   // منشئات
  public RdvMedecinsException() {
    super();
  }

  public RdvMedecinsException(String message) {
    super(message);
  }

  public RdvMedecinsException(String message, Throwable cause) {
    super(message, cause);
  }

  public RdvMedecinsException(Throwable cause) {
    super(cause);
  }

  public RdvMedecinsException(String message, int code) {
    super(message);
    setCode(code);
  }

  public RdvMedecinsException(Throwable cause, int code) {
    super(cause);
    setCode(code);
  }

  public RdvMedecinsException(String message, Throwable cause, int code) {
    super(message, cause);
    setCode(code);
  }

   // دالات الاسترجاع - دالات التعيين
...
}
  • السطر 6: الفئة مشتقة من الفئة [RuntimeException]. وبالتالي، لا يفرض المُجمِّع معالجتها باستخدام try / catch.
  • السطر 5: التوضيح @ApplicationException يضمن ألا يتم «ابتلاع» الاستثناء بواسطة استثناء من النوع [EjbException].

لفهم التعليق التوضيحي @ApplicationException، لنعد إلى البنية المستخدمة على جانب الخادم:

سيتم إطلاق الاستثناء من النوع [RdvMedecinsException] بواسطة طرق EJB الخاصة بالطبقة [dao] داخل حاوية Ejb3، وستقوم الحاوية باعتراضه. وبدون التعليق التوضيحي @ApplicationException، تقوم الحاوية Ejb3 بتغليف الاستثناء الذي حدث في استثناء من النوع [EjbException] وإعادة إطلاقه. قد لا نرغب في هذا التغليف ونسمح بخروج استثناء من النوع [RdvMedecinsException] من الحاوية Ejb3. وهذا ما تتيحه العلامة @ApplicationException. من ناحية أخرى، يشير السمة (rollback=true) في هذا التعليق التوضيحي إلى حاوية Ejb3 بأنه في حالة حدوث استثناء من النوع [RdvMedecinsException] داخل طريقة يتم تنفيذها ضمن معاملة تحتوي على SGBD، فيجب إلغاء هذه المعاملة. من الناحية الفنية، يُطلق على ذلك اسم «إجراء rollback» للمعاملة.

4.6.4. Ejb الخاص بطبقة [dao]

واجهة Java [IDao] للطبقة [dao] هي كما يلي:

package rdvmedecins.dao;
...
public interface IDao {

   // قائمة العملاء
  public List<Client> getAllClients();
   // قائمة الأطباء
  public List<Medecin> getAllMedecins();
   // قائمة المواعيد المتاحة للطبيب
  public List<Creneau> getAllCreneaux(Medecin medecin);
   // قائمة مواعيد الطبيب في يوم معين
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour);
   // البحث عن عميل باستخدام رقم التعريف الخاص به
  public Client getClientById(Long id);
   // البحث عن عميل باستخدام رقم التعريف الخاص به
  public Medecin getMedecinById(Long id);
   // البحث عن موعد محدد برقمه التعريفي
  public Rv getRvById(Long id);
   // البحث عن فترة زمنية محددة برقم التعريف الخاص بها
  public Creneau getCreneauById(Long id);
   // إضافة RV
  public Rv ajouterRv(String jour, Creneau creneau, Client client);
   // حذف RV
  public void supprimerRv(Rv rv);
}

تكتفي الواجهة المحلية [IDaoLocal] لـ Ejb باشتقاق الواجهة السابقة [IDao]:

1
2
3
4
5
6
7
package rdvmedecins.dao;

import javax.ejb.Local;

@Local
public interface IDaoLocal extends IDao{
}

وينطبق الأمر نفسه على الواجهة البعيدة [IDaoRemote]:

1
2
3
4
5
6
7
package rdvmedecins.dao;

import javax.ejb.Remote;

@Remote
public interface IDaoRemote extends IDao {
}

تقوم EJB [DaoJpa] بتنفيذ كلتا الواجهتين، المحلية والبعيدة:

1
2
3
4
5
6
7
package rdvmedecins.dao;
...
@Stateless(mappedName="rdvmedecins.dao")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoJpa implements IDaoLocal,IDaoRemote {
...
}
  • تشير السطر 3 إلى أن EJB البعيد يحمل الاسم "rdvmedecins.dao"
  • تشير السطر 4 إلى أن جميع أساليب EJB تتم ضمن معاملة يديرها حاوية EJB3.
  • يُظهر السطر 5 أن EJB يُنفذ الواجهتين المحلية والبعيدة.

فيما يلي الكود الكامل لـ EJB:

package rdvmedecins.dao;
...
@Stateless(mappedName="rdvmedecins.dao")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoJpa implements IDaoLocal,IDaoRemote {

  @PersistenceContext
  private EntityManager em;

   // قائمة العملاء
  public List<Client> getAllClients() {
    try {
      return em.createQuery("select c from Client c").getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 1);
    }
  }

   // قائمة الأطباء
  public List<Medecin> getAllMedecins() {
    try {
      return em.createQuery("select m from Medecin m").getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 2);
    }
  }

   // قائمة المواعيد المتاحة لطبيب معين
   // الطبيب: الطبيب
  public List<Creneau> getAllCreneaux(Medecin medecin) {
    try {
      return em.createQuery("select c from Creneau c join c.medecin m where m.id=:idMedecin").setParameter("idMedecin", medecin.getId()).getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 3);
    }
  }

   // قائمة المواعيد الطبية لطبيب معين، في يوم معين
   // medecin: الطبيب
   // اليوم: اليوم
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour) {
    try {
      return em.createQuery("select rv from Rv rv join rv.creneau c join c.medecin m where m.id=:idMedecin and rv.jour=:jour").setParameter("idMedecin", medecin.getId()).setParameter("jour", new SimpleDateFormat("yyyy:MM:dd").parse(jour)).getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 4);
    }
  }

   // إضافة موعد
   // اليوم: يوم الموعد
   // الفترة الزمنية: الفترة الزمنية للموعد
   // العميل: العميل الذي تم حجز الموعد من أجله
  public Rv ajouterRv(String jour, Creneau creneau, Client client) {
    try {
      Rv rv = new Rv(new SimpleDateFormat("yyyy:MM:dd").parse(jour), client, creneau);
      em.persist(rv);
      return rv;
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 5);
    }
  }

   // حذف موعد
   // الموعد: الموعد الذي تم حذفه
  public void supprimerRv(Rv rv) {
    try {
      em.remove(em.merge(rv));
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 6);
    }
  }

   // استرداد عميل معين
  public Client getClientById(Long id) {
    try {
      return (Client) em.find(Client.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 7);
    }
  }

   // استرداد طبيب معين
  public Medecin getMedecinById(Long id) {
    try {
      return (Medecin) em.find(Medecin.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 8);
    }
  }

   // استرداد موعد معين
  public Rv getRvById(Long id) {
    try {
      return (Rv) em.find(Rv.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 9);
    }
  }

   // استرداد موعد محدد
  public Creneau getCreneauById(Long id) {
    try {
      return (Creneau) em.find(Creneau.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 10);
    }
  }
}
  • السطر 8: الكائن EntityManager الذي يدير الوصول إلى سياق الاستمرارية. عند إنشاء مثيل للفئة، سيتم تهيئة هذا الحقل بواسطة حاوية EJB بفضل التعليق التوضيحي @PersistenceContext في السطر 7.
  • السطر 15: استعلام JPQL الذي يُرجع جميع السجلات من الجدول [clients] في شكل قائمة من الكائنات [Client].
  • السطر 22: استعلام مماثل للأطباء
  • السطر 32: استعلام JPQL يقوم بدمج الجدولين [creneaux] و [medecins]. ويتم تعيين معلماته باستخدام معرّف الطبيب.
  • السطر 43: استعلام JPQL يقوم بدمج الجداول [rv] و [creneaux] و [medecins]، وله معلمتان: معرّف الطبيب ويوم الموعد.
  • الأسطر 55-57: إنشاء موعد طبي ثم حفظه في قاعدة البيانات.
  • السطر 67: حذف موعد من قاعدة البيانات.
  • السطر 76: تنفيذ عملية استعلام (SELECT) في قاعدة البيانات للعثور على عميل معين
  • السطر 85: الأمر نفسه بالنسبة للطبيب
  • السطر 94: الأمر نفسه بالنسبة لموعد
  • السطر 103: الأمر نفسه بالنسبة لفترة زمنية
  • جميع العمليات التي تستخدم سياق الاستمرارية em الوارد في السطر 9 قد تواجه مشكلة مع قاعدة البيانات. ولذلك، فإنها جميعًا محاطة بعبارة try / catch. يتم تغليف الاستثناء المحتمل في الاستثناء "الداخلي" RdvMedecinsException.

بعد ترجمة الوحدة النمطية Ejb، يتم إنشاء ملف .jar باسم :

4.7. نشر وحدة EJB الخاصة بطبقة [dao] باستخدام NetBeans

يتيح NetBeans نشر مكون EJB الذي تم إنشاؤه مسبقًا على خادم GlassFish بطريقة بسيطة.

  • في خصائص مشروع EJB، تحقق من خيارات التشغيل [1].
  • في [2]، اسم الخادم الذي سيتم نشر تطبيق EJB عليه
  • في علامة التبويب [Services] [3]، يتم تشغيله [4].
  • في [5]، خادم Glassfish بمجرد تشغيله. لا يحتوي بعد على وحدة EJB.
  • قم بتشغيل الخادم MySQL وتأكد من أن قاعدة البيانات [dbrdvmedecins] متصلة بالشبكة. للقيام بذلك، يمكنك استخدام اتصال NetBeans الذي تم إنشاؤه في الفقرة 4.5.
  • في علامة التبويب [Projects] [6]، يتم نشر وحدة EJB [7]: يجب تشغيل SGBD MySQL5 حتى يصبح المورد JDBC "jdbc/dbrdvmedecins" الذي تستخدمه وحدة EJB متاحًا.
  • في [8]، يظهر EJB الذي تم نشره في شجرة الخادم Glassfish
  • في [9]، يتم إزالة تطبيق EJB الذي تم نشره
  • في [10]، لم يعد EJB يظهر في شجرة الخادم Glassfish.

4.8. نشر EJB الخاص بالطبقة [dao] باستخدام Glassfish

نوضح هنا كيفية نشر EJB على خادم Glassfish من ملف .jar الخاص به.

  • قم بتشغيل الخادم MySQL وتأكد من أن قاعدة البيانات [dbrdvmedecins] متصلة بالإنترنت. وللقيام بذلك، يمكنك استخدام اتصال NetBeans الذي تم إنشاؤه في الفقرة 4.5.

دعونا نستعرض تكوين JPA للوحدة EJB التي سيتم نشرها. يتم إجراء هذا التكوين في الملف [persistence.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
  <persistence-unit name="dbrdvmedecins" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
    <properties>
       <!-- اللهجة -->
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
    </properties>
  </persistence-unit>
</persistence>

تشير السطر 5 إلى أن طبقة Jpa تستخدم مصدر بيانات JTA، c.a.d، الذي يديره الحاوية Ejb3، والمسمى "jdbc/dbrdvmedecins".

لقد رأينا في الفقرة 4.5 كيفية إنشاء هذا المورد JDBC من خلال NetBeans. ونوضح هنا كيفية القيام بذلك مباشرةً باستخدام GlassFish. ونتبع هنا الإجراء الموصوف في الفقرة 13.1.2، الصفحة 79 من [ref1].

نبدأ بحذف المورد حتى نتمكن من إعادة إنشائه. نقوم بذلك من خلال NetBeans:

  • في [1]، الموارد JDBC الخاصة بخادم Glassfish
  • إلى [2]، والمورد "jdbc/dbrdvmedecins" الخاص بـ Ejb الخاص بنا
  • إلى [3]، ومجموعة اتصالات هذا المورد JDBC
  • في [4]، نقوم بحذف مجموعة الاتصالات. سيؤدي ذلك إلى حذف جميع الموارد JDBC التي تستخدمها، وبالتالي المورد "jdbc/dbrdvmedecins".
  • في [5] و [6]، تم إزالة المورد JDBC ومجموعة الاتصالات.

الآن، نستخدم وحدة التحكم الإدارية لخادم Glassfish لإنشاء المورد JDBC ونشر EJB.

  • في علامة التبويب [services] [1] في NetBeans، قم بتشغيل خادم Glassfish [2] ثم قم بالوصول إلى [3] لوحدة التحكم الإدارية الخاصة به
  • في [4]، وقم بتسجيل الدخول كمسؤول (كلمة المرور: adminadmin إذا لم تكن قد قمت بتغييرها أثناء التثبيت أو بعده).
  • إلى [5]، حدد الفرع [Connection Pools] من موارد Glassfish
  • في [6]، قم بإنشاء تجمع اتصالات جديد. تجدر الإشارة إلى أن تجمع الاتصالات هو تقنية تهدف إلى الحد من عدد مرات فتح/إغلاق الاتصالات مع SGBD. عند بدء تشغيل الخادم، يتم فتح عدد N من الاتصالات مع SGBD، حيث يتم تحديد هذا العدد من خلال التكوين. ثم يتم إتاحة هذه الاتصالات المفتوحة لـ EJB التي تطلبها لإجراء عملية مع SGBD. بمجرد انتهاء العملية، تعيد مكونة EJB الاتصال إلى المجمع. لا يتم إغلاق الاتصال أبدًا. بل يتم مشاركته بين الخيوط المختلفة التي تصل إلى SGBD
  • في [7]، قم بتسمية المجمع
  • في [8]، والفئة التي تمثل مصدر البيانات هي الفئة [javax.sql.DataSource]
  • في [9]، و SGBD الذي يحتوي على مصدر البيانات هو هنا MySQl.
  • في [10]، انتقل إلى الخطوة التالية
  • في [11]، يؤدي السمة "Connection Validation Required" إلى أن يقوم المجمع، قبل منح اتصال، بالتحقق من أنه يعمل. إذا لم يكن الأمر كذلك، فإنه ينشئ اتصالاً جديداً. وهذا يسمح للتطبيق بمواصلة العمل بعد انقطاع مؤقت في الاتصال بـ SGBD. أثناء الانقطاع، لا يكون أي اتصال قابلاً للاستخدام ويتم إرسال استثناءات إلى العميل. وعند انتهاء الانقطاع، يحصل العملاء الذين يواصلون طلب الاتصالات عليها مرة أخرى: بفضل السمة "Connection Validation Required"، سيتم إعادة إنشاء جميع اتصالات المجمع. وبدون هذه السمة، سيلاحظ المجمع أن الاتصالات الأولية قد انقطعت ولكنه لن يسعى إلى إنشاء اتصالات جديدة.
  • في [12]، يُطلب مستوى العزل «Read Committed» للمعاملات. يضمن هذا المستوى أن المعاملة T2 لا يمكنها قراءة البيانات التي تم تعديلها بواسطة المعاملة T1 ما لم تكن هذه الأخيرة قد اكتملت تمامًا.
  • في [13]، يُطلب أن تستخدم جميع المعاملات مستوى العزل المحدد في [12]
  • في [14] و [15]، حدد عنوان URL الخاص بـ BD الذي يدير مجمعه الاتصالات
  • في [16]، سيكون المستخدم هو root
  • في [17]، أضف خاصية
  • في [18]، أضف الخاصية "Password" بالقيمة () في [19]. على الرغم من أن لقطة الشاشة [19] لا تظهر ذلك، يجب عدم إدخال سلسلة فارغة بل () (قوس مفتوح، قوس مغلق) للإشارة إلى كلمة مرور فارغة. إذا كان المستخدم root في قاعدة البيانات SGBD MySQL لديه كلمة مرور غير فارغة، فقم بإدخال كلمة المرور هذه.
  • في [20]، أكمل مساعد إنشاء مجموعة الاتصالات لقاعدة البيانات MySQL [dbrdvmedecins].
  • في [21]، تم إنشاء مجموعة الاتصالات. انقر على الرابط الخاص بها.
  • في [22]، يتيح الزر [Ping] إنشاء اتصال بقاعدة البيانات [dbrdvmedecins]
  • في [23]، إذا سارت الأمور على ما يرام، ستظهر رسالة تفيد بنجاح الاتصال

بمجرد إنشاء مجموعة الاتصالات، يمكن إنشاء مورد Jdbc:

  • في [1]، نختار الفرع [JDBC Resources] من شجرة كائنات الخادم
  • في [2]، نقوم بإنشاء مورد جديد JDBC
  • في [3]، نسمي المورد JDBC. يجب أن يتطابق هذا الاسم مع الاسم المستخدم في الملف [persistence.xml]:
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
  • في [4]، نحدد مجموعة الاتصالات التي يجب أن تستخدمها المورد الجديد JDBC: وهي المجموعة التي أنشأناها للتو
  • في ملف [5]، ننهي مساعد الإنشاء
  • في [6] المورد الجديد JDBC

الآن بعد إنشاء المورد JDBC، يمكننا نشر ملف jar الخاص بـ Ejb:

  • إلى [1]، حدد الفرع [Enterprise Applications]
  • إلى [2]، باستخدام الزر [Deploy]، حدد أنك تريد نشر تطبيق جديد
  • في [3]، حدد أن التطبيق هو وحدة EJB
  • في [4]، حدد ملف jar الخاص بـ Ejb [serveur-ejb-dao-jpa-hibernate.jar] الذي تم تزويدك به من أجل TP.
  • في [5]، يمكنك تغيير اسم وحدة EJB إذا رغبت في ذلك
  • في [6]، أكمل مساعد نشر وحدة ejb
  • في [7]، تم نشر الوحدة النمطية Ejb. ويمكن الآن استخدامها.

4.9. اختبارات وحدة Ejb الخاصة بطبقة [dao]

الآن بعد أن تم نشر وحدة Ejb الخاصة بطبقة [dao] في تطبيقنا، يمكننا اختبارها. سنقوم بذلك باستخدام عميل Java التالي:

الفئة [MainTestsDaoRemote] [1] هي فئة اختبار JUnit 4. تتكون المكتبات في [2] من جانب:

  • ملف jar الخاص بـ EJB في الطبقة [dao] [3] (انظر الفقرة 4.6.4).
  • مكتبات Glassfish [4] اللازمة للعملاء البعيدين لـ Ejb.

فئة الاختبار هي التالية:

package dao;
...
public class MainTestsDaoRemote {

   // طبقة [dao] تم اختبارها
  private static IDaoRemote dao;

  @BeforeClass
  public static void init() throws NamingException {
     // تهيئة بيئة JNDI
    InitialContext initialContext = new InitialContext();
     // إنشاء مثيل لطبقة DAO
    dao = (IDaoRemote) initialContext.lookup("rdvmedecins.dao");
  }

  @Test
  public void test1() {
     // بيانات الاختبار
    String jour = "2006:08:23";
     // عرض العملاء
    List<Client> clients = null;
    try {
      clients = dao.getAllClients();
      display("Liste des clients :", clients);
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // عرض الأطباء
    List<Medecin> medecins = null;
    try {
      medecins = dao.getAllMedecins();
      display("Liste des médecins :", medecins);
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // عرض المواعيد المتاحة لأحد الأطباء
    Medecin medecin = medecins.get(0);
    List<Creneau> creneaux = null;
    try {
      creneaux = dao.getAllCreneaux(medecin);
      display(String.format("Liste des créneaux du médecin %s", medecin), creneaux);
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // قائمة مواعيد الطبيب في يوم معين
    try {
      display(String.format("Liste des créneaux du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // إضافة موعد RV
    Rv rv = null;
    Creneau creneau = creneaux.get(2);
    Client client = clients.get(0);
    System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
    try {
      rv = dao.ajouterRv(jour, creneau, client);
      System.out.println("Rv ajouté");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, "2006:08:23"));
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // إضافة RV في نفس الموعد من نفس اليوم
     // يجب أن يؤدي إلى حدوث استثناء
    System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
    try {
      rv = dao.ajouterRv(jour, creneau, client);
      System.out.println("Rv ajouté");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, "2006:08:23"));
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // حذف RV
    System.out.println("Suppression du Rv ajouté");
    try {
      dao.supprimerRv(rv);
      System.out.println("Rv supprimé");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, "2006:08:23"));
    } catch (Exception ex) {
      System.out.println(ex);
    }
  }

   // أداة مساعدة - تعرض عناصر مجموعة ما
  private static void display(String message, List elements) {
    System.out.println(message);
    for (Object element : elements) {
      System.out.println(element);
    }
  }
}
  • السطر 13: لاحظ إنشاء مثيل للوكيل الخاص بـ EJB البعيد. نستخدم اسمه JNDI "rdvmedecins.dao".
  • تستخدم طرق الاختبار الطرق التي يعرضها EJB (انظر الفقرة 4.6.4).

إذا سارت الأمور على ما يرام، يجب أن تنجح الاختبارات:

 

الآن بعد أن أصبح EJB الخاص بالطبقة [dao] جاهزًا للعمل، يمكننا الانتقال إلى عرضه للجمهور عبر خدمة ويب.

4.10. خدمة الويب الخاصة بالطبقة [dao]

للحصول على مقدمة موجزة لمفهوم خدمة الويب، يمكن الرجوع إلى الفقرة 14، الصفحة 111 من [ref1].

لنعد إلى بنية خادم تطبيقنا العميل/الخادم:

نحن نهتم هنا بخدمة الويب الخاصة بالطبقة [dao]. وتتمثل المهمة الوحيدة لهذه الخدمة في إتاحة واجهة EJB الخاصة بالطبقة [dao] للعملاء متعددي المنصات القادرين على التفاعل مع خدمة ويب.

تجدر الإشارة إلى أن هناك طريقتين لتنفيذ خدمة ويب:

  • عن طريق فئة مُعلَّمة بـ @WebService تُنفَّذ في حاوية ويب
  • عن طريق كائن EJB مُعلَّم بـ @WebService والذي يتم تنفيذه في حاوية EJB

نستخدم هنا الحل الأول. في NetBeans IDE، يتعين علينا إنشاء مشروع مؤسسي يتألف من وحدتين:

  • وحدة EJB التي ستُنفَّذ في حاوية EJB: EJB الخاصة بطبقة [dao].
  • وحدة الويب التي ستُنفَّذ في حاوية الويب: خدمة الويب التي نقوم بإنشائها حاليًا.

سنقوم بإنشاء هذا المشروع المؤسسي بطريقتين.

4.10.1. مشروع NetBeans - الإصدار 1

نبدأ أولاً بإنشاء مشروع NetBeans من نوع «Web Application»:

  • في [1]، نقوم بإنشاء مشروع جديد ضمن فئة «Java Web» [2] من نوع «Web Application» [3].
  • في [4]، نسمي المشروع، وفي [5] نحدد المجلد الذي سيتم إنشاؤه فيه
  • في [6]، يتم تحديد خادم التطبيق الذي سيقوم بتنفيذ تطبيق الويب
  • في [7]، يتم تحديد سياق التطبيق
  • في [8]، يتم التحقق من صحة تكوين المشروع.
  • في [9]، يتم إنشاء المشروع. ستستخدم خدمة الويب التي نقوم بإنشائها ملف EJB من المشروع السابق [10]. ولذلك، فإنها تحتاج إلى الإشارة إلى ملف .jar الخاص بوحدة Ejb [10].
  • في [11]، نضيف مشروع NetBeans إلى مكتبات مشروع الويب [12]
  • في [13]، نختار مجلد وحدة Ejb في نظام الملفات ونؤكد الاختيار.
  • في [14]، تمت إضافة الوحدة النمطية Ejb إلى مكتبات مشروع الويب.

في [15]، نقوم بتنفيذ خدمة الويب باستخدام الفئة [WsDaoJpa] التالية:

package rdvmedecins.ws;
...
@WebService()
public class WsDaoJpa implements IDao {

  @EJB
  private IDaoLocal dao;

   // قائمة العملاء
  @WebMethod
  public List<Client> getAllClients() {
    return dao.getAllClients();
  }

   // قائمة الأطباء
  @WebMethod
  public List<Medecin> getAllMedecins() {
    return dao.getAllMedecins();
  }

   // قائمة المواعيد المتاحة لطبيب معين
   // medecin: الطبيب
  @WebMethod
  public List<Creneau> getAllCreneaux(Medecin medecin) {
    return dao.getAllCreneaux(medecin);
  }

   // قائمة المواعيد الطبية لطبيب معين، في يوم معين
   // medecin: الطبيب
   // اليوم: اليوم
  @WebMethod
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour) {
    return dao.getRvMedecinJour(medecin, jour);
  }

   // إضافة موعد
   // اليوم: يوم الموعد
   // الفترة الزمنية: الفترة الزمنية للموعد
   // العميل: العميل الذي تم حجز الموعد من أجله
  @WebMethod
  public Rv ajouterRv(String jour, Creneau creneau, Client client) {
    return dao.ajouterRv(jour, creneau, client);
  }

   // حذف موعد
   // الموعد: الموعد الذي تم حذفه
  @WebMethod
  public void supprimerRv(Rv rv) {
    dao.supprimerRv(rv);
  }

   // استرداد عميل معين
  @WebMethod
  public Client getClientById(Long id) {
    return dao.getClientById(id);
  }

   // استرداد طبيب معين
  @WebMethod
  public Medecin getMedecinById(Long id) {
    return dao.getMedecinById(id);
  }

   // استرداد موعد معين
  @WebMethod
  public Rv getRvById(Long id) {
    return dao.getRvById(id);
  }

   // استرداد موعد محدد
  @WebMethod
  public Creneau getCreneauById(Long id) {
    return dao.getCreneauById(id);
  }
}
  • في السطر 4، تقوم الفئة [WsdaoJpa] بتنفيذ الواجهة [IDao]. تجدر الإشارة إلى أن هذه الواجهة محددة في أرشيف EJB للطبقة [dao] بالشكل التالي:
package rdvmedecins.dao;
...
public interface IDao {

   // قائمة العملاء
  public List<Client> getAllClients();
   // قائمة الأطباء
  public List<Medecin> getAllMedecins();
   // قائمة المواعيد المتاحة لطبيب معين
  public List<Creneau> getAllCreneaux(Medecin medecin);
   // قائمة مواعيد الطبيب في يوم معين
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour);
   // البحث عن عميل باستخدام رقم التعريف الخاص به
  public Client getClientById(Long id);
   // البحث عن عميل باستخدام رقم التعريف الخاص به
  public Medecin getMedecinById(Long id);
   // البحث عن موعد محدد برقمه التعريفي
  public Rv getRvById(Long id);
   // البحث عن فترة زمنية محددة برقم التعريف الخاص بها
  public Creneau getCreneauById(Long id);
   // إضافة RV
  public Rv ajouterRv(String jour, Creneau creneau, Client client);
   // حذف RV
  public void supprimerRv(Rv rv);
}
  • السطر 3: التعليق التوضيحي @WebService يجعل من الفئة [WsDaoJpa] خدمة ويب.
  • السطران 6-7: سيتم إدخال مرجع EJB الخاص بالطبقة [dao] بواسطة خادم التطبيق في حقل السطر 7. تجدر الإشارة إلى أن التنفيذ المحلي (IDaoLocal في هذه الحالة) هو الذي يتم حقنه دائمًا. ويكون هذا الحقن ممكنًا لأن خدمة الويب تُنفَّذ في نفس JVM التي يعمل فيها EJB.
  • يتم تمييز جميع أساليب خدمة الويب بعلامة التعليق التوضيحي @WebMethod لجعلها أساليب مرئية للعملاء البعيدين. أما الأسلوب غير المميز بعلامة التعليق التوضيحي @WebMethod فسيكون داخليًّا في خدمة الويب وغير مرئي للعملاء البعيدين. تكتفي كل طريقة M في خدمة الويب باستدعاء الطريقة M المقابلة في EJB الذي تم حقنه في السطر 7.

ينعكس إنشاء خدمة الويب هذه في فرع جديد في مشروع NetBeans:

نرى في [1] خدمة الويب WsDaoJpa، وفي [2] الطرق التي تعرضها للعملاء البعيدين.

دعونا نستعرض بنية خدمة الويب قيد الإنشاء:

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

  • [1]: الوحدة النمطية للويب التي أنشأناها للتو
  • [2]: وحدة EJB التي أنشأناها في خطوة سابقة والتي تعتمد عليها خدمة الويب

لنشرهما معًا، يجب تجميع الوحدتين في مشروع NetBeans يُعرف باسم «مشروع مؤسسي»:

في [1]، نقوم بإنشاء مشروع مؤسسي جديد باسم [2, 3].

  • في [4,5]، نسمي المشروع ونحدد مجلد إنشائه
  • في [6]، يتم اختيار خادم التطبيق الذي سيتم نشر تطبيق المؤسسة عليه
  • في [7]، يمكن أن يتكون مشروع المؤسسة من ثلاثة مكونات: تطبيق ويب، وحدة EJB، وتطبيق عميل. هنا، يتم إنشاء المشروع بدون أي مكونات. وسيتم إضافة هذه المكونات لاحقًا.
  • في [8]، تطبيق المؤسسة الذي تم إنشاؤه حديثًا.
  • في [9]، انقر بزر الماوس الأيمن على [Java EE Modules] وأضف وحدة جديدة
  • في [10]، لا تظهر سوى وحدات NetBeans المفتوحة حاليًا في IDE. هنا نختار وحدة الويب [serveur-webservice-1-ejb-dao-jpa-hibernate] ووحدة EJB [serveur-ejb-dao-jpa-hibernate] اللتين قمنا بإنشائهما.
  • في [11]، تظهر الوحدتان اللتان تمت إضافتهما إلى مشروع المؤسسة.

يبقى لنا الآن نشر هذا التطبيق المؤسسي على خادم Glassfish. في الخطوة التالية، يجب تشغيل SGBD و MySQL حتى يصبح مصدر البيانات JDBC «jdbc/dbrdvmedecins» الذي تستخدمه الوحدة النمطية Ejb متاحًا.

  • في [1]، يتم تشغيل خادم Glassfish
  • إذا تم نشر الوحدة النمطية Ejb [serveur-ejb-dao-jpa-hibernate]، يتم إيقاف تشغيلها [2]
  • إلى [3]، يتم نشر تطبيق المؤسسة
  • في [4]، يتم نشره. نلاحظ أنه يحتوي على الوحدتين: Web و Ejb.

4.10.2. مشروع NetBeans - الإصدار 2

نوضح الآن كيفية نشر خدمة الويب عندما لا يتوفر لدينا كود المصدر للوحدة النمطية Ejb بل يتوفر فقط ملفها المضغوط .jar.

سيكون مشروع NetBeans الجديد لخدمة الويب كما يلي:

العناصر البارزة في المشروع هي التالية:

  • [1]: يتم تنفيذ خدمة الويب من خلال مشروع NetBeans من النوع [Web Application].
  • [2]: يتم تنفيذ خدمة الويب بواسطة الفئة [WsDaoJpa] التي تمت دراستها سابقًا
  • [3]: أرشيف EJB للطبقة [dao] الذي يتيح للفئة [WsDaoJpa] الوصول إلى تعريفات الفئات والواجهات والكيانات المختلفة في الطبقتين [dao] و [jpa].

ثم نقوم بإنشاء مشروع المؤسسة اللازم لنشر خدمة الويب:

  • [1]، وننشئ تطبيقًا مؤسسيًا [ea-rdvmedecins]، في البداية بدون أي وحدة نمطية.
  • في [2]، نضيف الوحدة النمطية الويب السابقة [serveur-webservice-ejb-dao-jpa-hibernate]
  • في [3]، النتيجة.

بصيغتها الحالية، لا يمكن نشر تطبيق المؤسسة [ea-rdvmedecins] على خادم Glassfish من NetBeans. تظهر رسالة خطأ. لذا، يجب نشر ملف الأرشيف ear الخاص بالتطبيق [ea-rdvmedecins] يدويًّا:

  • يوجد الأرشيف [ea-rdvmedecins.ear] في المجلد [dist] [2] ضمن علامة التبويب [Files] في NetBeans.
  • في هذا الأرشيف [3]، نجد عنصري التطبيق المؤسسي التاليين:
  • أرشيف EJB [serveur-ejb-dao-jpa-hibernate]. يوجد هذا الأرشيف لأنه كان جزءًا من المكتبات المشار إليها بواسطة خدمة الويب.
  • أرشيف خدمة الويب [serveur-webservice- ejb-dao-jpa-hibernate].
  • تم إنشاء الأرشيف [ea-rdvmedecins.ear] من خلال أرشيفين بسيطين Build و [4] من تطبيق المؤسسة.
  • في [5]، عملية النشر التي تفشل.

لنشر ملف الأرشيف [ea-rdvmedecins.ear] الخاص بتطبيق المؤسسة، نتبع نفس الإجراءات التي تم توضيحها عند نشر ملف أرشيف EJB [serveur-ejb-dao-jpa-hibernate.jar] في الفقرة 4.2. ونستخدم مرة أخرى عميل الويب الخاص بإدارة خادم Glassfish. لن نكرر الخطوات التي سبق وصفها.

أولاً، سنبدأ بـ«إلغاء نشر» التطبيق المؤسسي الذي تم نشره في الفقرة 4.10.1:

  • [1]: حدد الفرع [Enterprise Applications] لخادم Glassfish
  • في [2]، حدد التطبيق المؤسسي المراد إلغاء تحميله، ثم في [3]، قم بإلغاء تحميله
  • في [4] تم تفريغ تطبيق المؤسسة
  • في [1]، اختر الفرع [Enterprise Applications] لخادم Glassfish
  • في [2]، قم بنشر تطبيق مؤسسي جديد
  • في [3]، حدد النوع [Enterprise Application]
  • في [4]، حدد ملف .ear الخاص بمشروع NetBeans [ea-rdvmedecins]
  • في [5]، قم بنشر هذا الأرشيف
  • إلى [6]، تم نشر التطبيق
  • في [7]، تظهر خدمة الويب [WsDaoJpa] في الفرع [Web Services] لخادم Glassfish. قم بتحديدها.
  • في [8]، تتوفر معلومات متنوعة عن خدمة الويب. وأهمها بالنسبة للعميل هي المعلومات الموجودة في [9]: عنوان URI لخدمة الويب.
  • في [10]، يمكن اختبار خدمة الويب
  • في [11]، نجد عنوان URI لخدمة الويب الذي أُضيفت إليه المعلمة ?tester. يعرض عنوان URI هذا صفحة اختبار. يتم عرض جميع الطرق (@WebMethod) التي توفرها خدمة الويب ويمكن اختبارها. هنا، يتم اختبار الطريقة [13] التي تطلب قائمة العملاء.
  • في [14]، لا نعرض سوى جزء من صفحة الرد. لكن يمكننا أن نرى أن الطريقة getAllClients قد أرجعت بالفعل قائمة العملاء. تُظهر لنا لقطة الشاشة أنها ترسل استجابتها بتنسيق XML.

يتم وصف خدمة الويب بالكامل بواسطة ملف يُسمى XML:

  • في ملف [1] في أداة إدارة الويب الخاصة بخادم Glassfish، حدد خدمة الويب [WsDaoJpa]
  • إلى [2]، ثم اتبع الرابط [View WSDL]
  • إلى [3]: عنوان URI لملف WSDL. هذه معلومة مهمة يجب معرفتها. فهي ضرورية لتكوين عملاء خدمة الويب هذه.
  • في [4]، الوصف XML لخدمة الويب. لن نعلق على هذا المحتوى المعقد.

4.10.3. اختبارات الخدمة الويب JUnit

نقوم بإنشاء مشروع NetBeans لـ«تشغيل» الاختبارات التي تم إجراؤها مسبقًا باستخدام عميل EJB، ولكن هذه المرة باستخدام عميل لخدمة الويب التي تم نشرها مؤخرًا. ونتبع هنا نهجًا مشابهًا للنهج الموصوف في الفقرة 14.2.1، الصفحة 115 من [ref1].

  • في [1]، مشروع Java تقليدي
  • في [2]، فئة الاختبار
  • في [3]، يستخدم العميل أرشيف EJB للوصول إلى تعريفات واجهة الطبقة [dao] وكيانات JPA. تجدر الإشارة إلى أن هذا الأرشيف موجود في المجلد الفرعي [dist] التابع لمجلد وحدة EJB.

للوصول إلى خدمة الويب البعيدة، من الضروري إنشاء فئات بروكسي:

في المخطط أعلاه، تتواصل الطبقة [2] [C=Client] مع الطبقة [1] [S=Serveur]. للتواصل مع الطبقة [S]، يتعين على العميل [C] إنشاء اتصال شبكي مع الطبقة [S] والتواصل معها وفقًا لبروتوكول محدد. اتصالات الشبكة هي اتصالات TCP وبروتوكول النقل هو HTTP. يتم تنفيذ الطبقة [S] التي تمثل خدمة الويب بواسطة سيرفلت جافا يتم تشغيله بواسطة خادم Glassfish. لم نقم بكتابة هذه الخدمة. يتم إنشاؤها تلقائيًا بواسطة Glassfish استنادًا إلى التعليقات التوضيحية @Webservice و@WebMethod في الفئة [WsDaoJpa] التي قمنا بكتابتها. وبالمثل، سنقوم بأتمتة إنشاء الطبقة [C] الخاصة بالعميل. يُطلق أحيانًا على الطبقة [C] اسم «طبقة الوكيل» لخدمة الويب البعيدة، حيث يشير مصطلح proxy إلى عنصر وسيط في سلسلة برمجية. هنا، يُعد الوكيل C الوسيط بين العميل الذي سنقوم بكتابته وخدمة الويب التي قمنا بنشرها.

باستخدام NetBeans 6.5، يمكن إنشاء الوكيل C بالطريقة التالية (للمتابعة، يجب أن تكون خدمة الويب نشطة على خادم Glassfish):

  • في [1]، أضف عنصرًا جديدًا إلى مشروع Java
  • في [2]، حدد الفرع [Web services]
  • في [3]، حدد [Web Service Client]
  • في [4]، أدخل عنوان URI لملف WSDL الخاص بخدمة الويب. وقد تم عرض عنوان URI هذا في الفقرة 4.10.2.
  • في [5]، اترك القيمة الافتراضية [JAX-WS]. القيمة الأخرى الممكنة هي [JAX-RPC]
  • بعد إتمام معالج إنشاء الوكيل لخدمة الويب، تمت إضافة فرع [Web Service References] [6] إلى مشروع NetBeans. يُظهر هذا الفرع الطرق التي توفرها خدمة الويب البعيدة.
  • في علامة التبويب [Files] [7]، تمت إضافة أكواد مصدرية بلغة Java [8]. وهي تتوافق مع الوكيل C الذي تم إنشاؤه.
  • في [9] نجد كود إحدى الفئات. ونلاحظ في [10] أنها وُضعت في حزمة [rdvmedecins.ws]. ولن نعلق على كود هذه الفئات الذي يتسم مرة أخرى بالتعقيد.

بالنسبة لعميل Java الذي نقوم ببنائه حاليًا، يعمل الوكيل C الذي تم إنشاؤه كوسيط. للوصول إلى الطريقة M لخدمة الويب البعيدة، يستدعي عميل Java الطريقة M للوكيل C. وبذلك، يستدعي عميل Java طرقًا محلية (يتم تنفيذها في نفس JVM)، وبطريقة شفافة بالنسبة له، يتم ترجمة هذه الاستدعاءات المحلية إلى استدعاءات بعيدة.

يبقى علينا معرفة كيفية استدعاء الطرق M للوكيل C. لنعد إلى فئة الاختبار JUnit:

في [1]، فئة الاختبار [MainTestsDaoRemote] هي نفسها التي تم استخدامها سابقًا عند اختبار EJB الخاص بالطبقة [dao]:

package dao;
...
public class MainTestsDaoRemote {

   // تم اختبار الطبقة [dao]
  private static IDaoRemote dao;

  @BeforeClass
  public static void init() throws NamingException {
  }

  @Test
  public void test1() {
...
  }
}
  • في السطر [13]، تم الاحتفاظ باختبار test1 دون تغيير.
  • السطر [9]، تم حذف محتوى الطريقة [init].

في هذه المرحلة، يحتوي المشروع على أخطاء لأن طريقة الاختبار [test1] تستخدم الكيانات [Client] و[Medecin] و[Creneau]، [Rv] التي لم تعد موجودة في الحزم نفسها كما في السابق. فهي موجودة الآن في حزمة الوكيل C الذي تم إنشاؤه. يتم حذف التعليمات import المعنية وإعادة إنشائها من خلال عملية «Fix Imports».

لنعد إلى كود فئة الاختبار [MainTestsDaoRemote]:

package dao;
...

public class MainTestsDaoRemote {

   // تم اختبار الطبقة [dao]
  private static IDaoRemote dao;

  @BeforeClass
  public static void init() throws NamingException {
}

يجب أن تقوم الطريقة [init] في السطر 10 بتهيئة مرجع الطبقة [dao] في السطر 7. نحتاج إلى معرفة كيفية استخدام الوكيل C الذي تم إنشاؤه في كودنا. يساعدنا NetBeans في هذه الخطوة.

  • حدد في [1] الطريقة [getAllClients] الخاصة بخدمة الويب، ثم اسحب هذه الطريقة بالماوس وأسقطها داخل الطريقة [init] في فئة الاختبار.

ونحصل على النتيجة [2]. يوضح لنا هذا الهيكل الأساسي للكود كيفية استخدام الوكيل C الذي تم إنشاؤه:

1
2
3
4
5
6
7
8
9
    try { // عملية استدعاء خدمة الويب
      rdvmedecins.ws.WsDaoJpaService service = new rdvmedecins.ws.WsDaoJpaService();
      rdvmedecins.ws.WsDaoJpa port = service.getWsDaoJpaPort();
       // معالجة نتيجة TODO هنا
      java.util.List<rdvmedecins.ws.Client> result = port.getAllClients();
      System.out.println("Result = "+result);
    } catch (Exception ex) {
       // TODO معالجة الاستثناءات المخصصة هنا
}
  • يُظهر لنا السطر [5] أن الطريقة [getAllClients] هي طريقة للكائن من النوع [WsDaoJpa] المُعرَّف في السطر 3. النوع [WsDaoJpa] هو واجهة تحتوي على نفس الطرق التي يعرضها خدمة الويب البعيدة.
  • في السطر [3]، يتم الحصول على الكائن [WsDaoJpa port] من كائن آخر من النوع [WsDaoJpaService] المُعرَّف في السطر 2. يمثل النوع [WsDaoJpaService] الوكيل C الذي تم إنشاؤه محليًّا.
  • قد يفشل الوصول إلى خدمة الويب البعيدة، ولذلك تم تضمين الكود بأكمله في جملة try / catch.
  • توجد كائنات الوكيل C في الحزمة [rdvmedecins.ws]

بمجرد فهم هذا الكود، يتضح أن المرجع المحلي لخدمة الويب البعيدة يمكن الحصول عليه من خلال الكود التالي:

WsDaoJpa dao=new WsDaoJpaService().getWsDaoJpaPort();

وبذلك يصبح كود فئة الاختبار JUnit كما يلي:

package dao;

import rdvmedecins.ws.Client;
import rdvmedecins.ws.Creneau;
import rdvmedecins.ws.Medecin;
import rdvmedecins.ws.Rv;
import rdvmedecins.ws.WsDaoJpa;
import rdvmedecins.ws.WsDaoJpaService;
...

public class MainTestsDaoRemote {

   // تم اختبار طبقة [dao]
  private static WsDaoJpa dao;

  @BeforeClass
  public static void init(){
    dao=new WsDaoJpaService().getWsDaoJpaPort();
  }

  @Test
  public void test1() {
...
  }

   // طريقة مساعدة - تعرض عناصر مجموعة
  private static void display(String message, List elements) {
 ...
  }
}

نحن الآن جاهزون لإجراء الاختبارات:

في [1]، يتم تنفيذ الاختبار JUnit. وفي [2]، يتم اجتيازه بنجاح. إذا نظرنا إلى العروض على وحدة التحكم Netbeans، نجد أسطرًا مثل التالية:

Liste des clients :
rdvmedecins.ws.Client@1982fc1
rdvmedecins.ws.Client@676437
rdvmedecins.ws.Client@1e4853f
rdvmedecins.ws.Client@1e808ca

على جانب الخادم، تحتوي الكيان [Client] على طريقة toString التي تعرض الحقول المختلفة لكائن من النوع [Client]. أثناء الإنشاء التلقائي للوكيل C، يتم إنشاء الكيانات في الوكيل C ولكن مع الحقول الخاصة فقط مصحوبة بأساليب get / set الخاصة بها. وبالتالي، لم يتم إنشاء الأسلوب toString في الكيان [Client] التابع للوكيل C. وهذا ما يفسر العرض السابق. ولا يؤثر ذلك على اختبار JUnit: فقد تم اجتيازه بنجاح. وسنعتبر من الآن فصاعدًا أن لدينا خدمة ويب جاهزة للعمل.