Skip to content

4. سرویس وب J2EE برای قرار ملاقات‌ها

بیایید به معماری برنامه‌ای که قرار است ساخته شود بازگردیم:

در این بخش، ما بر ساخت سرویس وب J2EE [1] تمرکز خواهیم کرد که بر روی سرور Sun / GlassFish اجرا می‌شود.

4.1. پایگاه داده

پایگاه داده، که آن را [dbrdvmedecins] می‌نامیم، یک پایگاه داده MySQL5 است که شامل چهار جدول می‌باشد:

Image

4.1.1. جدول [MEDECINS]

این پایگاه داده حاوی اطلاعاتی دربارهٔ پزشکان مدیریت‌شده توسط برنامهٔ [RdvMedecins] است.

  • ID: شماره شناسایی پزشک – کلید اصلی جدول
  • VERSION: شماره‌ای که نسخهٔ سطر در جدول را شناسایی می‌کند. این شماره هر بار که تغییری در سطر ایجاد می‌شود، یک واحد افزایش می‌یابد.
  • NOM: نام خانوادگی پزشک
  • PRENOM: نام کوچک آنها
  • TITRE: عنوان آنها (خانم، بانو، آقا)

4.1.2. جدول [CLIENTS]

بیماران پزشکان مختلف در جدول [CLIENTS] ثبت می‌شوند:

  • ID: شماره شناسه مشتری – کلید اصلی جدول
  • VERSION: شماره‌ای که نسخهٔ سطر در جدول را شناسایی می‌کند. این شماره هر بار که تغییری در سطر ایجاد می‌شود، یک واحد افزایش می‌یابد.
  • NOM: نام خانوادگی مشتری
  • PRENOM: نام کوچک آنها
  • TITRE: عنوان آنها (خانم، بانو، آقا)

4.1.3. جدول [CRENEAUX]

این جدول بازه‌های زمانی را که ثبت‌نام در RV امکان‌پذیر است، فهرست می‌کند:

  • ID: شماره‌ای که جایگاه زمانی را شناسایی می‌کند – کلید اصلی جدول (ردیف 8)
  • VERSION: عددی که نسخهٔ سطر در جدول را شناسایی می‌کند. این عدد هر بار که تغییری در سطر ایجاد می‌شود، یک واحد افزایش می‌یابد.
  • ID_MEDECIN: شماره‌ای که پزشک مربوط به این اسلات را شناسایی می‌کند – کلید خارجی روی ستون MEDECINS (ID).
  • HDEBUT: زمان شروع اسلات
  • MDEBUT: دقیقه شروع اسلات
  • HFIN: زمان پایان اسلات
  • MFIN: دقایق پایان اسلات

