Skip to content

6. الإصدار 2: بنية OpenEJB / JPA

6.1. مقدمة إلى مبادئ النقل

نقدم هنا المبادئ التي ستحكم عملية نقل تطبيق JPA / Spring / Hibernate إلى تطبيق JPA / OpenEJB / EclipseLink. سننتظر حتى الفقرة 6.2 لإنشاء مشاريع Maven.

6.1.1. البنيتان

التنفيذ الحالي باستخدام Spring / Hibernate

التنفيذ المراد إنشاؤه باستخدام OpenEJB / EclipseLink

6.1.2. مكتبات المشاريع

  • لم تعد طبقات [DAO] و [metier] تُنشأ بواسطة Spring. بل يتم إنشاؤها بواسطة الحاوية OpenEJB.
  • تختفي مكتبات حاوية Spring وتكوينها لتحل محلها مكتبات حاوية OpenEJB وتكوينها.
  • يتم استبدال مكتبات الطبقة JPA / Hibernate بمكتبات الطبقة JPA / EclipseLink

  • يصبح الملف [META-INF/persistence.xml] الذي يكوّن الطبقة JPA كما يلي:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="pam-openejb-ui-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
     <!-- الكيانات JPA -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
     <!-- المزود JPA هو EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
     <!-- خصائص المورد -->
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • السطر 3: المعاملات في حاوية EJB هي من النوع JTA (معاملة Java API). كانت من النوع RESOURCE_LOCAL مع Spring.
  • السطر 9: التنفيذ JPA المستخدم هو EclipseLink
  • الأسطر 5-7: الكيانات التي تديرها الطبقة JPA
  • الأسطر 11-13: خصائص المزود EclipseLink
  • السطر 12: عند كل عملية تنفيذ، سيتم إنشاء الجداول

سيتم تحديد خصائص JDBC لمصدر البيانات JTA المستخدم من قبل الحاوية OpenEJB بواسطة ملف التكوين [conf/openejb.conf] التالي:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • السطر 3: يتم استخدام المعرف Default JDBC Database عند العمل مع حاوية OpenEJB مدمجة (embedded) في التطبيق نفسه.
  • السطر 5: نستخدم قاعدة بيانات MySQL [dbpam_eclipselink]

6.1.4. تنفيذ الطبقة [DAO] بواسطة EJB

  • تصبح الفئات التي تنفذ الطبقة [DAO] هي EJB. لنأخذ مثال الفئة [CotisationDao]:

كانت واجهة [ICotisationDao] في إصدار Spring كما يلي:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
   // إنشاء اشتراك جديد
  Cotisation create(Cotisation cotisation);
   // تعديل اشتراك موجود
  Cotisation edit(Cotisation cotisation);
   // حذف اشتراك موجود
  void destroy(Cotisation cotisation);
   // البحث عن اشتراك معين
  Cotisation find(Long id);
   // الحصول على جميع عناصر الاشتراك
  List<Cotisation> findAll();

}

ستقوم EJB بتنفيذ هذه الواجهة نفسها في شكلين مختلفين: محلي وبعيد. يمكن استخدام الواجهة المحلية من قبل عميل يعمل في نفس JVM، بينما يمكن استخدام الواجهة البعيدة من قبل عميل يعمل في JVM آخر.

الواجهة المحلية:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}
  • السطر 6: ترث الواجهة [ICotisationDaoLocal] الواجهة [ICotisationDao] لتتولى جميع طرقها. ولا تضيف أي طرق جديدة.
  • السطر 5: التعليق التوضيحي @Local يجعلها واجهة محلية لواجهة EJB التي ستقوم بتنفيذها.

الواجهة البعيدة:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}
  • السطر 6: ترث الواجهة [ICotisationDaoRemote] الواجهة [ICotisationDao] لتتضمن جميع طرقها. ولا تضيف أي طرق جديدة.
  • السطر 5: التعليق التوضيحي @Remote يجعلها واجهة بعيدة لـ EJB التي ستقوم بتنفيذها.

يتم تنفيذ الطبقة [DAO] بواسطة EJB الذي ينفذ الواجهتين (وهذا ليس إلزاميًا):

