Skip to content

4. تطبيق نموذجي – 02: rdvmedecins-jsf2-spring

نقترح الآن نقل التطبيق السابق إلى بيئة Spring / Tomcat:

إنها عملية نقل حقيقية. سنبدأ من التطبيق السابق ونقوم بتكييفه مع البيئة الجديدة. لن نعلق إلا على التعديلات. وهي من ثلاثة أنواع:

  • لم يعد الخادم Glassfish بل Tomcat، وهو خادم خفيف لا يحتوي على حاوية EJB،
  • لاستبدال EJB، سنستخدم Spring، المنافس الرئيسي لـ EJB [http://www.springsource.com/
  • سيتم استخدام Hibernate بدلاً من EclipseLink لتنفيذ JPA.

نظرًا لأننا سنقوم بالكثير من عمليات النسخ واللصق بين المشروع القديم والجديد، فإننا نحتفظ بالمشاريع السابقة مفتوحة في Netbeans:

  

يتطلب استخدام إطار عمل Spring بعض المعرفة التي يمكن العثور عليها في [ref7] (انظر الصفحة 166).

4.1. الطبقات [DAO] و [JPA]

4.1.1. مشروع Netbeans

نقوم بإنشاء مشروع Maven من النوع [Java Application]:

  • في [1]، المشروع الذي تم إنشاؤه،
  • في [2]، نفس المشروع بعد إزالة حزم [Source Packages] و [Test Packages] والتبعية [junit-3.8.1].

أصعب ما في مشاريع Maven هو العثور على التبعيات الصحيحة. بالنسبة لمشروع Spring / JPA / Hibernate هذا، فهي كما يلي:


<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-rdvmedecins-spring-dao-jpa</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-rdvmedecins-spring-dao-jpa</name>
  <url>http://maven.apache.org</url>

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

    <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.hibernate.java-persistence</groupId>
      <artifactId>jpa-api</artifactId>
      <version>2.0.Beta-20090815</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
  </dependencies>

</project>
  • الأسطر 18-29: لـ Hibernate،
  • الأسطر 30-34: لبرنامج التشغيل JDBC الخاص بـ MySQL،
  • الأسطر 35-41: للاختبار JUnit،
  • الأسطر 42-51: لمجموعة اتصالات Apache Commons DBCP. مجموعة الاتصالات هي مجموعة من الاتصالات المفتوحة. عندما تحتاج التطبيق إلى اتصال، يطلبه من المجموعة. وعندما لا يعود بحاجة إليه، يعيده. يتم فتح الاتصالات عند بدء تشغيل التطبيق وتظل مفتوحة طوال عمر التطبيق. وهذا يقلل من تكلفة فتح/إغلاق الاتصالات بشكل متكرر. كان هذا النوع من المجموعات موجودًا في Glassfish ولكن استخدامه كان شفافًا بالنسبة لنا. وسيكون الحال كذلك هنا ولكننا بحاجة إلى تثبيته وتهيئته،
  • الأسطر 52-75: لـ Spring.

دعونا نضيف هذه التبعيات ونقوم بتركيب المشروع:

  • في [1]، نقوم ببناء المشروع مما سيجبر Maven على تنزيل التبعيات،
  • في [2]، ثم تظهر هذه التبعيات في الفرع [Dependencies]. وهي كثيرة جدًا، حيث أن إطاري العمل Hibernate و Spring لهما تبعيات عديدة جدًا. وهنا أيضًا، بفضل Maven، لا داعي لأن نهتم بهذه التبعيات. فهي تُحمَّل تلقائيًا.

الآن بعد أن أصبح لدينا التبعيات، نلصق كود مشروع EJB من الطبقة [dao] في مشروع Spring للطبقة [dao]:

  • في [1]، ننسخ إلى المشروع المصدر،
  • في [2]، نلصق في مشروع الوجهة،
  • في [3]، النتيجة.

بمجرد الانتهاء من النسخ، يجب تصحيح الأخطاء.

4.1.2. الحزمة [exceptions]

تحتوي الفئة [RdvMedecinsExceptions] [1] على أخطاء بسبب الحزمة [javax] في السطر 4 التي لم تعد موجودة. إنها حزمة خاصة بـ EJB. تنبع الخطأ في السطر 6 من الخطأ في السطر 4. يتم حذف هذين السطرين. يؤدي ذلك إلى إزالة أخطاء [2].

4.1.3. الحزمة [jpa]

  • في [1]، الفئة [Creneau] خاطئة بسبب عدم وجود حزمة التحقق من صحة السطر [5]. كان من الممكن إضافة هذه الحزمة إلى تبعيات المشروع. ولكن عند الاختبار، أطلق Hibernate استثناءً بسببها. ونظرًا لأنها ليست ضرورية لتطبيقنا، فقد قمنا بإزالتها. لتصحيح الفئة، يكفي حذف جميع الأسطر الخاطئة [2]. نقوم بذلك لجميع الفئات الخاطئة.

4.1.4. الحزمة [dao]

لقد وصلنا إلى النقطة التالية:

  • في [1]، الحزمتان المصححتان،
  • في [2]، الحزمة [dao]. نظرًا لعدم وجود EJB، لم يعد هناك مفهوم الواجهة البعيدة والمحلية لـ EJB. نقوم بحذفها [3].
  • في [1]، تنشأ أخطاء فئة [DaoJpa] من مصدرين:
  • استيراد حزمة مرتبطة بـ EJB (الأسطر 6-8)؛
  • استخدام الواجهات المحلية والبعيدة التي قمنا بحذفها للتو.

نحذف الأسطر الخاطئة ونستخدم واجهة [IDao] بدلاً من الواجهتين المحلية والبعيدة [2].

في مشروع EJB، كانت الفئة [DaoJpa] فئة فردية (singleton) وكانت طرقها تُنفَّذ ضمن معاملة. سنرى أن الفئة [DaoJpa] ستكون حبة يديرها Spring. بشكل افتراضي، كل حبة Spring هي فئة فردية. هذا بالنسبة للخاصية الأولى. أما الثانية فتتم الحصول عليها باستخدام تعليق Spring @Transactional [3]:

وبذلك، لم يعد المشروع يعرض أية أخطاء [4].

4.1.5. تكوين الطبقة [JPA]

في المشروع EJB، قمنا بتكوين الطبقة [JPA] باستخدام الملف [persistence.xml]. لدينا هنا طبقة [JPA]، وبالتالي يتعين علينا إنشاء هذا الملف. في المشروع EJB، قمنا بإنشائه باستخدام Glassfish. هنا، نقوم بإنشائه يدويًا. السبب الرئيسي لذلك هو أن جزءًا من تكوين الملف [persistence.xml] ينتقل إلى ملف تكوين Spring نفسه.

نقوم بإنشاء الملف [persistence.xml]:

بالمحتوى التالي:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="spring-dao-jpa-hibernate-mysqlPU" transaction-type="RESOURCE_LOCAL">
    <class>rdvmedecins.jpa.Client</class>
    <class>rdvmedecins.jpa.Creneau</class>
    <class>rdvmedecins.jpa.Medecin</class>
    <class>rdvmedecins.jpa.Rv</class>
  </persistence-unit>
</persistence>
  • السطر 3: نسمي وحدة الاستمرارية،
  • السطر 3: نوع المعاملات هو RESOURCE_LOCAL. في المشروع EJB، كان JTA للإشارة إلى أن المعاملات كانت تدار بواسطة الحاوية EJB. تشير القيمة RESOURCE_LOCAL إلى أن التطبيق يدير معاملاته بنفسه. وسيكون هذا هو الحال هنا من خلال Spring،
  • السطور 4-7: الأسماء الكاملة للكيانات الأربعة JPA. هذا اختياري لأن Hibernate يبحث عنها تلقائيًا في ClassPath الخاص بالمشروع.

هذا كل شيء. اسم المزود JPA وخصائصه وخصائص مصدر البيانات JDBC موجودة الآن في ملف تكوين Spring.

4.1.6. ملف تكوين Spring

لقد ذكرنا أن الفئة [DaoJpa] هي bean يديرها Spring. ويتم ذلك عن طريق ملف تكوين. سيتضمن هذا الملف أيضًا تكوين الوصول إلى قاعدة البيانات بالإضافة إلى إدارة المعاملات. يجب أن يكون موجودًا في ClassPath الخاص بالمشروع. نضعه في الفرع [Other sources]:

الملف [spring-config-dao.xml] هو التالي:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

  <!-- طبقات التطبيقات -->
  <bean id="dao" class="    " />
  
  <!-- EntityManagerFactory -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
        <!--
        <property name="showSql" value="true" />
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
  </bean>

  <!-- مصدر البيانات DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2" />
    <property name="username" value="root" />
    <property name="password" value="" />
  </bean>

  <!-- مدير المعاملات -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <!-- ترجمة الاستثناءات -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

  <!-- الاستمرارية -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

</beans>

إنه ملف متوافق مع Spring 2.x. لم نسعَ إلى استخدام الميزات الجديدة للإصدارات 3.x.

  • الأسطر 2-4: العلامة الجذرية <beans> لملف التكوين. لن نعلق على السمات المختلفة لهذه العلامة. يجب الحرص على النسخ واللصق لأن الخطأ في إحدى هذه السمات يؤدي إلى أخطاء يصعب فهمها أحيانًا،
  • السطر 7: bean "dao" هو مرجع إلى مثيل من فئة [rdvmedecins.dao.DaoJpa]. سيتم إنشاء مثيل واحد (singleton) وسيقوم بتنفيذ طبقة [dao] للتطبيق،
  • الأسطر 24-29: يتم تعريف مصدر بيانات. يوفر هذا المصدر خدمة "مجمع الاتصالات" التي تحدثنا عنها. يتم هنا استخدام [DBCP] من مشروع Apache commons DBCP [http://jakarta.apache.org/commons/dbcp/
  • الأسطر 25-28: لإنشاء اتصالات مع قاعدة البيانات المستهدفة، يحتاج مصدر البيانات إلى معرفة برنامج التشغيل JDBC المستخدم (السطر 25)، و URL الخاص بقاعدة البيانات (السطر 26)، مستخدم الاتصال وكلمة المرور الخاصة به (الأسطر 27-28)،
  • الأسطر 10-21: تكوين طبقة JPA،
  • السطر 10: يحدد حبة من النوع [EntityManagerFactory] قادرة على إنشاء كائنات من النوع [EntityManager] لإدارة سياقات الاستمرارية. يتم توفير الفئة التي تم إنشاء مثيل لها [LocalContainerEntityManagerFactoryBean] بواسطة Spring. وهي تحتاج إلى عدد معين من المعلمات لإنشاء مثيل لها، محددة في الأسطر 11-20،
  • السطر 11: مصدر البيانات الذي سيتم استخدامه للحصول على اتصالات بـ SGBD. وهو مصدر [DBCP] المحدد في الأسطر 24-29،
  • الأسطر 12-20: التنفيذ JPA المطلوب استخدامه،
  • السطر 13: يحدد Hibernate باعتباره التنفيذ JPA المطلوب استخدامه،
  • السطر 14: اللهجة SQL التي يجب أن يستخدمها Hibernate مع الهدف SGBD، وهو هنا MySQL5،
  • السطر 16 (في التعليقات): يطلب أن يتم تسجيل الأوامر SQL التي ينفذها Hibernate على وحدة التحكم،
  • السطر 17 (في التعليقات): يطلب أن يتم إنشاء قاعدة البيانات (drop و create) عند بدء تشغيل التطبيق،
  • السطر 32: يشير إلى أن المعاملات تدار باستخدام تعليقات توضيحية Java (كان من الممكن أيضًا الإعلان عنها في spring-config.xml). ويتعلق الأمر على وجه الخصوص بالتعليق التوضيحي @Transactional الموجود في الفئة [DaoJpa
  • الأسطر 33-35: تحدد مدير المعاملات الذي سيتم استخدامه،
  • السطر 33: مدير المعاملات هو فئة مقدمة من Spring،
  • السطر 34: يحتاج مدير المعاملات في Spring إلى معرفة EntityManagerFactory الذي يدير الطبقة JPA. وهو المحدد في الأسطر 10-21،
  • السطر 41: يحدد الفئة التي تدير تعليقات Spring الخاصة بالاستمرارية،
  • السطر 38: يحدد فئة Spring التي تدير بشكل خاص التعليق التوضيحي @Repository الذي يجعل الفئة المُعلَّمة بهذه الطريقة مؤهلة لترجمة الاستثناءات الأصلية من برنامج التشغيل JDBC من SGBD إلى استثناءات Spring عامة من النوع [DataAccessException]. يغلف هذا التحويل الاستثناء الأصلي JDBC في نوع [DataAccessException] الذي يحتوي على فئات فرعية متنوعة:

Image

تسمح هذه الترجمة لبرنامج العميل بإدارة الاستثناءات بشكل عام بغض النظر عن الهدف SGBD. لم نستخدم التعليق التوضيحي @Repository في كود Java الخاص بنا. لذا فإن السطر 38 غير ضروري. وقد تركناه لمجرد توضيح المعلومات.

لقد انتهينا من ملف تكوين Spring. وقد تم استخراجه من وثائق Spring. غالبًا ما يقتصر تكييفه مع المواقف المختلفة على تعديلين:

  • تعديل قاعدة البيانات المستهدفة: الأسطر 24-29،
  • تعديل التنفيذ JPA: الأسطر 12-20.

عند تنفيذ الكود، سيتم إنشاء مثيلات لجميع الفاصوليا في ملف التكوين. سنرى كيف.

4.1.7. فئة الاختبار JUnit

لقد قمنا باختبار الطبقة [DAO] من مشروع EJB باستخدام اختبار JUnit. ونفعل الشيء نفسه بالنسبة للطبقة [DAO] من مشروع Spring:

  • في [1] و [2]، نسخ / لصق الاختبار JUnit بين المشروعين،
  • في [3]، يظهر الاختبار المستورد أخطاء في بيئته الجديدة.

الخطأ المبلغ عنه [1] هو خطأ واجهة EJB البعيدة التي لم تعد موجودة. علاوة على ذلك، كان رمز تهيئة الحقل [dao] في السطر 19 عبارة عن استدعاء JNDI خاص بـ EJB (السطور 25-28). لإنشاء مثيل للحقل [dao] في السطر 19، يتعين علينا استخدام ملف تكوين Spring. ويتم ذلك على النحو التالي:

  • السطر 21: أصبح نوع الواجهة [IDao
  • السطر 28: إنشاء مثيل لجميع الفاصوليات المعلنة في الملف [spring-config-dao.xml]، ولا سيما هذه:

  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  • السطر 29 يطلب من سياق Spring في السطر 28 مرجعًا إلى bean الذي له id="dao". نحصل عندئذٍ على مرجع إلى singleton [DaoJpa] (class أعلاه) الذي أنشأه Spring.

تقوم السطران 28-29 ببناء الكتل التالية (الخطوط الوردية المنقطة):

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

يمكننا الآن تنفيذ اختبار JUnit. يجب تشغيل الخادم MySQL. النتائج هي كما يلي:

نجح الاختبار JUnit. دعونا نراجع سجلات الاختبار كما تم في اختبار EJB:

mai 24, 2012 5:10:29 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@67291453: startup date [Thu May 24 17:10:29 CEST 2012]; root of context hierarchy
mai 24, 2012 5:10:29 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
...
mai 24, 2012 5:10:30 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
mai 24, 2012 5:10:30 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.2}
mai 24, 2012 5:10:30 PM org.hibernate.cfg.Environment <clinit>
...
Infos: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6affe94b: defining beans [dao,entityManagerFactory,dataSource,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,txManager,org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0]; جذر التسلسل الهرمي للمصنع
Liste des clients :
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
Liste des médecins :
Médecin[1,Mme,Marie,PELISSIER]
Médecin[2,Mr,Jacques,BROMARD]
Médecin[3,Mr,Philippe,JANDOT]
Médecin[4,Melle,Justine,JACQUEMOT]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER]
Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
Ajout d'un Rv le [Thu May 24 17:10:30 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
après persist : Rv[216, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Rv ajouté
mai 24, 2012 5:10:31 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
WARN: SQL Error: 1062, SQLState: 23000
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
Rv[216, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
mai 24, 2012 5:10:31 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
Ajout d'un Rv le [Thu May 24 17:10:30 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
ERROR: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
Rv[216, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Suppression du Rv ajouté
Rv supprimé
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
  • الأسطر 1-4: سجلات Spring،
  • السطور 5-10: سجلات Hibernate،
  • السطر 11: Spring يُبلغ عن جميع الفاصوليا التي قام بإنشاء مثيل لها. نجد في البداية الفاصوليا [dao]،
  • السطور 12 وما يليها: سجلات اختبار JUnit،
  • الأسطر 60-65: نرى بوضوح الاستثناء الناتج عن إضافة موعد موجود بالفعل في قاعدة البيانات. نذكر أنه مع EJB، لم نواجه هذا الاستثناء بسبب مشكلة في التسلسل.

الطبقة [dao] جاهزة للعمل. نقوم الآن بإنشاء الطبقة [métier].

4.2. الطبقة [métier]

نقوم بنفس الطريقة المتبعة مع الطبقة [DAO]، عن طريق النسخ واللصق من المشروع EJB إلى مشروع Spring.

4.2.1. مشروع Netbeans

نقوم بإنشاء مشروع Maven جديد من النوع [Java Application] بعد حذف كل ما لا نريد الاحتفاظ به من [1]:

4.2.2. تبعيات المشروع

في البنية:

تعتمد الطبقة [métier] على الطبقة [dao]. لذلك نضيف تبعية للمشروع السابق:

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

نبدأ بحذف الواجهات البعيدة والمحلية من الطبقة [métier] التي لم تعد موجودة [4]:

  • في [5]، أخطاء الفئة [Metier] لها عدة أسباب:
    • استخدام الحزمة [javax.ejb] التي لم تعد موجودة؛
    • استخدام واجهة [IDaoLocal] التي لم تعد موجودة؛
    • استخدام واجهتي [IMetierRemote] و [IMetierLocal] اللتين لم تعدا موجودتين.

نقوم

  • نحذف جميع الأسطر الخاطئة المرتبطة بالحزمة [javax.ejb
  • ونستبدل الواجهة [IDaoLocal] بالواجهة [IDao
  • ونستبدل الواجهتين [IMetierRemote] و [IMetierLocal] بالواجهة [IMetier].
  • إلى [6]، الفئة التي تم تصحيحها،
  • إلى [7]، ولم تعد هناك أخطاء.

لقد حذفنا الإشارات إلى EJB ولكن علينا الآن العثور على خصائصها:

 
  • السطر 22: كان لدينا عنصر فريد. سيتم الحصول على هذه الخاصية بجعل الفئة حبة يديرها Spring،
  • السطر 23: كانت كل طريقة تتم في معاملة. سيتم الحصول على ذلك باستخدام تعليق Spring @Transactional،
  • السطران 27-28: تم الحصول على المرجع في الطبقة [DAO] عن طريق حقن الحاوية EJB. سنستخدم حقن Spring.

وبالتالي، يتطور كود فئة [Metier] في مشروع Spring على النحو التالي:

هذا كل شيء بالنسبة لرمز Java. أما الباقي فيتم في ملف تكوين Spring.

4.2.3. ملف تكوين Spring

نقوم بنسخ ملف تكوين Spring من مشروع الطبقة [DAO] إلى مشروع الطبقة [métier]. نبدأ بإنشاء الفرع [Other Resources] في مشروع الطبقة [métier] إذا لم يكن موجودًا:

  • في [1]، في علامة التبويب [Files]، نقوم بإنشاء مجلد فرعي للمجلد [main
  • في [2]، يجب أن يُسمى [resources
  • في [3]، في علامة التبويب [Projects]، تم إنشاء الفرع [Other Sources].

يمكننا الانتقال إلى نسخ/لصق ملف تكوين Spring:

  • في [1]، ننسخ ملف المشروع [DAO] إلى المشروع [métier] [2]،
  • إلى [3]، الملف المنسوخ.

يقوم ملف التكوين الذي تم نسخه بتكوين الطبقة [DAO]. نضيف إليه bean لتكوين الطبقة [métier]:

1
2
3
4
5
   <!-- طبقات التطبيقات -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • السطر 2: bean للطبقة [DAO
  • الأسطر 3-5: bean الطبقة [métier
  • السطر 3: يُسمى bean métier (السمة id) وهو مثيل للفئة [rdvmedecins.metier.service.Metier] (السمة class). سيتم إنشاء مثيل لهذا البين مثل البينات الأخرى عند بدء تشغيل التطبيق.

لنتذكر كود bean [rdvmedecins.metier.service.Metier]:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // طبقة DAO
  private IDao dao;

  public Metier() {
}
  • السطر 8: سيتم إنشاء مثيل للحقل [dao] بواسطة Spring في نفس الوقت الذي يتم فيه إنشاء مثيل للبيان métier. لنعد إلى تعريف هذا البيان في ملف تكوين Spring:
1
2
3
4
5
   <!-- طبقات التطبيقات -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • السطر 4: تُستخدم العلامة <property> لتهيئة حقول الكائن الذي تم إنشاء مثيل له. يتم تحديد اسم الحقل بواسطة السمة name. وبالتالي، فإن الحقل dao من الفئة [rdvmedecins.metier.service.Metier] هو الذي سيتم إنشاء مثيل له. وسيتم ذلك عبر طريقة setDao التي يجب أن تكون موجودة. والقيمة التي سيتم تعيينها له هي قيمة السمة ref. وهذه القيمة هي، في هذه الحالة، مرجع bean dao في السطر 2.

ببساطة، في الكود:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // طبقة DAO
  private IDao dao;

  public Metier() {
}

سيتم تهيئة الحقل dao في السطر 19 بواسطة Spring باستخدام مرجع على الطبقة [dao]. وهذا ما كنا نريده. سيتم تهيئة الحقل dao بواسطة Spring عبر setter الذي يجب أن نضيفه:


  // مُعيّن

  public void setDao(IDao dao) {
    this.dao = dao;
}

نقوم بإعادة تسمية ملف تكوين Spring لمراعاة التغييرات:

نحن الآن جاهزون لإجراء الاختبار. نعيد استخدام اختبار وحدة التحكم المستخدم لاختبار EJB [Metier].

4.2.4. اختبار الطبقة [métier]

سيتم إجراء الاختبار باستخدام البنية التالية:

نقوم بنسخ اختبار وحدة التحكم من مشروع EJB إلى مشروع Spring:

  • في [1] و [2]، والنسخ واللصق بين المشروعين،
  • في [3]، يظهر الكود المستورد أخطاء.
 

يحتوي الكود المستورد على نوعين من الأخطاء:

  • السطر 13: تم استبدال الواجهة [IMetierRemote] بالواجهة [IMetier
  • الأسطر 24-27: لم يعد إنشاء مثيل الطبقة [métier] يتم باستخدام استدعاء JNDI بل عن طريق إنشاء مثيلات للـ beans من ملف تكوين Spring.

نقوم بتصحيح هذين الأمرين:

  • السطر 22: يتم استخدام الملف [spring-config-metier-dao.xml]. يتم عندئذٍ إنشاء مثيلات لجميع الحبوب في هذا الملف. ومن بينها، توجد الحبوب التالية:

  <!-- طبقات التطبيقات -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>

يمثل هذان البينان الطبقتين [DAO] و [métier] في بنية الاختبار:

وبعد ذلك، يمكن إجراء الاختبار:

  

وتكون سجلات الاختبار كما يلي:

mai 25, 2012 9:45:07 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@22a92801: startup date [Fri May 25 09:45:07 CEST 2012]; root of context hierarchy
mai 25, 2012 9:45:07 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
....
Infos: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@38a0a058: defining beans [dao,metier,entityManagerFactory,dataSource,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,txManager,org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0]; جذر التسلسل الهرمي للمصنع
Liste des clients :
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
Liste des médecins :
Médecin[1,Mme,Marie,PELISSIER]
Médecin[2,Mr,Jacques,BROMARD]
Médecin[3,Mr,Philippe,JANDOT]
Médecin[4,Melle,Justine,JACQUEMOT]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER]
Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]]
Liste des rendez-vous du médecin Médecin[1,Mme,Marie,PELISSIER], le [Fri May 25 09:45:07 CEST 2012]
Agenda[Médecin[1,Mme,Marie,PELISSIER],25/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
Ajout d'un Rv le [Fri May 25 09:45:07 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
après persist : Rv[220, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Rv ajouté
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Fri May 25 09:45:07 CEST 2012]
Rv[220, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Agenda[Médecin[1,Mme,Marie,PELISSIER],25/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] Rv[220, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
Suppression du Rv ajouté
Rv supprimé
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Fri May 25 09:45:07 CEST 2012]
Agenda[Médecin[1,Mme,Marie,PELISSIER],25/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
  • الأسطر 1-4: سجلات Spring و Hibernate،
  • السطر 5: الفاصوليا التي تم إنشاء مثيل لها بواسطة Spring. تجدر الإشارة إلى فاصوليا dao و metier،
  • السطور 6-53: سجلات الاختبار. وهي متوافقة مع ما تم الحصول عليه من اختبار مشروع EJB. نحيل القارئ إلى تعليقات هذا الاختبار (الفقرة 3.5.3).

لقد أنشأنا الطبقة [métier]. ننتقل الآن إلى الطبقة الأخيرة، وهي الطبقة [web].

4.3. الطبقة [web]

لبناء الطبقة [web]، سنتبع نفس الطريقة المتبعة مع الطبقتين الأخريين، عن طريق النسخ واللصق من الطبقة [web] في المشروع EJB.

4.3.1. مشروع Netbeans

نقوم أولاً بإنشاء مشروع ويب:

  • في [1]، نقوم بإنشاء مشروع جديد،
  • في [2]، مشروع Maven من النوع [Web Application
  • في [3]، نسميه
  • في [4]، نختار هذه المرة خادم Tomcat وليس Glassfish الذي استخدم في المشروع EJB،
  • في [5]، المشروع الناتج،
  • في [6]، المشروع بعد حذف [index.jsp] وحزمة [Source Packages].

4.3.2. تبعيات المشروع

لنلقِ نظرة على بنية المشروع:

تحتاج الطبقة [web] إلى الطبقات [métier] و [DAO] و [JPA]. هذه الطبقات هي جزء من المشروعين اللذين قمنا بإنشائهما للتو. ومن هنا تأتي التبعية لكل من هذين المشروعين:

  • في [1]، نضيف التبعية إلى مشروع Spring / métier،
  • في [2]، تمت إضافة مشروع Spring / métier. ونظرًا لأنه كان يعتمد بدوره على مشروع Spring / DAO / JPA، فقد تمت إضافته تلقائيًا إلى تبعيات [3].

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

طبقة الويب هي طبقة JSF. لذلك نحتاج إلى مكتبات Java Server Faces. لا يحتوي خادم Tomcat عليها. وبالتالي، لن يكون نطاق التبعية [provided] كما كان الحال مع خادم Glassfish، بل سيكون نطاقها [compile]، وهو النطاق الافتراضي عندما لا يتم تحديد نطاق.

نضيف هذه التبعيات مباشرةً في كود [pom.xml]:


<dependencies>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>mv-rdvmedecins-spring-metier</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>2.1.7</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-impl</artifactId>
      <version>2.1.7</version>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  • تمت إضافة الأسطر 7-16 إلى الملف [pom.xml]. هذه هي التبعيات المتعلقة بـ JSF. وهي تلك المستخدمة في مشروع EJB / Glassfish. تجدر الإشارة إلى أنها لا تحتوي على العلامة <scope>. وبالتالي، فإن نطاقها الافتراضي هو [compile]. وبالتالي، سيتم تضمين المكتبة JSF في أرشيف [war] الخاص بمشروع الويب.

بعد إضافة هذه التبعيات إلى الملف [pom.xml]، نقوم بتجميع المشروع حتى يتم تنزيلها.

4.3.3. نقل المشروع JSF / Glassfish إلى المشروع JSF / Tomcat

نقوم بنسخ جميع أكواد مشروع JSF / Glassfish إلى مشروع JSF / Tomcat:

  • [1, 2, 3]: نسخ صفحات الويب من المشروع القديم إلى الجديد،
  • [1, 2, 3]: نسخ أكواد Java من المشروع القديم إلى الجديد. هناك أخطاء. هذا أمر طبيعي. سنقوم بتصحيحها،
  • في [1]، في علامة التبويب [Files] في Netbeans، نقوم بإنشاء مجلد فرعي [resources] في المجلد [main
  • وهذا يؤدي إلى إنشاء الفرع [Other Sources] [3] في علامة التبويب [Projects
  • [1, 2, 3]: يتم نسخ ملفات الرسائل من المشروع القديم إلى المشروع الجديد.

4.3.4. تعديلات على المشروع المستورد

لقد أشرنا إلى أن كود Java المستورد يحتوي على أخطاء. دعونا نلقي نظرة عليها:

  • في [1]، فقط bean [Application] هو الذي يحتوي على خطأ،
  • في [2]، يرجع الخطأ فقط إلى واجهة [IMetierLocal] التي لم تعد موجودة. هنا، قد نستغرب أن السطر 20 لم يُعتبر خطأً. تشير التعليقات التوضيحية @EJB صراحةً إلى EJB ويتم التعرف عليها هنا. ويرجع ذلك إلى وجود التبعية [javaee-web-api-6.0] [3]. جلبت Java EE 6 معها بنية تسمح بنشر تطبيق ويب يعتمد على EJB بدون واجهة بعيدة، على خوادم لا تحتوي على حاوية EJB. يكفي أن يوفر الخادم التبعية [javaee-web-api-6.0]. نلاحظ بالفعل أن نطاقها هو [provided] [3].

لن نستخدم هنا التبعية [javaee-web-api-6.0]. سنقوم بحذفها [1]:

وهذا يؤدي إلى ظهور أخطاء جديدة [2]. سنبدأ بأخطاء bean [Form]:

  • في [1]، ترتبط الأسطر الخاطئة بفقدان الحزمة [javax]. نقوم بحذفها جميعًا [2]. كانت الأسطر الخاطئة تجعل من الفئة [Form] مكونًا ذو نطاق جلسة (الأسطر 18-20 من [1]). بالإضافة إلى ذلك، تم حقن bean [Application] في السطر 25. ستنتقل هذه المعلومات إلى ملف التكوين الخاص بـ JSF [faces-config.xml].

لننتقل إلى bean [Application]:

نحذف جميع الأسطر الخاطئة من [1] ونغير واجهة [IMetierLocal] في السطرين 13 و21 إلى [IMetier]. في [2]، لم تعد هناك أخطاء. في [1]، قمنا بحذف السطرين 15-16 اللذين كانا يجعلان من الفئة [Application] حبة ذات نطاق application.. ستنتقل هذه المعلومات إلى ملف تكوين JSF [faces-config.xml]. كما قمنا بحذف السطر 20 الذي كان يضيف مرجعًا للطبقة [métier] إلى الفول. والآن، سيتم تهيئة هذا الفول بواسطة Spring. لدينا بالفعل ملف التكوين اللازم، وهو ملف مشروع Spring / Métier. نقوم بنسخه:

  • إلى [1, 2]، وننسخ ملف تكوين Spring من مشروع Spring / Métier إلى مشروع Spring / JSF،

في [3]، النتيجة.

في bean [Application]، يجب استخدام ملف التكوين هذا للحصول على مرجع في الطبقة [métier]. ويتم ذلك في طريقة [init]:


package beans;

...
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Application {

  // طبقة الأعمال
  private IMetier metier;
...

  public Application() {
  }

  @PostConstruct
  public void init() {
    try {
      // تجسيد الطبقة [métier]
      ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
      metier = (IMetier) ctx.getBean("metier");
      // يتم تخزين الأطباء والعملاء في ذاكرة التخزين المؤقت
...
    } catch (Throwable th) {
...
    }
...
  }
  • السطر 20: يتم إنشاء مثيلات للبيانات الموجودة في ملف تكوين Spring،
  • السطر 21: يتم طلب مرجع عن bean الخاص بالمجال، وبالتالي عن الطبقة [métier].

بشكل عام، يجب إنشاء مثيلات مكونات Spring في طريقة init الخاصة بالمكون ذي نطاق التطبيق. هناك طريقة أخرى يتم فيها إنشاء مثيلات المكونات بواسطة سيرفلت Spring. وهذا يتطلب تغيير الملف [web.xml] وإضافة تبعية إلى الأرتيفاكت [spring-web]. لم نقم بذلك هنا لتتماشى مع ما تم استخدامه في الأكواد السابقة.

لقد قمنا بإزالة التعليقات التوضيحية في الفئتين [Application] و [Form] التي كانت تجعلهما حبات JSF. يجب أن تظل هاتان الفئتان حبات JSF. وبدلاً من التعليقات التوضيحية، نستخدم ملف التكوين الخاص بـ JSF [WEB-INF / faces.config.xml] لإعلان الفاصوليا.

أصبح هذا الملف كما يلي:


<?xml version='1.0' encoding='UTF-8'?>

<!-- =========== FULL CONFIGURATION FILE ================================== -->

<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">

  <application>
    <!-- ملف الرسائل -->
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
    <!-- البيان applicationBean -->
    <managed-bean>
      <managed-bean-name>applicationBean</managed-bean-name>
      <managed-bean-class>beans.Application</managed-bean-class>
      <managed-bean-scope>application</managed-bean-scope>
    </managed-bean>
    <!-- البيان النموذجي -->
    <managed-bean>
      <managed-bean-name>form</managed-bean-name>
      <managed-bean-class>beans.Form</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope>
      <managed-property>
        <property-name>application</property-name>
        <value>#{applicationBean}</value>
      </managed-property>
    </managed-bean>
</faces-config>
  • تقوم الأسطر 10-19 بتكوين ملف الرسائل. كان هذا هو التكوين الوحيد الذي كان لدينا في مشروع JSF / EJB،
  • تعلن الأسطر 21-35 عن حبوب التطبيق JSF. كانت هذه هي الطريقة القياسية مع JSF 1.x. أدخلت JSF 2 التعليقات التوضيحية، لكن طريقة JSF 1.x لا تزال مدعومة،
  • الأسطر 21-25: تعلن عن bean applicationBean،
  • السطر 22: اسم bean. قد يميل المرء إلى استخدام اسم application. يجب تجنب ذلك لأنه اسم bean محدد مسبقًا في JSF،
  • السطر 23: الاسم الكامل لفئة bean،
  • السطر 24: نطاقه،
  • الأسطر 27-35: تحدد bean form،
  • السطر 28: اسم البين،
  • السطر 29: الاسم الكامل لفئة bean،
  • السطر 30: نطاقه،
  • الأسطر 31-34: تحدد خاصية لفئة [beans.Form
  • السطر 32: اسم الخاصية. يجب أن تحتوي فئة [beans.Form] على حقل بهذا الاسم والـ setter المرتبط به،
  • السطر 33: قيمة الحقل. هنا هي الإشارة إلى الفول applicationBean المحدد في السطر 21. وبالتالي، فإننا نقوم هنا بحقن bean ذي النطاق application في bean ذي النطاق session حتى يتمكن هذا الأخير من الوصول إلى بيانات النطاق application.

ذكرنا أعلاه أن الحقل [application] الخاص بـ bean [beans.Form] سيتم تهيئته عبر setter. لذا يجب إضافته إلى الفئة [beans.Form] إذا لم يكن موجودًا بالفعل:


public void setApplication(Application application) {
    this.application = application;
  }

4.3.5. اختبار التطبيق

أصبح تطبيقنا الآن خاليًا من الأخطاء وجاهزًا للاختبار:

  • في [1]، المشروع المصحح،
  • في [2]، نقوم ببنائه،
  • في [3]، نقوم بتشغيله. يجب تشغيل SGBD MySQL. سيتم عندئذٍ تشغيل خادم Tomcat [4] إذا لم يكن قيد التشغيل، ثم سيتم عرض الصفحة الرئيسية للتطبيق [5]:

من هنا، نجد التطبيق الذي تمت دراسته. نترك للقارئ التحقق من أنه يعمل. الآن، لنوقف التطبيق:

  • في [1]، نقوم بتفريغ التطبيق،
  • في [2]، لم يعد موجودًا.

لنلقِ نظرة الآن على سجلات في Tomcat:

1
2
3
4
5
6
mai 25, 2012 2:15:57 PM org.apache.catalina.loader.WebappClassLoader clearReferencesJdbc
Grave: The web application [/mv-rdvmedecins-spring-jsf2] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
mai 25, 2012 2:15:57 PM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
Grave: The web application [/mv-rdvmedecins-spring-jsf2] appears to have started a thread named [MySQL Statement Cancellation Timer] but has failed to stop it. This is very likely to create a memory leak.
mai 25, 2012 2:15:59 PM org.apache.catalina.startup.HostConfig checkResources
Infos: Repli (undeploy) de l'application web ayant pour chemin de contexte /mv-rdvmedecins-spring-jsf2

تشير السطران 2 و4 إلى حدوث خلل عند إيقاف تشغيل التطبيق. ويشير السطر 4 إلى احتمال وجود تسرب في الذاكرة. وبالفعل، يحدث هذا التسرب وبعد فترة يصبح Netbeans غير قابل للاستخدام. هذه المشكلة مزعجة بشكل خاص لأنه يتعين إعادة تشغيل Netbeans عند كل تشغيل جديد للمشروع. وقد تمت الإشارة إلى هذه المشكلة سابقًا في الوثيقة "مقدمة إلى Struts 2 من خلال الأمثلة" [http://tahe.developpez.com/java/struts2].

تتوفر على الإنترنت الكثير من المعلومات حول هذا الخطأ. يظهر هذا الخطأ عند تحميل/إلغاء تحميل تطبيق من Tomcat بشكل متكرر. بعد فترة، يظهر الخطأ java.lang.OutOfMemoryError: PermGen space. يبدو أنه لا يوجد حل لتجنب هذا الخطأ عندما ينشأ عن أرشيفات خارجية (jar) كما هو الحال هنا. لذا، يجب إعادة تشغيل Tomcat لإزالة هذا الخطأ.

ومع ذلك، يمكن تأخير ظهور هذا الخطأ. أولاً، نقوم بزيادة مساحة الذاكرة التي تم تجاوزها.

  • في [1]، ننتقل إلى خصائص خادم Tomcat،
  • في [2]، في علامة التبويب [Platform]، نحدد قيمة الذاكرة التي تجاوزت سعتها. هنا، قمنا بتعيين 1 غيغابايت لأن الذاكرة الإجمالية كانت 8 غيغابايت. يمكننا تعيين 512M (512 ميغابايت) مع ذاكرة أصغر.

بعد ذلك، نضع برنامج التشغيل JDBC من MySQL في <tomcat>/lib حيث <tomcat> هو دليل تثبيت Tomcat.

  • في [1]، في خصائص Tomcat، نلاحظ أن دليل التثبيت الخاص به هو <tomcat
  • في <tomcat>/lib [2]، نضع برنامج تشغيل JDBC حديث من MySQL [3].

بعد ذلك، نزيل التبعية التي كان المشروع يعتمد عليها في برنامج التشغيل JDBC من MySQL [4].

بعد ذلك، نختبر التطبيق. نلاحظ أنه يمكننا إجراء عمليات تحميل/تفريغ متكررة للتطبيق. ومع ذلك، لم يتم حل مشاكل تسرب الذاكرة. إنها تظهر ببساطة في وقت لاحق.

4.4. الخلاصة

لقد قمنا بنقل التطبيق JSF / EJB / Glassfish إلى بيئة JSF / Spring / Tomcat. وقد تم ذلك بشكل أساسي عن طريق النسخ واللصق بين المشروعين. وقد كان ذلك ممكنًا لأن تقنيات Spring و EJB3 تتشابه إلى حد كبير. فقد تم إنشاء EJB3 بالفعل بعد أن أثبت Spring أنه أكثر كفاءة من EJB2. ثم استفاد EJB3 من الأفكار الجيدة في Spring.

4.5. الاختبارات مع Eclipse

  • في [1]، يتم استيراد المشاريع الثلاثة لـ Spring،
  • في [2]، نختار الاختبار JUnit من الطبقة [DAO] وننفذه في [3]،
  • في [4]، نجح الاختبار،
  • في [5]، سجلات وحدة التحكم.
  • في [6A] [6B]، يتم تشغيل عميل وحدة التحكم للطبقة [métier
  • في [7]، عرض وحدة التحكم الناتج،
  • في [8] [9]، يتم تشغيل مشروع الويب على خادم Tomcat 7 [10]،
  • إلى [11]، يتم عرض الصفحة الرئيسية للتطبيق في متصفح Eclipse الداخلي.