Skip to content

4. اپلیکیشن نمونه – 02: rdvmedecins-jsf2-spring

اکنون پیشنهاد می‌کنیم برنامه قبلی را به محیط Spring/Tomcat منتقل کنیم:

این در واقع یک پورت است. ما با برنامه قبلی شروع می‌کنیم و آن را برای محیط جدید تطبیق می‌دهیم. ما فقط در مورد تغییرات توضیح می‌دهیم. این تغییرات در سه دسته قرار می‌گیرند:

  • سرور دیگر GlassFish نیست بلکه Tomcat است، یک سرور سبک که دارای کانتینر EJB نیست،
  • برای جایگزینی EJB، از Spring، رقیب اصلی EJB و [http://www.springsource.com/]، استفاده خواهیم کرد،
  • پیاده‌سازی مورد استفاده به جای EclipseLink، Hibernate خواهد بود.

از آنجا که ما کپی و پیست زیادی بین پروژه‌های قدیمی و جدید انجام خواهیم داد، پروژه‌های قبلی را در 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>
  • خطوط ۱۸–۲۹: برای Hibernate،
  • خطوط ۳۰–۳۴: برای درایور JDBC از MySQL،
  • خطوط ۳۵–۴۱: برای تست JUnit،
  • خطوط ۴۲–۵۱: برای استخر اتصالات Apache Commons DBCP. استخر اتصالات، استخری از اتصالات باز است. هنگامی که برنامه به یک اتصال نیاز دارد، آن را از استخر درخواست می‌کند. هنگامی که دیگر به آن نیازی ندارد، آن را بازمی‌گرداند. اتصالات هنگام راه‌اندازی برنامه باز می‌شوند و تا پایان عمر برنامه باز می‌مانند. این کار از سربار باز و بسته کردن مکرر اتصالات جلوگیری می‌کند. این نوع استخر در GlassFish وجود داشت اما استفاده از آن برای ما شفاف بود. در اینجا نیز همین‌طور خواهد بود، اما ما باید آن را نصب و پیکربندی کنیم،
  • خطوط ۵۲–۷۵: برای Spring.

بیایید این وابستگی‌ها را اضافه کرده و پروژه را بسازیم:

  • در [1]، پروژه را ساخت می‌کنیم که Maven را مجبور به دانلود وابستگی‌ها می‌کند،
  • در [2]، این وابستگی‌ها سپس در شاخه [Dependencies] ظاهر می‌شوند. تعداد آن‌ها بسیار زیاد است، زیرا خود فریم‌ورک‌های Hibernate و Spring وابستگی‌های زیادی دارند. در اینجا نیز، به لطف Maven، نیازی به نگرانی در مورد آن‌ها نداریم. آن‌ها به طور خودکار دانلود می‌شوند.

اکنون که وابستگی‌ها را در اختیار داریم، کد را از پروژه EJB در لایه [dao] به پروژه Spring در لایه [dao] کپی می‌کنیم:

  • در [1]، آن را در پروژهٔ منبع کپی می‌کنیم،
  • سپس آن را به عنوان [2] در پروژه مقصد می‌چسبانیم،
  • در [3]، نتیجه.

پس از اتمام کپی، خطاها باید اصلاح شوند.

4.1.2. پکیج [exceptions]

کلاس [RdvMedecinsExceptions] [1] به دلیل بسته [javax] در خط ۴ حاوی خطاهایی است که دیگر وجود ندارد. این بسته مختص EJB است. خطای خط ۶ از خطای خط ۴ نشأت می‌گیرد. این دو خط را حذف می‌کنیم. این کار خطاهای [2] را برطرف می‌کند.

4.1.3. پکیج [jpa]

  • در [1]، کلاس [Creneau] به دلیل عدم وجود بسته اعتبارسنجی برای خط [5] نادرست است. ما می‌توانستیم این بسته را به وابستگی‌های پروژه اضافه کنیم. با این حال، در حین تست، Hibernate به‌خاطر آن یک استثنا پرتاب می‌کند. از آنجا که برای برنامه ما ضروری نیست، آن را حذف کرده‌ایم. برای اصلاح کلاس، کافی است تمام خطوط نادرست [2] را حذف کنید. این کار را برای همه کلاس‌های نادرست انجام می‌دهیم.

4.1.4. پکیج [dao]

اکنون به نقطه زیر رسیده‌ایم:

  • در [1]، دو بسته اصلاح‌شده،
  • به [2]، بسته [dao]. از آنجا که EJB دیگر وجود ندارد، مفهوم رابط‌های راه دور و محلی در EJB نیز دیگر وجود ندارد. ما آن‌ها را از [3] حذف می‌کنیم.
  • در [1]، خطاها در کلاس [DaoJpa] دو علت دارند:
  • وارد کردن پکیجی که به EJB مرتبط است (خطوط ۶–۸)؛
  • استفاده از رابط‌های محلی و راه دوری که همین‌الان حذف کردیم.

ما خطوط اشتباه را حذف می‌کنیم و به جای رابط‌های محلی و راه دوری [2]، از رابط [IDao] استفاده می‌کنیم.

در پروژه EJB، کلاس [DaoJpa] یک اشیاء تک‌نمونه (singleton) بود و متدهای آن در داخل یک تراکنش اجرا می‌شدند. ما خواهیم دید که کلاس [DaoJpa] یک بین مدیریت‌شده توسط Spring خواهد بود. به‌طور پیش‌فرض، هر بین Spring یک singleton است. این اولین ویژگی را پوشش می‌دهد. ویژگی دوم با استفاده از annotation @Transactional در Spring روی [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>
  • خط ۳: ما یک نام به واحد پایداری اختصاص می‌دهیم،
  • خط ۳: نوع تراکنش RESOURCE_LOCAL است. در پروژه EJB، این مقدار JTA بود تا نشان دهد که تراکنش‌ها توسط کانتینر EJB مدیریت می‌شدند. مقدار RESOURCE_LOCAL نشان می‌دهد که خود برنامه معاملات را مدیریت می‌کند. این مورد در اینجا از طریق Spring اعمال می‌شود،
  • خطوط ۴–۷: نام‌های کامل چهار موجودیت 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 استفاده کنیم.

  • خطوط ۲–۴: تگ ریشه <beans> فایل پیکربندی. ما در مورد ویژگی‌های مختلف این تگ توضیحی نمی‌دهیم. هنگام کپی و پیست باید دقت کرد، زیرا یک اشتباه در هر یک از این ویژگی‌ها می‌تواند باعث خطاهایی شود که گاهی اوقات درک آن‌ها دشوار است،
  • خط ۷: بیون (bean) «dao» مرجعی به یک نمونه از کلاس [rdvmedecins.dao.DaoJpa] است. یک نمونه واحد (singleton) ایجاد خواهد شد و لایه [dao] برنامه را پیاده‌سازی خواهد کرد،
  • خطوط ۲۴–۲۹: یک منبع داده تعریف شده است. این منبع، سرویس «استخر اتصال» (connection pool) را که پیش‌تر به آن اشاره کردیم، فراهم می‌کند. در اینجا از کلاس [DBCP] پروژه Apache Commons (DBCP, [http://jakarta.apache.org/commons/dbcp/]) استفاده شده است،
  • خطوط ۲۵–۲۸: برای برقراری ارتباط با پایگاه داده مقصد، منبع داده باید درایور JDBC مورد استفاده (خط ۲۵)، و URL پایگاه داده (خط ۲۶)، را بشناسد، کاربر اتصال و رمز عبور آن (خطوط 27–28)،
  • خطوط ۱۰–۲۱: پیکربندی لایه JPA،
  • خط ۱۰: یک بیون (bean) از نوع [EntityManagerFactory] را تعریف می‌کند که قادر به ایجاد اشیایی از نوع [EntityManager] برای مدیریت زمینه‌های پایداری (persistence contexts) است. کلاس نمونه شده [LocalContainerEntityManagerFactoryBean] توسط Spring فراهم می‌شود. این کلاس برای نمونه شدن به تعدادی پارامتر نیاز دارد که در خطوط ۱۱–۲۰ تعریف شده‌اند،
  • خط ۱۱: منبع داده‌ای که برای به‌دست آوردن اتصالات به SGBD استفاده می‌شود. این همان منبع داده [DBCP] است که در خطوط ۲۴–۲۹ تعریف شده است،
  • خطوط ۱۲–۲۰: پیاده‌سازی JPA که باید استفاده شود،
  • خط ۱۳: Hibernate را به عنوان پیاده‌سازی JPA که باید استفاده شود، تعریف می‌کند،
  • خط ۱۴: گویش SQL که Hibernate باید با هدف SGBD استفاده کند، در این مورد MySQL5،
  • خط ۱۶ (توضیح داده شده): درخواست می‌کند که دستورات SQL که توسط Hibernate اجرا می‌شوند، در کنسول ثبت شوند،
  • خط ۱۷ (توضیح داده شده): مشخص می‌کند که هنگام راه‌اندازی برنامه، پایگاه داده باید ایجاد (حذف و ساخت) شود،
  • خط ۳۲: مشخص می‌کند که تراکنش‌ها با استفاده از anotationهای جاوا مدیریت می‌شوند (می‌توانستند در spring-config.xml نیز تعریف شوند). به طور خاص، این anotation @Transactional است که در کلاس [DaoJpa] یافت می‌شود،
  • خطوط ۳۳–۳۵: مدیر تراکنش مورد استفاده را تعریف می‌کنند،
  • خط ۳۳: مدیر تراکنش کلاسی است که توسط Spring ارائه می‌شود،
  • خط ۳۴: مدیر تراکنش Spring باید از کلاس EntityManagerFactory که لایه JPA را مدیریت می‌کند، مطلع باشد. این همان کلاسی است که در خطوط ۱۰ تا ۲۱ تعریف شده است،
  • خط ۴۱: کلاسی را تعریف می‌کند که anotationهای پایداری Spring را مدیریت می‌کند،
  • خط ۳۸: کلاس Spring را تعریف می‌کند که، در میان سایر موارد، انوتیشن @Repository را مدیریت می‌کند؛ این باعث می‌شود کلاسی که این انوتیشن را دارد برای تبدیل استثناءهای بومی از درایور JDBC (SGBD) به استثناءهای عمومی Spring از نوع [DataAccessException] واجد شرایط شود. این تبدیل، استثنای بومی JDBC را در درون نوع [DataAccessException] جای می‌دهد که زیرکلاس‌های مختلفی دارد:

Image

این نگاشت به برنامهٔ مشتری اجازه می‌دهد تا استثناها را به صورت کلی، صرف‌نظر از هدف SGBD، مدیریت کند. ما از anotation @Repository در کد جاوا خود استفاده نکرده‌ایم. بنابراین خط ۳۸ غیرضروری است. ما آن را صرفاً برای اهداف اطلاع‌رسانی باقی گذاشته‌ایم.

ما اکنون کار با فایل پیکربندی Spring را به پایان رسانده‌ایم. این فایل از مستندات Spring گرفته شده است. تطبیق آن با شرایط مختلف اغلب به دو تغییر خلاصه می‌شود:

  • پایگاه داده هدف: خطوط 24–29،
  • پیاده‌سازی JPA: خطوط ۱۲–۲۰.

وقتی کد اجرا می‌شود، تمام بی́n‌های موجود در فایل پیکربندی نمونه سازی خواهند شد. خواهیم دید چگونه.

4.1.7. کلاس تست JUnit

ما قبلاً لایه [DAO] پروژه EJB را با استفاده از تستی به نام JUnit آزمایش کرده بودیم. ما همین کار را برای لایه [DAO] پروژه Spring انجام می‌دهیم:

  • در [1] و [2]، با کپی و پیست کردن تست JUnit بین این دو پروژه،
  • در [3]، تست واردشده در محیط جدید خود دارای خطاهایی است.

خطای گزارش‌شده ([1]) مربوط به رابط دور EJB است که دیگر وجود ندارد. علاوه بر این، کد инициализация برای فیلد [dao] در خط ۱۹، فراخوانی JNDI بود که مختص EJB (خطوط ۲۵–۲۸) بود. برای نمونه‌سازی فیلد [dao] در خط ۱۹، باید از فایل پیکربندی Spring استفاده کنیم. این کار به شرح زیر انجام می‌شود:

  • خط ۲۱: نوع رابط به [IDao] تبدیل شده است،
  • خط ۲۸: همهٔ بیَن‌های تعریف‌شده در فایل [spring-config-dao.xml] را نمونه‌سازی می‌کند، به‌ویژه این یکی:

  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  • خط ۲۹ یک مرجع به bean با id="dao" را از context اسپرینگ در خط ۲۸ درخواست می‌کند. این یک مرجع به singleton [DaoJpa] (class در بالا) را که اسپرینگ instantiate کرده است، بازمی‌گرداند.

خطوط ۲۸–۲۹ بلوک‌های زیر را می‌سازند (خطوط نقطه‌چین صورتی):

هنگامی که تست‌های کلاینت 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]]
  • خطوط ۱–۴: لاگ‌های Spring،
  • خطوط ۵–۱۰: لاگ‌های Hibernate،
  • خط ۱۱: Spring تمام بی́n‌هایی را که ایجاد کرده است، فهرست می‌کند. اولین مورد در این فهرست بی́n [dao] است،
  • از خط ۱۲ به بعد: لاگ‌های تست JUnit،
  • خطوط ۶۰–۶۵: ما می‌توانیم به وضوح استثنایی را که در اثر افزودن یک قرار ملاقات که از قبل در پایگاه داده وجود دارد، ایجاد شده است، ببینیم. به یاد داشته باشید که با 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]، کد منبع جاوا از پروژه EJB به پروژه Spring کپی می‌شود،
  • به [3]؛ کد منبع واردشده در محیط جدید خود دارای خطاهایی است.

ما با حذف رابط‌های دور و محلی از لایه [métier] شروع می‌کنیم، که دیگر در [4] وجود ندارند:

  • در [5]، خطاها در کلاس [Metier] چندین علت دارند:
    • استفاده از بسته [javax.ejb] که دیگر وجود ندارد؛
    • استفاده از رابط [IDaoLocal] که دیگر وجود ندارد؛
    • استفاده از رابط‌های [IMetierRemote] و [IMetierLocal] که دیگر وجود ندارند.

ما

  • تمام خطوط نادرست مربوط به بسته [javax.ejb] را حذف کنید،
  • رابط [IDaoLocal] را با رابط [IDao] جایگزین کنید،
  • رابط‌های [IMetierRemote] و [IMetierLocal] را با رابط [IMetier] جایگزین کنید.
  • به [6]، کلاس بدین ترتیب اصلاح شد،
  • در [7] دیگر هیچ خطایی وجود ندارد.

ما ارجاعات به EJB را حذف کرده‌ایم، اما اکنون باید ویژگی‌های آن را بازیابی کنیم:

 
  • خط ۲۲: ما یک کلاس تک‌نسخه‌ای (singleton) داشتیم. این رفتار با تبدیل کلاس به یک بیون (bean) مدیریت‌شده توسط Spring، محقق می‌شود،
  • خط ۲۳: هر متد در داخل یک تراکنش اجرا می‌شد. این کار با استفاده از anotation @Transactional در Spring انجام خواهد شد،
  • خطوط ۲۷–۲۸: ارجاع به لایه [DAO] از طریق تزریق از کانتینر EJB به دست آمد. ما از تزریق Spring استفاده خواهیم کرد.

بنابراین کد کلاس [Metier] در پروژه Spring به شرح زیر اصلاح می‌شود:

این تمام چیزی است که برای کد جاوا لازم است. بقیه کارها در فایل پیکربندی 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>
  • خط ۲: بین برای لایه [DAO
  • خطوط ۳–۵: بین برای لایه [métier
  • خط ۳: این بین métier نامیده می‌شود (ویژگی id) و یک نمونه از کلاس [rdvmedecins.metier.service.Metier] است (ویژگی class). این بین مانند سایر بین‌ها هنگام راه‌اندازی برنامه ایجاد خواهد شد.

بیایید کد بیون [rdvmedecins.metier.service.Metier] را به یاد بیاوریم:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  //لایه DAO
  private IDao dao;

  public Metier() {
}
  • خط ۸: فیلد [dao] همزمان با بیون métier توسط Spring ایجاد خواهد شد. بیایید به تعریف این بیون در فایل پیکربندی 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>
  • خط ۴: تگ <property> برای مقداردهی اولیه فیلدهای بیون (bean) نمونه شده استفاده می‌شود. نام فیلد توسط ویژگی name مشخص می‌شود. بنابراین، فیلد `dao` از کلاس `[rdvmedecins.metier.service.Metier]` است که نمونه آن ایجاد خواهد شد. این کار از طریق متدی به نام `setDao` انجام می‌شود که باید وجود داشته باشد. مقدار تخصیص‌یافته به آن، مقدار ویژگی `ref` است. در اینجا، این مقدار، مرجع به بین `dao` در خط ۲ است.

به عبارت ساده‌تر، در کد:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // لایه DAO
  private IDao dao;

  public Metier() {
}

فیلد dao در خط ۱۹ توسط Spring با یک مرجع به لایه [dao] مقداردهی اولیه خواهد شد. این همان چیزی است که ما می‌خواستیم. فیلد dao توسط Spring از طریق یک setter مقداردهی اولیه خواهد شد، که باید آن را اضافه کنیم:


  // setter

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

ما نام فایل پیکربندی Spring را برای انعکاس تغییرات تغییر می‌دهیم:

اکنون برای اجرای یک تست آماده هستیم. ما از تست کنسول استفاده خواهیم کرد که برای تست EJB و [Metier] به کار می‌رود.

4.2.4. آزمایش لایه [métier]

آزمون با استفاده از معماری زیر انجام خواهد شد:

ما تست کنسول را از پروژه EJB به پروژه Spring کپی می‌کنیم:

  • در [1] و [2]، هنگام کپی و پیست بین این دو پروژه،
  • در [3]، کد واردشده حاوی خطا است.
 

کد واردشده شامل دو نوع خطا است:

  • خط ۱۳: رابط [IMetierRemote] با رابط [IMetier] جایگزین شده است،
  • خطوط ۲۴–۲۷: لایه [métier] دیگر از طریق فراخوانی JNDI نمونه برداری نمی‌شود، بلکه با نمونه برداری از Beans از فایل پیکربندی Spring انجام می‌شود.

ما این دو نکته را اصلاح می‌کنیم:

  • خط ۲۲: از فایل [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]]
  • خطوط ۱–۴: لاگ‌های Spring و Hibernate،
  • خط ۵: بیین‌هایی که توسط Spring نمونه برداری شده‌اند. توجه کنید به بیین‌های DAO و منطق کسب‌وکار،
  • خطوط ۶–۵۳: لاگ‌های تست. این لاگ‌ها با نتایج به‌دست‌آمده از تست پروژه EJB مطابقت دارند. خواننده را به توضیحات مربوط به آن تست (بخش ۳.۵.۳) ارجاع می‌دهیم.

ما لایه [métier] را ساخته‌ایم. اکنون به لایه نهایی، یعنی [web]، می‌رویم.

4.3. لایه [web]

برای ساخت لایه [web]، همانند دو لایه دیگر، با کپی و پیست از لایه [web] پروژه EJB ادامه خواهیم داد.

4.3.1. پروژه NetBeans

ابتدا یک پروژه وب ایجاد خواهیم کرد:

  • در [1]، یک پروژه جدید ایجاد می‌کنیم،
  • در [2]، یک پروژه Maven از نوع [Web Application
  • در [3]، برای آن نامی انتخاب می‌کنیم،
  • در [4]، این بار به جای GlassFish که برای پروژه EJB استفاده شد، سرور Tomcat را انتخاب می‌کنیم،
  • در [5]، پروژه حاصل،
  • [6]، پروژه پس از حذف [index.jsp] و پکیج از [Source Packages].

4.3.2. وابستگی‌های پروژه

بیایید نگاهی به معماری پروژه بیندازیم:

لایه [web] به لایه‌های [métier]، [DAO] و [JPA] نیاز دارد. این لایه‌ها بخشی از دو پروژه‌ای هستند که همین حالا ساخته‌ایم. بنابراین، به هر یک از این پروژه‌ها وابستگی وجود دارد:

  • در [1]، وابستگی به پروژه Spring / business اضافه می‌کنیم،
  • در [2]، پروژه Spring / business اضافه شده است. از آنجایی که خود این پروژه وابستگی به پروژه 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>
  • خطوط ۷–۱۶ به فایل [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]: کپی کردن کد جاوا از پروژه قدیمی به پروژه جدید. چند خطا وجود دارد. این طبیعی است. ما آنها را اصلاح خواهیم کرد،
  • در [1]، در زبانه [Files] در NetBeans، یک زیرپوشه با نام [resources] را درون پوشه [main] ایجاد کنید،
  • این کار، در برگه [Projects]، شاخه [Other Sources] [3] را ایجاد می‌کند،
  • [1, 2, 3]: فایل‌های پیام از پروژه قدیمی به پروژه جدید کپی می‌شوند.

4.3.4. تغییرات در پروژه واردشده

ما متوجه شدیم که کد جاوای واردشده حاوی خطاهایی بود. بیایید آن‌ها را بررسی کنیم:

  • در [1]، تنها بیون [Application] نادرست است؛
  • در [2]، خطا صرفاً به این دلیل است که رابط [IMetierLocal] دیگر وجود ندارد. در اینجا ممکن است تعجب‌آور باشد که خط ۲۰ به‌عنوان خطا علامت‌گذاری نشده است. توضیحیه @EJB صراحتاً به EJB اشاره می‌کند و در اینجا شناسایی می‌شود. این به دلیل وجود وابستگی [javaee-web-api-6.0] [3] است. جاوا EE 6 معماری‌ای را معرفی کرد که به یک برنامه وب متکی بر EJB بدون رابط دور اجازه می‌دهد تا بر روی سرورهایی که کانتینر EJB را ندارند، مستقر شود. سرور صرفاً باید وابستگی [javaee-web-api-6.0] را فراهم کند. می‌توانیم ببینیم که این دارای دامنه [provided] [3] است.

در اینجا از وابستگی [javaee-web-api-6.0] استفاده نخواهیم کرد. ما [1] را حذف می‌کنیم:

این منجر به خطاهای جدیدی می‌شود: [2]. با خطاهای مربوط به bean [Form] شروع می‌کنیم:

  • در [1]، خطوط نادرست به از دست رفتن بسته [javax] مرتبط هستند. ما همهٔ آن‌ها را از [2] حذف می‌کنیم. خطوط نادرست کلاس [Form] را به یک bean با دامنهٔ جلسه (session-scoped) تبدیل می‌کردند (خطوط ۱۸–۲۰ از [1]). علاوه بر این، bean [Application] در خط ۲۵ تزریق شده بود. این اطلاعات به فایل پیکربندی برای JSF و [faces-config.xml] منتقل خواهد شد.

بیان [Application] را بررسی کنیم:

ما تمام خطوط نادرست را از [1] حذف می‌کنیم و رابط کاربری را در خطوط ۱۳ و ۲۱ از [IMetierLocal] به [IMetier] تغییر می‌دهیم. در [2] دیگر هیچ خطایی وجود ندارد. در [1]، ما خطوط ۱۵–۱۶ را حذف کرده‌ایم که کلاس [Application] را به یک بین با دامنه application. تبدیل می‌کرد. این اطلاعات به فایل پیکربندی برای JSF [faces-config.xml] منتقل خواهد شد. ما همچنین خط ۲۰ را حذف کرده‌ایم که مرجعی به لایه [métier] را به بین تزریق می‌کرد. این مورد اکنون توسط Spring اولیه می‌شود. ما از قبل فایل پیکربندی لازم را داریم؛ همان فایلی است که از پروژه Spring / Business گرفته شده است. ما آن را کپی می‌کنیم:

  • به [1, 2]؛ ما فایل پیکربندی Spring را از پروژه Spring / Business به پروژه Spring / JSF کپی می‌کنیم،

در [3]، نتیجه.

در بیون [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) {
...
    }
...
  }
  • خط ۲۰: بین‌ها از فایل پیکربندی Spring نمونه برداری می‌شوند،
  • خط ۲۱: یک مرجع برای bean تجاری درخواست می‌شود، یعنی در لایه [métier].

به طور کلی، بین‌های اسپرینگ باید در متد init بین با دامنهٔ اپلیکیشن (application-scoped) instance شوند. رویکرد دیگری نیز وجود دارد که در آن بین‌ها توسط یک سرولت اسپرینگ instance می‌شوند. این کار مستلزم اصلاح فایل [web.xml] و افزودن وابستگی به artifact [spring-web] است. ما این کار را در اینجا انجام نداده‌ایم تا با آنچه در کد قبلی استفاده شده بود، سازگار بمانیم.

ما آنوتیشن‌ها را در کلاس‌های [Application] و [Form] که آن‌ها را به عنوان بی́n‌های JSF مشخص می‌کردند، حذف کرده‌ایم. این کلاس‌ها باید همچنان بی́n‌های 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>
  • خطوط ۱۰–۱۹ فایل پیام را پیکربندی می‌کنند. این تنها پیکربندی بود که در پروژه JSF / EJB داشتیم؛
  • خطوط 21–35 بیین‌ها را برای برنامه JSF اعلام می‌کنند. این روش استاندارد در JSF 1.x بود. JSF 2 انوتیشن‌ها را معرفی کرد، اما روشی که در JSF و 1.x استفاده می‌شود هنوز پشتیبانی می‌شود،
  • خطوط ۲۱–۲۵: بیانه bean applicationBean را اعلام کنید،
  • خط ۲۲: نام bean. ممکن است وسوسه شوید از نام 'application' استفاده کنید. از این کار خودداری کنید، زیرا این نام یک bean از پیش تعریف‌شده در JSF است،
  • خط ۲۳: نام کامل کلاس بین،
  • خط ۲۴: دامنه آن،
  • خطوط ۲۷–۳۵: تعریف کردن فرم‌بین،
  • خط ۲۸: نام بین،
  • خط ۲۹: نام کامل کلاس بین،
  • خط ۳۰: دامنه آن،
  • خطوط ۳۱–۳۴: تعریف یک ویژگی از کلاس [beans.Form
  • خط ۳۲: نام خاصیت. کلاس [beans.Form] باید دارای یک فیلد با این نام و متد ستر (setter) متناظر باشد،
  • خط ۳۳: مقدار فیلد. در اینجا، این ارجاع به بین applicationBean است که در خط ۲۱ تعریف شده است. بنابراین ما بیونِ دامنه‌ای application را به بیونِ دامنه‌ای session تزریق می‌کنیم تا دومی به داده‌های موجود در دامنه‌ای application دسترسی داشته باشد.

قبلاً اشاره کردیم که فیلد [application] از بیون [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

خطوط ۲ و ۴ نشان‌دهنده یک خطا هنگام خاموش شدن برنامه هستند. خط ۴ پیشنهاد می‌کند احتمال نشت حافظه وجود دارد. در واقع، این مشکل رخ می‌دهد و پس از مدتی، NetBeans غیرقابل استفاده می‌شود. این مشکل به‌ویژه آزاردهنده است زیرا به این معنی است که هر بار که پروژه را اجرا می‌کنید، باید NetBeans را مجدداً راه‌اندازی کنید. این مشکل قبلاً در سند «مقدمه‌ای بر Struts 2 از طریق مثال‌ها» [http://tahe.developpez.com/java/struts2] مورد توجه قرار گرفته است.

اطلاعات زیادی در اینترنت درباره این خطا موجود است. این خطا زمانی رخ می‌دهد که یک برنامه به طور مکرر از Tomcat بارگذاری و تخلیه می‌شود. پس از مدتی، خطای java.lang.OutOfMemoryError: PermGen space نمایش داده می‌شود. به نظر می‌رسد هیچ راهی برای جلوگیری از این خطا وجود ندارد، زمانی که علت آن آرشیوهای شخص ثالث (JARها) باشد، همانطور که در اینجا اتفاق افتاده است. بنابراین برای رفع آن باید Tomcat را مجدداً راه‌اندازی کنید.

با این حال، می‌توان وقوع این خطا را به تأخیر انداخت. ابتدا اندازه حافظه‌ای را که سرریز شده است افزایش دهید.

  • در [1]، به ویژگی‌های سرور Tomcat بروید،
  • در [2]، در زبانه [Platform]، مقدار مربوط به سرریز حافظه را تنظیم کنید. در اینجا آن را روی ۱ گیگابایت تنظیم کرده‌ایم زیرا در مجموع ۸ گیگابایت حافظه داشتیم. اگر حافظه‌ی کمتری دارید می‌توانید آن را روی 512M (۵۱۲ مگابایت) تنظیم کنید.

سپس، درایور JDBC را از MySQL به <tomcat>/lib کپی کنید، که در آن <tomcat> مسیر نصب Tomcat است.

  • در [1]، در ویژگی‌های Tomcat، توجه کنید که دایرکتوری نصب آن <tomcat> است،
  • در <tomcat>/lib [2]، یک درایور جدید JDBC را قرار دهید (به جای MySQL و [3]).

سپس وابستگی پروژه به درایور JDBC را حذف کرده و آن را با MySQL یا [4] جایگزین کنید.

پس از انجام این کار، برنامه را آزمایش می‌کنیم. متوجه می‌شویم که می‌توانیم برنامه را بارها بارگذاری و تخلیه کنیم. با این حال، مشکلات نشت حافظه حل نشده‌اند؛ آن‌ها صرفاً دیرتر رخ می‌دهند.

4.4. Conclusion

ما اپلیکیشن JSF / EJB / GlassFish را به یک محیط JSF / Spring / Tomcat منتقل کردیم. این کار عمدتاً با کپی و پیست بین دو پروژه انجام شد. این امر ممکن شد زیرا Spring و EJB3 شباهت‌های زیادی با یکدیگر دارند. در واقع، EJB3 پس از آن ایجاد شد که Spring کارآمدتر از EJB2 ثابت شد. سپس EJB3 بهترین ایده‌ها را از Spring گنجاند.

4.5. آزمایش‌ها با اکلیپس

  • در [1]، سه پروژه Spring را وارد می‌کنیم،
  • در [2]، تست JUnit را از لایه [DAO] انتخاب کرده و آن را در [3] اجرا می‌کنیم،
  • در [4]، تست با موفقیت انجام می‌شود،
  • در [5]، کنسول لاگ می‌کند.
  • در [6A] [6B]، کلاینت کنسول برای لایه [métier] اجرا می‌شود،
  • در [7]، خروجی کنسول حاصل،
  • در [8] [9]، پروژه وب روی سرور Tomcat 7 [10] اجرا می‌شود،
  • در [11]، صفحهٔ اصلی برنامه در مرورگر داخلی Eclipse نمایش داده می‌شود.