1
2
3
4
5
6
@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
  • السطر 1: التعليق التوضيحي @Stateless الذي يجعل الفئة EJB
  • السطر 2: التعليق التوضيحي @TransactionAttribute الذي يجعل كل طريقة في الفئة تُنفَّذ ضمن معاملة.
  • السطر 5: التعليق التوضيحي @PersistenceContext الذي يضخ في الفئة [CotisationDao] الـ EntityManager من الطبقة JPA. وهي مطابقة لما كان موجودًا في إصدار Spring.

عند استخدام الواجهة المحلية للطبقة [DAO]، يتم تشغيل عميل هذه الواجهة في نفس JVM.

فيما سبق، تتبادل الطبقتان [metier] و [DAO] الكائنات بالرجوع إليها. عندما تغير إحدى الطبقات الكائن المشترك، ترى الطبقة الأخرى هذا التغيير.

عند استخدام الواجهة البعيدة للطبقة [DAO]، عادةً ما يتم تشغيل عميل هذه الواجهة في JVM أخرى.

فيما سبق، تتبادل الطبقتان [metier] و [DAO] الكائنات بالقيمة (تسلسل الكائن المتبادل). عندما تقوم إحدى الطبقات بتغيير كائن مشترك، لا ترى الطبقة الأخرى هذا التغيير إلا إذا تم إرجاع الكائن المعدل إليها.

6.1.5. تنفيذ الطبقة [metier] بواسطة EJB

  • تصبح الفئة التي تنفذ الطبقة [metier] هي الأخرى EJB التي تنفذ واجهة محلية وبعيدة. كانت الواجهة الأولية [IMetier] كما يلي:
package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
   // الحصول على كشف الراتب
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // قائمة الموظفين
  List<Employe> findAllEmployes();
}

نقوم بإنشاء واجهة محلية وواجهة بعيدة استنادًا إلى الواجهة السابقة:

1
2
3
4
5
6
7
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{
}
1
2
3
4
5
6
7
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{
}

تقوم EJB من الطبقة [metier] بتنفيذ هاتين الواجهتين:

@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // مرجع حول الطبقات المحلية [DAO]
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;
  • السطران 1-2: يحددان EJB حيث يتم تنفيذ كل طريقة في معاملة.
  • السطر 7: مرجع إلى الواجهة المحلية لـ EJB [CotisationDao].
  • السطر 6: يطلب التعليق التوضيحي @EJB أن يقوم الحاوية EJB بإدخال مرجع إلى الواجهة المحلية لـ EJB [CotisationDao].
  • الأسطر 8-11: نكرر نفس الإجراء للواجهات المحلية لـ EJB و [EmployeDao] و [IndemniteDao].

في النهاية، عند إنشاء مثيل EJB و [Metier]، سيتم تهيئة الحقول في الأسطر 7 و9 و11 بمراجع إلى الواجهات المحلية لثلاثة EJB من الطبقة [DAO]. لذلك نفترض هنا أن الطبقتين [metier] و [DAO] ستُنفّذان في نفس JVM.

6.1.6. عملاء EJB

في المخطط أعلاه، للتواصل مع الطبقة [metier]، يجب أن تحصل الطبقة [ui] على مرجع على الواجهة البعيدة لـ EJB من الطبقة [metier].

في المخطط أعلاه، للتواصل مع الطبقة [metier]، يجب أن تحصل الطبقة [ui] على مرجع على الواجهة المحلية لـ EJB من الطبقة [metier]. تختلف طريقة الحصول على هذه المراجع من حاوية إلى أخرى. بالنسبة للحاوية OpenEJB، يمكن اتباع الخطوات التالية:

مرجع على الواجهة المحلية:

1
2
3
4
5
6
7
8
9
     // يتم تكوين الحاوية المدمجة Open EJB
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // تهيئة السياق JNDI بالخصائص السابقة
    InitialContext initialContext = new InitialContext(properties);
     // إنشاء مثيل الطبقات DAO
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
  • الأسطر 2-5: يتم تهيئة الحاوية OpenEJB.
  • السطر 5: لدينا سياق JNDI (واجهة التسمية والدليل في Java) الذي يسمح بالحصول على مراجع حول EJB. يتم تعيين كل EJB باسم JNDI:
  • (تابع)
    • بالنسبة للواجهة المحلية، نضيف Local إلى اسم EJB (السطور 7-9)
    • بالنسبة للواجهة البعيدة، نضيف Remote إلى اسم EJB