رده‌ی دوم جدول [CRENEAUX] (به [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 هم‌زمان برای یک پزشک ثبت شده‌اند. از دیدگاه برنامه‌نویسی جاوا، درایور پایگاه داده JDBC هنگام وقوع این رویداد یک SQLException را فعال می‌کند.

ورودی مربوط به id که برابر با ۳ است (به [1] در بالا مراجعه کنید)، نشان می‌دهد که یک RV برای اسلات شماره ۲۰ و مشتری شماره ۴ در تاریخ ۲۳ اوت ۲۰۰۶ رزرو شده است. جدول [CRENEAUX] به ما می‌گوید که اسلات شمارهٔ ۲۰ معادل بازهٔ زمانی ۱۶:۲۰ تا ۱۶:۴۰ است و متعلق به پزشک شمارهٔ ۱ (خانم ماری PELISSIER) می‌باشد. جدول [CLIENTS] نشان می‌دهد که مشتری شمارهٔ ۴ خانم بریژیت 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] را که در نمودار بالا نشان داده شده است، پیاده‌سازی می‌کنند. مشخص است که یک برنامه می‌تواند از طریق پروتکل‌های RMI و JNDI به یک EJB راه دور دسترسی پیدا کند. در عمل، این امر مشتریان را به مشتریان جاوا محدود می‌کند. یک سرویس وب از یک پروتکل ارتباطی استاندارد استفاده می‌کند که توسط زبان‌های مختلف پیاده‌سازی شده است: .NET، PHP، C++ و غیره. این چیزی است که ما می‌خواهیم در اینجا با استفاده از یک مشتری .NET نشان دهیم.

برای معرفی مختصر سرویس‌های وب، به دوره [ref1]، بند ۱۴، صفحه ۱۰۹ مراجعه کنید.

یک سرویس وب را می‌توان به دو روش پیاده‌سازی کرد:

  • با استفاده از کلاسی که با @WebService علامت‌گذاری شده و در یک کانتینر وب اجرا می‌شود
  • توسط یک EJB که با @WebService علامت‌گذاری شده و در یک کانتینر EJB اجرا می‌شود

در اینجا از راه‌حل اول استفاده خواهیم کرد:

در دوره [ref1]، پاراگراف ۱۴، صفحه ۱۰۹، مثالی با استفاده از راه‌حل دوم وجود دارد.

4.4. پیکربندی Hibernate برای سرور 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 را اجرا کنید تا BD در دسترس باشد
  • ایجاد یک اتصال NetBeans به پایگاه داده [dbrdvmedecins]
  • در برگه [Files]، در شاخه [Databases] [1]، درایور JDBC MySQL [2] را انتخاب کنید
  • سپس گزینه «اتصال با استفاده از» [3] را برای ایجاد اتصال به پایگاه داده 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]، کategori [Glassfish] را انتخاب کنید و در [7] مشخص کنید که می‌خواهید با انتخاب نوع [JDBC Resource] یک منبع JDBC ایجاد کنید
  • در [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 ذخیره شده است. این فایل توسط NetBeans برای دستور دادن به سرور GlassFish جهت ایجاد منبع «jdbc/dbrdvmedecins» که در خط ۴ تعریف شده است، استفاده خواهد شد.

ایجاد یک واحد پایداری

واحد پایداری [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]، نوع جاوا را که لیست‌های اشیاء بازگردانده‌شده توسط لایه 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]، مشخص کنید که EJBها باید هم رابط محلی و هم رابط راه دوری را پیاده‌سازی کنند
  • ویزارد را به پایان برسانید
  • در [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 جدید و خالی ایجاد می‌کنیم (به بخش ۴.۵ مراجعه کنید):

 
  • پکیج [rdvmedecins.entites] شامل اجزای لایه JPA است
  • پکیج [rdvmedecins.daoEJB را برای لایه [dao] پیاده‌سازی می‌کند
  • پکیج [rdvmedecins.exceptions] یک کلاس استثنای خاص برنامه را پیاده‌سازی می‌کند

در ادامه فرض می‌کنیم خواننده همهٔ مراحل پاراگراف ۴.۵ را دنبال کرده است. او باید برخی از آن‌ها را تکرار کند.

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>
  • خط ۳: نوع تراکنش JTA است: تراکنش‌ها توسط کانتینر EJB3 گلس‌فیش مدیریت خواهند شد
  • خط ۴: یک پیاده‌سازی JPA/Hibernate استفاده می‌شود. برای این منظور، کتابخانه Hibernate به سرور GlassFish اضافه شده است (به بند ۴.۴ مراجعه کنید).
  • خط ۵: منبع داده JTA که توسط لایه JPA استفاده می‌شود، با نام JNDI 'jdbc/dbrdvmedecins' نامگذاری شده است.
  • خط ۸: این خط به‌طور خودکار تولید نمی‌شود. باید به‌صورت دستی اضافه شود. این خط به 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>
  • خطوط ۸–۱۰: ویژگی‌های JDBC منبع داده (URL پایگاه داده، نام کاربری و رمز عبور). پایگاه داده MySQL dbrdvmedecins همان است که در بخش ۴.۱ توصیف شده است.
  • خط ۷: ویژگی‌های استخر اتصال مرتبط با این منبع داده

4.6.2. اشیاء در لایه JPA

بیایید معماری برنامهٔ کلاینت/سرور خود را به یاد بیاوریم:

پروژه NetBeans:

پکیج [rdvmedecins.entites] لایه [Jpa] را پیاده‌سازی می‌کند.

در بخش ۴.۵ دیدیم چگونه به‌طور خودکار انتیتیز JPA را برای یک برنامه تولید کنیم. در اینجا از این تکنیک استفاده نخواهیم کرد، بلکه خودمان انتیتیز را تعریف می‌کنیم. با این حال، این انتیتیزها بخش زیادی از کد تولیدشده در بخش ۴.۵ را در خود خواهند داشت. در اینجا، می‌خواهیم انتیت‌های [Medecin] و [Client] زیرکلاس‌های کلاس [Personne] باشند.

کلاس Person برای نمایش پزشکان و مشتریان استفاده می‌شود:

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 + "]";
  }

// گیرنده‌ها و تنظیم‌کننده‌ها
....
}
  • خط ۳: توجه کنید که کلاس [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);
  }
}
  • خط ۳: کلاس [Client] یک انتیت JPA است
  • خط ۴: این با جدول [clients] مرتبط است
  • خط ۵: از کلاس [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() + "]";
  }

   // متدهای setter و getter