مع Java EE 5، تتغير هذه القواعد وفقًا للحاوية EJB. وهذا يمثل صعوبة. أدخلت Java 6 ترميزًا قابلاً للنقل عبر جميع خوادم التطبيقات.

يسترد الكود السابق المراجع على واجهات EJB المحلية عبر أسمائها JNDI. ذكرنا سابقًا أنه يمكن الحصول عليها أيضًا عبر التعليق التوضيحي @EJB. لذا قد نرغب في كتابة:

@EJB
private IemployeDaoLocal employeDaoLocal ;

لا يتم تفعيل التعليق التوضيحي @EJB إلا إذا كان ينتمي إلى فئة يتم تحميلها بواسطة الحاوية EJB. وهذا هو الحال بالنسبة للفئة [Metier] على سبيل المثال. أما الكود أعلاه، فسيكون تابعًا لفئة console لن يتم تحميلها بواسطة الحاوية EJB. لذا، نحن مضطرون لاستخدام أسماء JNDI و EJB.

فيما يلي، الكود للحصول على مرجع على الواجهة البعيدة لـ EJB [Metier]:

1
2
3
4
5
6
7
8
     // يتم تكوين الحاوية المدمجة Open EJB
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // تهيئة سياق JNDI للحاوية EJB
    InitialContext initialContext = new InitialContext(properties);

     // إنشاء مثيل لطبقة الأعمال البعيدة
metier = (IMetierRemote) initialContext.lookup("MetierRemote");

6.2. العمل العملي

نقترح نقل تطبيق Netbeans Spring / Hibernate إلى بنية OpenEJB / EclipseLink.

التنفيذ الحالي باستخدام Spring / Hibernate

التنفيذ المراد إنشاؤه باستخدام OpenEJB / EclipseLink

إذا لم تكن موجودة، فقم بإنشاء قاعدة البيانات MySQL [dbpam_eclipselink]. إذا كانت موجودة، فاحذف جميع جداولها. قم بإنشاء اتصال Netbeans بهذه القاعدة كما هو موضح في الفقرة 6.2.1.

6.2.2. التكوين الأولي لمشروع Netbeans

  • تحميل مشروع Maven [mv-pam-spring-hibernate]
  • قم بإنشاء مشروع Maven Java جديد [mv-pam-openejb-eclipselink] [1]
  • في علامة التبويب [Files] [2]، قم بإنشاء مجلد [conf] [3] تحت جذر المشروع
  • ضع في هذا المجلد الملف [openejb.conf] [4] التالي:
1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • أنشئ المجلد [src / main/ resources/ META-INF] [5]
  • وضع الملف [persistence.xml] [6] التالي فيه:

<?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="dbpam_eclipselinkPU" transaction-type="JTA">
    <!-- المزود JPA هو EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- كيانات Jpa -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- خصائص المزود EclipseLink -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • السطر 12: نطلب سجلات دقيقة من EclipseLink،
  • السطر 13: سيتم إنشاء الجداول عند إنشاء مثيل الطبقة JPA،
  • أضف المكتبات OpenEJB و EclipseLink بالإضافة إلى برنامج التشغيل JDBC من MySQL إلى الملف [pom.xml] الخاص بالمشروع:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>istia.st</groupId>
  <artifactId>mv-pam-openejb-eclipselink</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-pam-openejb-eclipselink</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>openejb-core</artifactId>
      <version>4.0.0</version>
    </dependency>                
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>eclipselink</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>javax.persistence</artifactId>
      <version>2.0.3</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
  
  <repositories>
    <repository>
      <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
      <id>eclipselink</id>
      <layout>default</layout>
      <name>Repository for library Library[eclipselink]</name>
    </repository>
  </repositories>
  
</project>
  • الأسطر 18-22: التبعية OpenEJB،
  • الأسطر 30-39: التبعيات EclipseLink،
  • الأسطر 41-44: التبعية لبرنامج التشغيل JDBC من MySQL

6.2.3. نقل الطبقة [DAO]

سنقوم بنقل الطبقة [DAO] عن طريق نسخ الحزم من مشروع [mv-pam-spring-hibernate] إلى مشروع [mv-pam-openejb-eclipselink].

  • نسخ الحزم [dao, exception, jpa]
 

تعود الأخطاء المذكورة أعلاه إلى أن الطبقة [DAO] المنسوخة تستخدم Spring وأن مكتبات Spring لم تعد جزءًا من المشروع.

6.2.3.1. EJB [CotisationDao]

نقوم بإنشاء الواجهات المحلية والبعيدة لـ EJB [CotisationDao] المستقبلية:

الواجهة المحلية ICotisationDaoLocal:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}

للحصول على الحزم الصحيحة import، قم بـ [clic droit sur le code / Fix Imports].

الواجهة البعيدة ICotisationDaoRemote:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}

ثم نقوم بتعديل الفئة [CotisationDao] لتحويلها إلى EJB:

...
import javax.persistence.PersistenceContext;
import jpa.Cotisation;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
...  

يختفي import الذي كانت تقوم به هذه الفئة على إطار عمل Spring. إنشاء [Clean and Build] للمشروع:

في [1]، لم تعد هناك أخطاء في الفئة [CotisationDao].

6.2.3.2. EJB و [EmployeDao] و [IndemniteDao]

نكرر نفس الإجراء بالنسبة للعناصر الأخرى في الطبقة [DAO]:

  • الواجهات IEmployeDaoLocal و IEmployeDaoRemote المشتقة من IEmployeDao
  • EJB و EmployeDao اللتان تنفذان هاتين الواجهتين
  • الواجهات IIndemniteDaoLocal و IIndemniteDaoRemote المشتقتان من IIndemniteDao
  • EJB IndemniteDao التي تنفذ هاتين الواجهتين

وبذلك، لم تعد هناك أخطاء في المشروع [2].

6.2.3.3. الفئة [PamException]

تظل الفئة [PamException] كما كانت باستثناء تفصيل واحد:

package exception;

import javax.ejb.ApplicationException;

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

   // رمز الخطأ
  private int code;
...

تمت إضافة السطر 5. للحصول على import الصحيح، قم بإنشاء [Fix imports].

لفهم التعليق التوضيحي للسطر 5، يجب أن نتذكر أن كل طريقة في EJB في طبقتنا [DAO]:

  • يتم تنفيذها في معاملة يبدأها وينهيها الحاوية EJB
  • تطلق استثناءً من النوع [PamException] بمجرد حدوث خطأ ما

عندما تستدعي الطبقة [metier] طريقة M من الطبقة [DAO]، يتم اعتراض هذا الاستدعاء بواسطة الحاوية EJB. يحدث كل شيء كما لو كان هناك فئة وسيطة بين الطبقة [metier] والطبقة [DAO]، تسمى هنا [Proxy EJB]، تعترض جميع الاستدعاءات الموجهة إلى الطبقة [DAO]. عندما يتم اعتراض استدعاء الطريقة M للطبقة [DAO]، يبدأ الوكيل EJB معاملة ثم يمرر زمام الأمور إلى الطريقة M للطبقة [DAO] التي يتم تنفيذها بعد ذلك في هذه المعاملة. تنتهي الطريقة M مع أو بدون استثناء.

  • إذا انتهت الطريقة M بدون استثناء، يعود التنفيذ إلى الوكيل EJB الذي ينهي المعاملة عن طريق التحقق من صحتها بواسطة commit. ثم يتم إعادة تدفق التنفيذ إلى الطريقة المستدعية للطبقة [metier]
  • إذا انتهت الطريقة M بحدوث استثناء، يعود التنفيذ إلى الوكيل EJB الذي ينهي المعاملة بإبطالها بواسطة rollback. بالإضافة إلى ذلك، يقوم بتغليف هذا الاستثناء في نوع EJBException. ثم يتم إعادة تدفق التنفيذ إلى الطريقة المستدعية للطبقة [metier] التي تتلقى بالتالي EJBException. تمنع التعليقة التوضيحية في السطر 5 أعلاه هذا التغليف. وبالتالي، ستتلقى الطبقة [metier] قيمة PamException. علاوة على ذلك، يشير السمة rollback=true إلى الوكيل EJB بأنه عند تلقيه قيمة PamException، يجب عليه إبطال المعاملة.