...
}
  • رده‌های ۱۵ تا ۱۷، رابطه «یک به چند» بین جدول [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() + "]";
  }

// گیرنده و تنظیم‌کننده
...
}
  • رده‌های ۱۵–۱۷ رابطه «یک-به-چند» بین جداول پایگاه داده [rv] و [clients] را مدل‌سازی می‌کنند، در حالی که رده‌های ۱۸–۲۰ رابطه رابطه «یک‌به‌چند» بین جدول [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;

   // fields خصوصی
  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);
  }

   // گیرنده‌ها و تنظیم‌کننده‌ها
...
}
  • خط ۶: این کلاس از کلاس [RuntimeException] ارث می‌برد. بنابراین کامپایلر نیازی ندارد که با استفاده از بلوک‌های try/catch به آن رسیدگی شود.
  • خط ۵: تگ @ApplicationException تضمین می‌کند که استثناء توسط استثناء‌ای از نوع [EjbException] «بلعیده» نخواهد شد.

برای درک تگ @ApplicationException، بیایید به معماری سمت سرور بازگردیم:

استثنای نوع [RdvMedecinsException] توسط متدهای EJB لایه [dao] در داخل کانتینر EJB3 پرتاب شده و توسط آن رهگیری می‌شود. بدون تگ @ApplicationException، کانتینر EJB3 استثنایی را که رخ داده است در یک استثنای نوع [EjbException] محصور می‌کند و آن را مجدداً پرتاب می‌کند. ممکن است نخواهید این پوشانندگی رخ دهد و بخواهید به یک استثنای نوع [RdvMedecinsException] اجازه دهید از کانتینر Ejb3 خارج شود. این همان کاری است که anotation @ApplicationException انجام می‌دهد. علاوه بر این، ویژگی (rollback=true) این آنوتیشن به کانtejner EJB3 دستور می‌دهد که اگر استثنایی از نوع [RdvMedecinsException] در داخل متدی که به عنوان بخشی از یک تراکنش با SGBD اجرا می‌شود، رخ دهد، آن تراکنش باید رول‌بک شود. به زبان فنی، این عمل به عنوان انجام یک rollback روی تراکنش شناخته می‌شود.

4.6.4. EJB در لایه [dao]

رابط جاوا [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);
}

رابط محلی EJB [IDaoLocal] به سادگی از رابط قبلی [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 {
...
}
  • خط ۳ نشان می‌دهد که EJB راه دور «rdvmedecins.dao» نامیده می‌شود
  • خط ۴ نشان می‌دهد که تمام متدهای EJB در داخل یک تراکنش که توسط کانtejner EJB3 مدیریت می‌شود، اجرا می‌شوند.
  • خط ۵ نشان می‌دهد که 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);
    }
  }

   // فهرست قرار ملاقات‌ها برای یک پزشک مشخص در یک روز مشخص
   // پزشک: پزشک
   // روز: روز
  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);
    }
  }

   // افزودن یک قرار ملاقات
   // روز: روز قرار ملاقات
   // slot: بازه زمانی قرار ملاقات
   // مشتری: مشتری که وقت ملاقات برای او رزرو شده است
  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);
    }
  }

   // حذف یک قرار ملاقات
   // appointment: قرار ملاقات حذف‌شده
  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);
    }
  }
}
  • خط ۸: شیء `EntityManager` که دسترسی به زمینه پایداری (persistence context) را مدیریت می‌کند. هنگامی که کلاس نمونه سازی می‌شود، این فیلد توسط کانتینر EJB با استفاده از تفسیر `@PersistenceContext` در خط ۷ مقداردهی اولیه خواهد شد.
  • خط ۱۵: پرس‌وجوی JPQL که تمام ردیف‌های جدول [clients] را به صورت لیستی از اشیاء [Client] برمی‌گرداند.
  • خط ۲۲: پرس‌وجوی مشابه برای پزشکان
  • خط ۳۲: یک پرس‌وجوی JPQL که پیوند بین جداول [creneaux] و [medecins] را انجام می‌دهد. این پرس‌وجو با شناسهٔ پزشک پارامترگذاری شده است.
  • خط ۴۳: یک پرس‌وجوی JPQL که پیوندی بین جداول [rv]، [creneaux] و [medecins] برقرار می‌کند، با دو پارامتر: شناسهٔ پزشک و تاریخ قرار ملاقات.
  • خطوط ۵۵–۵۷: ایجاد یک قرار ملاقات، و سپس ذخیره‌سازی آن در پایگاه داده.
  • خط ۶۷: حذف یک قرار ملاقات از پایگاه داده.
  • خط ۷۶: یک پرس‌وجوی SELECT روی پایگاه داده اجرا می‌کند تا بیمار خاصی را پیدا کند
  • خط ۸۵: مشابه مورد بالا، اما برای یک پزشک
  • خط ۹۴: مشابه مورد بالا برای یک قرار ملاقات
  • خط ۱۰۳: همانند مورد بالا برای یک بازه زمانی
  • تمام عملیات‌هایی که از کانتکست پایداری تعریف‌شده در خط ۹ استفاده می‌کنند، احتمالاً با مشکلی در پایگاه داده مواجه خواهند شد. بنابراین همگی در یک بلاک 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 را از آرشیو .jar آن در سرور GlassFish مستقر کنیم.

  • سرور 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>

خط ۵ نشان می‌دهد که لایه JPA از یک منبع داده به نام JTA و c.a.d استفاده می‌کند که توسط کانتینر EJB3 مدیریت شده و با نام «jdbc/dbrdvmedecins» نامگذاری شده است.

در بخش ۴.۵ دیدیم که چگونه این منبع JDBC را با استفاده از NetBeans ایجاد کنیم. در اینجا، نشان می‌دهیم که چگونه این کار را مستقیماً با استفاده از GlassFish انجام دهیم. ما در حال دنبال کردن رویه‌ای هستیم که در بخش ۱۳.۱.۲، صفحه ۷۹ از [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]، با نام کاربری administrator و رمز عبور 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» تضمین می‌کند که پیش از ارائه یک اتصال، پول (pool) بررسی کند که اتصال عملیاتی است. اگر اینطور نباشد، یک اتصال جدید ایجاد می‌کند. این امکان را برای یک برنامه فراهم می‌کند تا پس از یک وقفه موقت با 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] از برنامه ما مستقر شده است، می‌توانیم آن را آزمایش کنیم. ما این کار را با استفاده از کلاینت جاوای زیر انجام خواهیم داد:

کلاس [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);
    }
  }
}
  • خط ۱۳: به ایجاد نمونهٔ پروکسی EJB راه دور توجه کنید. از نام آن، JNDI "rdvmedecins.dao"، استفاده می‌شود.
  • متدهای تست از متدهای ارائه شده توسط EJB استفاده می‌کنند (به بخش 4.6.4 مراجعه کنید).

اگر همه چیز به درستی پیش برود، تست‌ها باید با موفقیت انجام شوند:

 

اکنون که EJB در لایه [dao] عملیاتی شده است، می‌توانیم آن را از طریق یک سرویس وب به صورت عمومی در دسترس قرار دهیم.

4.10. وب سرویس لایه [dao]

برای معرفی مختصر مفهوم سرویس‌های وب، لطفاً به بند ۱۴، صفحهٔ ۱۱۱ از [ref1] مراجعه کنید.

بیایید به معماری سرور برنامهٔ کلاینت/سرور خود بازگردیم:

ما در اینجا بر سرویس وب لایه [dao] تمرکز می‌کنیم. هدف یگانه این سرویس، در دسترس قرار دادن رابط EJB لایه [dao] برای کلاینت‌های چند سکویی است که قادر به ارتباط با یک سرویس وب هستند.

شایان ذکر است که دو روش برای پیاده‌سازی یک سرویس وب وجود دارد:

  • استفاده از کلاسی که با @WebService علامت‌گذاری شده و در یک کانتینر وب اجرا می‌شود
  • از طریق یک EJB که با @WebService علامت‌گذاری شده و در یک کانتینر EJB اجرا می‌شود