6.2.3.4. اختبار الطبقة [DAO]

يمكن اختبار طبقتنا [DAO] التي تم تنفيذها بواسطة EJB. نبدأ بنسخ الحزمة [dao] من [Test Packages] من المشروع [mv-pam-springhibernate] إلى المشروع قيد الإنشاء [1]:

نحتفظ فقط باختبار [JUnitInitDB] الذي يقوم بتهيئة قاعدة البيانات ببعض البيانات [2]. نقوم بتغيير اسم الفئة [ JUnitInitDbLocal] إلى [3]. ستستخدم الفئة [JUnitInitDBLocal] الواجهة المحلية لـ EJB من الطبقة [DAO].

نقوم أولاً بتعديل الفئة [JUnitInitDBLocal] بالطريقة التالية:

public class JUnitInitDBLocal {

  static private IEmployeDaoLocal employeDao = null;
  static private ICotisationDaoLocal cotisationDao = null;
  static private IIndemniteDaoLocal indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // يتم تكوين الحاوية المدمجة Open EJB
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // تهيئة السياق JNDI بالخصائص السابقة
    InitialContext initialContext = new InitialContext(properties);
     // إنشاء مثيلات الطبقات المحلية DAO
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
}

...
  • الأسطر 3-5: مراجع على الواجهات المحلية لـ EJB من الطبقة [DAO]
  • السطر 7: @BeforeClass يعلق على الطريقة التي يتم تنفيذها عند بدء الاختبار JUnit
  • الأسطر 10-13: تهيئة الحاوية OpenEJB. هذه التهيئة خاصة وتختلف مع كل حاوية EJB.
  • السطر 13: لدينا سياق JNDI (واجهة التسمية والدليل في Java) الذي يسمح بالوصول إلى EJB عبر الأسماء. مع OpenEJB، يُشار إلى الواجهة المحلية لـ EJB E بـ ELocal وإلى الواجهة البعيدة بـ ERemote.
  • الأسطر 15-17: يُطلب من السياق JNDI مرجعًا للواجهات المحلية لـ EJB [EmployeDao, CotisationDao, IndemniteDao].

يتم إنشاء المشروع (Build)، وتشغيل الخادم MySQL إذا لزم الأمر، ثم تنفيذ الاختبار JUnitInitDBLocal. يُذكر أن الملف [persistence.xml] قد تم تكوينه لإعادة إنشاء الجداول عند كل عملية تشغيل. قبل تشغيل الاختبار، يُفضل حذف أي جداول محتملة من قاعدة البيانات MySQL [dbpam_eclipselink].

  • في [1]، في علامة التبويب [Services]، يتم حذف الجداول من اتصال Netbeans الذي تم إنشاؤه في الفقرة 6.2.1.
  • في [2]، لم تعد قاعدة البيانات [dbpam_eclipselink] تحتوي على جداول
  • في [3]، يتم إنشاء المشروع
  • في [4]، يتم تنفيذ الاختبار JUnitInitDBLocal
  • في [5]، نجح الاختبار
  • في [6]، يتم تحديث اتصال Netbeans
  • في [7]، نرى الجداول الأربعة التي أنشأتها الطبقة JPA. كان الهدف من الاختبار هو ملؤها. نعرض محتوى إحداها
  • في [8]، محتوى الجدول [EMPLOYES]

عرضت الحاوية OpenEJB سجلات في وحدة التحكم:

Infos - PersistenceUnit(name=dbpam_eclipselinkPU, provider=org.eclipse.persistence.jpa.PersistenceProvider) - provider time 396ms
Infos - Jndi(name=CotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=CotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=EmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=EmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=IndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=IndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao) --> Ejb(deployment-id=IndemniteDao)
Infos - existing thread singleton service in SystemInstance() org.apache.openejb.cdi.ThreadSingletonServiceImpl@624a240d
Infos - OpenWebBeans Container is starting...
Infos - Adding OpenWebBeansPlugin : [CdiPlugin]
Infos - All injection points were validated successfully.
Infos - OpenWebBeans Container has started, it took [70] ms.
Infos - Created Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Deployed Application(path=D:\data\istia-1112\netbeans\glassfish\mv-pam\tmp\mv-pam-openejb-eclipselink\classpath.ear)
  • السطران 2-3: الاسمان JNDI و EJB [CotisationDaoLocal
  • السطران 4-5: الاسمان JNDI من EJB و [CotisationDaoRemote
  • السطران 7-8: الاسمان JNDI من EJB [EmployeDaoLocal
  • السطران 9-10: الاسمان JNDI من EJB [EmployeDaoRemote
  • السطران 12-13: الاسمان JNDI من EJB و [IndemniteDaoLocal
  • السطران 14-15: الاسمان JNDI و EJB و [EmployeDaoRemote].

نعيد إجراء الاختبار نفسه، مستخدمين هذه المرة الواجهة البعيدة لـ EJB.

في [1]، تم نسخ الفئة [JUnitInitDBLocal] (نسخ / لصق) إلى [JUnitInitDBRemote]. في هذه الفئة، نستبدل الواجهات المحلية بالواجهات البعيدة:

public class JUnitInitDBRemote {

  static private IEmployeDaoRemote employeDao = null;
  static private ICotisationDaoRemote cotisationDao = null;
  static private IIndemniteDaoRemote indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // يتم تكوين الحاوية المدمجة Open EJB
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // تهيئة سياق JNDI بالخصائص السابقة
    InitialContext initialContext = new InitialContext(properties);
     // إنشاء مثيلات الطبقات البعيدة DAO
    employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
}

بعد ذلك، يمكن تشغيل فئة الاختبار الجديدة. قبل ذلك، باستخدام اتصال Netbeans [dbpam_eclipselink]، احذف الجداول من قاعدة البيانات [dbpam_eclipselink].

 

باستخدام اتصال Netbeans [dbpam_eclipselink]، تحقق من أن قاعدة البيانات قد تم ملؤها.

6.2.4. نقل الطبقة [metier]

سنقوم بنقل الطبقة [metier] عن طريق نسخ حزم المشروع [mv-pam-spring-hibernate] إلى المشروع [mv-pam-openejb-eclipselink].

الأخطاء المذكورة أعلاه [1] ناتجة عن أن الطبقة [metier] المنسوخة تستخدم Spring وأن مكتبات Spring لم تعد جزءًا من المشروع.

6.2.4.1. EJB [Metier]

نتبع نفس الإجراء الموصوف لـ EJB [CotisationDao]. نقوم أولاً بإنشاء الواجهات المحلية والبعيدة لـ EJB [Metier] المستقبلية في [2]. وكلاهما مشتق من الواجهة الأولية [IMetier].

1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{

}
1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{

}

وبعد ذلك، في [3]، نقوم بتعديل الفئة [Metier] لتصبح EJB:

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // إشارات إلى الطبقة المحلية [DAO]
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;

   // الحصول على كشف الراتب
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
     // استرداد المعلومات المتعلقة بالموظف
...
  • السطر 1: التعليق التوضيحي @Stateless يجعل الفئة EJB
  • السطر 2: سيتم تنفيذ كل طريقة في الفئة ضمن معاملة
  • السطر 3: EJB [Metier] تنفذ الواجهتين المحلية والبعيدة اللتين حددناهما للتو
  • السطر 7: ستستخدم EJB [Metier] EJB [CotisationDao] عبر واجهتها المحلية. وهذا يعني أن الطبقتين [metier] و [DAO] يجب أن تعملان في نفس JVM.
  • السطر 6: تعمل العلامة @EJB على جعل الحاوية EJB تقوم بنفسها بإدراج المرجع في الواجهة المحلية لـ EJB [CotisationDao]. الطريقة الأخرى التي صادفناها هي استخدام سياق JNDI.
  • الأسطر 8-11: يتم استخدام نفس الآلية لـ EJB الآخرين في الطبقة [DAO].

6.2.4.2. اختبار الطبقة [metier]

يمكن اختبار طبقتنا [metier] التي تم تنفيذها بواسطة EJB. نبدأ بنسخ الحزمة [metier] من [Test Packages] من المشروع [mv-pam-spring-hibernate] إلى المشروع قيد الإنشاء [1]:

  • إلى [1]، ونتيجة النسخ
  • إلى [2]، يتم حذف الاختبار الأول
  • في [3]، يتم إعادة تسمية الاختبار المتبقي إلى [JUnitMetierLocal]

تصبح الفئة [JUnitMetierLocal] كما يلي:

public class JUnitMetierLocal {

// الطبقة المحلية الخاصة بالعمل
  static private IMetierLocal metier;

  @BeforeClass
  public static void init() throws NamingException {
     // تكوين الحاوية المدمجة Open EJB
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // تهيئة السياق JNDI بالخصائص السابقة
    InitialContext initialContext = new InitialContext(properties);

     // إنشاء مثيلات الطبقات المحلية DAO
    IEmployeDaoLocal employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    ICotisationDaoLocal cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    IIndemniteDaoLocal indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
     // إنشاء مثيل لطبقة الأعمال المحلية
    metier = (IMetierLocal) initialContext.lookup("MetierLocal");

     // إفراغ قاعدة البيانات
...
}
  • السطر 4: إشارة إلى الواجهة المحلية لـ EJB [Metier]
  • الأسطر 8-12: تكوين الحاوية OpenEJB مطابق لتلك التي تمت في اختبار الطبقة [DAO]
  • الأسطر 15-19: يُطلب من سياق JNDI في السطر 12، مراجع حول 3 EJB من الطبقة [DAO] وحول EJB من الطبقة [metier]. ستُستخدم EJB من الطبقة [DAO] لتهيئة قاعدة البيانات، وEJB من الطبقة [metier] لإجراء اختبارات حساب الرواتب.

يؤدي تنفيذ الاختبار [JUnitMetierLocal] إلى النتيجة التالية [1]:

في [2]، يتم تكرار [JUnitMetierLocal] في [JUnitMetierRemote] لاختبار واجهة EJB [Metier] عن بُعد هذه المرة. يتم تعديل كود [JUnitMetierRemote] لاستخدام هذه الواجهة البعيدة. لا يتغير الباقي.

public class JUnitMetierRemote {

   // طبقة الأعمال البعيدة
  static private IMetierRemote metier;

  @BeforeClass
  public static void init() throws NamingException {
     // تكوين الحاوية المدمجة Open EJB
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // تهيئة السياق JNDI بالخصائص السابقة
    InitialContext initialContext = new InitialContext(properties);

     // إنشاء مثيلات الطبقات البعيدة DAO
    IEmployeDaoRemote employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    ICotisationDaoRemote cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    IIndemniteDaoRemote indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
     // إنشاء مثيل لطبقة الأعمال البعيدة
    metier = (IMetierRemote) initialContext.lookup("MetierRemote");

     // إفراغ قاعدة البيانات
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // ملء قاعدة البيانات
    Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
    Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
    indemnite1=indemniteDao.create(indemnite1);
    indemnite2=indemniteDao.create(indemnite2);
    employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }
}
  • السطران 4 و 19: يتم استخدام الواجهة البعيدة لـ EJB [Metier].
  • الأسطر 15-17: يتم استخدام واجهات بعيدة من الطبقة [DAO]
  • السطران 34-35: نظرًا لأن الكائنات التي يتم تبادلها بين العميل والخادم عبر الواجهات البعيدة يتم تبادلها حسب القيمة، يجب استرداد النتيجة التي تعطيها الطريقة create(Indemnite i). لم يكن ذلك إلزاميًا مع الواجهات المحلية حيث يتم تمرير الكائنات حسب المرجع.

وبعد القيام بذلك، يمكن بناء المشروع وتنفيذ الاختبار [JUnitMetierRemote]:

  

6.2.5. نقل الطبقة [console]

سنقوم بنقل الطبقة [console] عن طريق نسخ حزم المشروع [mv-pam-spring-hibernate] إلى المشروع [mv-pam-openejb-eclipselink].

الأخطاء المذكورة أعلاه [1] ناتجة عن أن الطبقة [metier] المنسوخة تستخدم Spring وأن مكتبات Spring لم تعد جزءًا من المشروع. في [2]، تم تغيير اسم الفئة [Main] إلى [MainLocal]. وستستخدم الواجهة المحلية لـ EJB [Metier].

يتطور كود الفئة [MainLocal] على النحو التالي:

  public static void main(String[] args) {
     // البيانات المحلية
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
...
     // هل هناك أخطاء؟
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // حسناً - يمكن طلب كشف الراتب من الطبقة [métier]
    IMetierLocal metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // نقوم بتكوين الحاوية المدمجة Open EJB
      Properties properties = new Properties();
      properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
       // تهيئة السياق JNDI بالخصائص السابقة
      InitialContext initialContext = new InitialContext(properties);
       // إنشاء مثيل لطبقة الأعمال المحلية
      metier = (IMetierLocal) initialContext.lookup("MetierLocal");
       // حساب كشف الراتب
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
      return;
    } catch (Exception ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.toString());
      return;
    }
     // عرض تفصيلي
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
....

توجد التعديلات في الأسطر 13-25. هذه هي الطريقة التي تتغير بها الإشارة إلى الطبقة [metier] (الأسطر 17-22). لن نشرح الكود الجديد الذي سبق أن رأيناه في الأمثلة السابقة. بمجرد إجراء هذه التعديلات، لن يعود المشروع يعرض أية أخطاء (انظر [3]).

نقوم بتكوين المشروع ليتم تنفيذه باستخدام المعلمات [1]:

لكي يعمل تطبيق وحدة التحكم بشكل طبيعي، يجب أن تكون هناك بيانات في قاعدة البيانات. ولذلك، يجب تعديل الملف [META-INF/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="dbpam_eclipselinkPU" transaction-type="JTA">
    <!-- المزود JPA هو EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- كيانات Jpa -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- خصائص المزود EclipseLink -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <!--
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
      -->
    </properties>
  </persistence-unit>
</persistence>

يتم تعليق السطر 14 الذي كان يتسبب في إعادة إنشاء جداول قاعدة البيانات عند كل تشغيل. يجب إعادة بناء المشروع (Clean and Build) حتى يتم أخذ هذا التعديل في الاعتبار. بعد ذلك، يمكن تشغيل البرنامج. إذا سارت الأمور على ما يرام، فسيظهر على وحدة التحكم عرض مشابه لما يلي:

.......
INFO - Created EJB(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
INFO - Deployed Application(path=classpath.ear)
[EL Info]: 2009-09-30 15:09:21.109--ServerSession(16658781)--EclipseLink, version: Eclipse Persistence Services - 1.1.2.v20090612-r4475
[EL Info]: 2009-09-30 15:09:21.937--ServerSession(16658781)--file:/C:/temp/09-09-28/pam-console-metier-dao-openejb-eclipselink-0910/build/classes/-jpa login successful
Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé : 
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2

Informations Cotisations : 
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %

Informations Indemnités : 
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %

Informations Salaire : 
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

BUILD SUCCESSFUL (total time: 4 seconds)

استخدمنا هنا الواجهة المحلية للطبقة [metier]. نستخدم الآن واجهتها البعيدة في فئة وحدة تحكم ثانية:

في [1]، تم نسخ الفئة [MainLocal] إلى [MainRemote]. تم تعديل كود [MainRemote] لاستخدام الواجهة البعيدة للطبقة [metier]:

// لا بأس - يمكننا طلب كشف الراتب من الطبقة [metier]
    IMetierRemote metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // نقوم بتكوين الحاوية المدمجة Open EJB
...
       // إنشاء مثيل لطبقة الأعمال البعيدة
      metier = (IMetierRemote) initialContext.lookup("MetierRemote");
       // حساب كشف الراتب
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
...
    } catch (Exception ex) {
...
    }

تم إجراء التعديلات على السطرين 2 و 8. تم تكوين المشروع [2] لتنفيذ الفئة [MainRemote]. ويؤدي تنفيذه إلى نفس النتائج السابقة.

6.3. Conclusion

لقد أوضحنا كيفية نقل بنية Spring / Hibernate إلى بنية OpenEJB / EclipseLink.

بنية Spring / Hibernate

بنية OpenEJB / EclipseLink

تمت عملية النقل دون صعوبات تذكر لأن التطبيق الأصلي كان منظمًا على شكل طبقات. من المهم فهم هذه النقطة.