ما در اینجا از راه‌حل اول استفاده می‌کنیم. در NetBeans، باید یک پروژه سازمانی (enterprise project) با دو ماژول ایجاد کنیم:

  • ماژول EJB که در کانتینر EJB اجرا خواهد شد: EJB برای لایه [dao].
  • ماژول وب که در کانتینر وب اجرا خواهد شد: سرویس وبی که در حال حاضر در حال ساخت آن هستیم.

ما قصد داریم این پروژهٔ سازمانی را به دو روش بسازیم.

4.10.1. پروژه NetBeans – نسخه ۱

ابتدا یک پروژه 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();
  }

   // فهرست اسلات‌های قرار ملاقات برای یک پزشک مشخص
   // پزشک: پزشک
  @WebMethod
  public List<Creneau> getAllCreneaux(Medecin medecin) {
    return dao.getAllCreneaux(medecin);
  }

   // فهرست قرار ملاقات‌ها برای یک پزشک مشخص در یک روز مشخص
   // پزشک: پزشک
   // روز: روز
  @WebMethod
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour) {
    return dao.getRvMedecinJour(medecin, jour);
  }

   // افزودن یک قرار ملاقات
   // روز: روز قرار ملاقات
   // slot: بازه زمانی قرار ملاقات
   // مشتری: مشتری‌ای که وقت ملاقات برای او رزرو شده است
  @WebMethod
  public Rv ajouterRv(String jour, Creneau creneau, Client client) {
    return dao.ajouterRv(jour, creneau, client);
  }

   // حذف یک قرار ملاقات
   // appointment: قرار ملاقات حذف‌شده
  @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);
  }
}
  • در خط ۴، کلاس [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);
}
  • خط ۳: تذکر @WebService کلاس [WsDaoJpa] را به‌عنوان یک سرویس وب تعیین می‌کند.
  • خطوط ۶–۷: مرجع EJB برای لایه [dao] توسط سرور برنامه به فیلد در خط ۷ تزریق خواهد شد. باید توجه داشت که همواره پیاده‌سازی محلی (در این مورد IDaoLocal) به این روش تزریق می‌شود. این تزریق ممکن است زیرا سرویس وب در همان JVM که EJB اجرا می‌شود، اجرا می‌گردد.
  • تمام متدهای سرویس وب با anotation @WebMethod تگ شده‌اند تا برای کلاینت‌های راه دور قابل مشاهده باشند. متدی که با anotation @WebMethod تگ نشده باشد، داخلی سرویس وب خواهد بود و برای کلاینت‌های راه دور قابل مشاهده نخواهد بود. هر متد M سرویس وب به سادگی متد متناظر M از EJB تزریق‌شده در خط ۷ را فراخوانی می‌کند.

ایجاد این سرویس وب با یک شاخه جدید در پروژه NetBeans منعکس می‌شود:

در [1]، سرویس وب WsDaoJpa را می‌بینیم، و در [2]، متدهایی را که در معرض دید کلاینت‌های راه دور قرار می‌دهد.

بیایید معماری سرویس وب فعلی در حال توسعه را به یاد بیاوریم:

کامپوننت‌های سرویس وب که قصد داریم مستقر کنیم عبارتند از:

  • [1]: ماژول وبی که همین‌الان ساخته‌ایم
  • [2]: ماژول EJB که در مرحله قبلی ساختیم و سرویس وب به آن وابسته است

برای استقرار همزمان آن‌ها، باید دو ماژول را در یک پروژه «enterprise» در 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]؛ آن مستقر می‌شود. می‌توانیم ببینیم که شامل هر دو ماژول است: وب و EJB.

4.10.2. پروژه NetBeans – نسخه ۲

اکنون نشان می‌دهیم چگونه سرویس وب را زمانی که کد منبع ماژول 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] را نمی‌توان از NetBeans روی سرور GlassFish مستقر کرد. یک خطا بازگردانده می‌شود. بنابراین بایستی آرشیو 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 سرویس وب است که پارامتر ؟test به آن اضافه شده است. این 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]، یک پروژه استاندارد جاوا
  • در [2]، کلاس تست
  • در [3]، کلاینت از آرشیو EJB برای دسترسی به تعاریف رابط لایه [dao] و انتیت‌های JPA استفاده می‌کند. توجه داشته باشید که این آرشیو در زیرپوشه [dist] از پوشه ماژول EJB قرار دارد.

برای دسترسی به سرویس وب راه دور، کلاس‌های پروکسی باید تولید شوند:

در نمودار بالا، لایه‌های [2] و [C=Client] با لایه‌های [1] و [S=Serveur] ارتباط برقرار می‌کنند. برای ارتباط با لایه [S]، کلاینت [C] باید یک اتصال شبکه با لایه [S] برقرار کرده و با استفاده از یک پروتکل خاص با آن ارتباط برقرار کند. اتصالات شبکه از نوع TCP و پروتکل حمل‌ونقل HTTP است. لایه [S] که نماینده سرویس وب است، توسط یک سروِلِت جاوا که روی سرور GlassFish اجرا می‌شود، پیاده‌سازی شده است. ما این سرولت را ننوشته‌ایم. تولید آن توسط GlassFish بر اساس anotationهای @Webservice و @WebMethod در کلاس [WsDaoJpa] که ما نوشته‌ایم، خودکار شده است. به همین ترتیب، تولید لایه [C] سمت کلاینت را نیز خودکار خواهیم کرد. لایه [C] گاهی اوقات به عنوان یک لایه پروکسی برای سرویس وب راه دور نامیده می‌شود، که در آن اصطلاح proxy نشان‌دهنده یک عنصر واسطه در یک زنجیره نرم‌افزاری است. در اینجا، پروکسی C به عنوان واسطه بین کلاینتی که قصد نوشتن آن را داریم و سرویس وبی که مستقر کرده‌ایم عمل می‌کند.

با NetBeans 6.5، پروکسی C را می‌توان به صورت زیر تولید کرد (برای ادامه این راهنما، سرویس وب باید روی سرور GlassFish در حال اجرا باشد):

  • در [1]، یک عنصر جدید به پروژه جاوا اضافه کنید
  • در [2]، شاخه [Web services] را انتخاب کنید
  • در [3]، [Web Service Client] را انتخاب کنید
  • در [4]، URI فایل WSDL را از سرویس وب ارائه دهید. این URI در بخش 4.10.2 ارائه شده بود.
  • برای [5]، مقدار پیش‌فرض [JAX-WS] را حفظ کنید. مقدار دیگر ممکن [JAX-RPC] است.
  • پس از تأیید جادوگر ایجاد پروکسی سرویس وب، پروژه NetBeans با شاخه‌ای به نام [Web Service References] [6] گسترش یافت. این شاخه متدهای ارائه شده توسط سرویس وب راه دور را نشان می‌دهد.
  • در برگه‌های [Files] و [7]، کد منبع جاوا اضافه شده است ([8]). این کد، پروکسی C تولیدشده را نشان می‌دهد.
  • در [9]، کد یکی از کلاس‌ها نمایش داده شده است. از [10] می‌توان دید که آن‌ها در یک پکیج به نام [rdvmedecins.ws] قرار داده شده‌اند. ما در مورد کد این کلاس‌ها توضیحی نمی‌دهیم، زیرا بار دیگر نسبتاً پیچیده است.

برای کلاینت جاوایی که در حال حاضر در حال ساخت آن هستیم، پروکسی C تولیدشده به‌عنوان یک واسطه عمل می‌کند. برای دسترسی به متد M سرویس وب از راه دور، کلاینت جاوا متد M پروکسی C را فراخوانی می‌کند. بدین ترتیب کلاینت جاوا متدهای محلی (که در همان 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 حذف و با استفاده از عملیات «اصلاح imports» مجدداً تولید شده‌اند.

بیایید به کد کلاس تست [MainTestsDaoRemote] بازگردیم:

package dao;
...

public class MainTestsDaoRemote {

   // [dao] لایه آزمایش شد
  private static IDaoRemote dao;

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

متد [init] در خط ۱۰ باید مرجع لایه [dao] در خط ۷ را مقداردهی اولیه کند. ما باید بدانیم چگونه از پروکسی C تولیدشده در کد خود استفاده کنیم. NetBeans در این زمینه به ما کمک می‌کند.

  • روش [getAllClients] را با استفاده از ماوس از سرویس وب [1] انتخاب کنید، سپس این روش را بکشید و در روش [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] است که در خط ۳ تعریف شده است. نوع [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 نمی‌کاهد: این تست با موفقیت انجام شد. از این پس سرویس وب را عملیاتی در نظر می‌گیریم.