Skip to content

5. نسخهٔ ۱: معماری اسپرینگ / JPA

هدف نوشتن یک برنامه کنسولی و یک برنامه گرافیکی برای تولید فیش‌های حقوقی مراقبان کودک است که توسط «مرکز سال‌های اولیه» یک نهاد محلی استخدام شده‌اند. این برنامه معماری زیر را خواهد داشت:

5.1. BD پایگاه داده

داده‌های ثابتی که برای تولید فیش حقوقی لازم است در پایگاه‌دادهٔ dbpam ذخیره خواهد شد. این پایگاه‌داده می‌تواند شامل جداول زیر باشد:

جدول EMPLOYES: شامل اطلاعات مربوط به مراقبان مختلف کودک است

ساختار:

ID
کلید اصلی
VERSION
شماره نسخه – با هر تغییر در سطر افزایش می‌یابد
SS
شماره بیمه اجتماعی ملی کارمند – منحصر به فرد
NOM
نام کارمند
prenom
نام کوچک کارمند
ADRESSE
آدرس آنها
VILLE
شهر او
CODEPOSTAL
کد پستی او
INDEMNITE_ID
کلید خارجی روی فیلد [ID] در جدول [INDEMNITES]

محتوای آن می‌تواند به شرح زیر باشد:

Image

جدول COTISATIONS: شامل درصدهای لازم برای محاسبه حق بیمه تأمین اجتماعی است

ساختار:

ID
کلید اصلی
VERSION
شماره نسخه – با هر اصلاح در سطر افزایش می‌یابد
CSGRDS
درصد: مشارکت اجتماعی کلی + مشارکت در بازپرداخت بدهی اجتماعی
CSGD
درصد: سهم اجتماعی عمومی قابل کسر
SECU
درصد: تأمین اجتماعی، مستمری بیوگی، مستمری پیری
RETRAITE
درصد: مستمری تکمیلی + بیمه بیکاری

محتوای آن می‌تواند به شرح زیر باشد:

Image

نرخ‌های حق بیمه تأمین اجتماعی به کارمند بستگی ندارند. جدول بالا تنها شامل یک سطر است.

جدول INDEMNITES: شامل داده‌های مورد نیاز برای محاسبه حقوق قابل پرداخت است.
ID
کلید اصلی
 
VERSION
شماره نسخه – با هر اصلاح در سطر افزایش می‌یابد
 
INDICE
شاخص پردازش – یکتا
 
BASEHEURE
قیمت خالص به یورو برای یک ساعت آماده‌باش
 
ENTRETIENJOUR
کمک‌هزینه نگهداری به یورو برای هر روز مراقبت
 
REPASJOUR
کمک‌هزینه غذا به یورو برای هر روز مراقبت از کودک
 
INDEMNITESCP
اضافه حقوق تعطیلات. این درصد است که باید به حقوق پایه اعمال شود.
 
  

محتوای آن می‌تواند به شرح زیر باشد:

Image

شایان ذکر است که مزایای دریافتی ممکن است از یک پرستار کودک به دیگری متفاوت باشد. این مزایا در واقع از طریق گروه حقوق به هر پرستار کودک خاص مرتبط می‌شوند. برای مثال، خانم ماری جووینال که رتبه حقوق ۲ دارد (جدول EMPLOYES)، دستمزد ساعتی ۲.۱ یورو دارد (جدول INDEMNITES).

5.2. روش محاسبه دستمزد یک پرستار کودک

اکنون روش محاسبه حقوق ماهانه یک پرستار کودک را تشریح می‌کنیم. این مطلب قصد ندارد روشی را که عملاً استفاده می‌شود منعکس کند. به‌عنوان مثال، حقوق خانم ماری جووینال را در نظر می‌گیریم که در ماه حقوق، به مدت ۲۰ روز و ۱۵۰ ساعت کار کرده است.

عوامل زیر در نظر گرفته می‌شوند:

[TOTALHEURES]: total des heures
travaillées dans le mois

[TOTALJOURS]: total des jours travaillés
dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
حقوق پایه یک پرستار کودک
با استفاده از فرمول زیر محاسبه می‌شود:
[SALAIREBASE]=([TOTALHEURES]
*[BASEHEURE])*(1+
[INDEMNITESCP]/100)
[SALAIREBASE]=
(150*[2.1])*(1+0.15)= 362,25
مبلغ معینی از حق بیمه تأمین اجتماعی
باید از این کسر شود
:

Contribution sociale généralisée et
contribution au remboursement de la
dette sociale :
 [SALAIREBASE]*[CSGRDS/100]

Contribution sociale généralisée déductible :
 [SALAIREBASE]*[CSGD/100]

Sécurité sociale, veuvage, vieillesse :
 [SALAIREBASE]*[SECU/100]

Retraite Complémentaire + AGPF +
Assurance Chômage :
 [SALAIREBASE]*[RETRAITE/100]
CSGRDS : 12,64
CSGD : 22,28
Sécurité sociale : 34,02
Retraite : 28,55
مجموع حق بیمه تأمین اجتماعی:
[COTISATIONSSOCIALES]=
[SALAIREBASE]*(CSGRDS+CSGD
+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
علاوه بر این، پرستار کودک برای هر روز کاری، مستحق دریافت فوق‌العاده معیشت و فوق‌العاده غذا است. بنابراین، او مزایای زیر را دریافت می‌کند:

[Indemnités]=[TOTALJOURS]
*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
در نهایت، حقوق خالص پرداختی به مراقب کودک به شرح زیر است:
[SALAIREBASE]-[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

5.3. نحوه کارکرد برنامه کنسولی

در اینجا مثالی از اجرای برنامه کنسولی در یک پنجره DOS آورده شده است:

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20

Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

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

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

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

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

ما برنامه‌ای خواهیم نوشت که اطلاعات زیر را دریافت می‌کند:

  1. شماره بیمه ملی پرستار کودک (254104940426058 در مثال – خط 1)
  2. تعداد کل ساعات کاری (۱۵۰ در مثال – خط ۱)
  3. تعداد کل روزهای کاری (۲۰ در مثال – خط ۱)

می‌توانیم ببینیم که:

  • رده‌های ۹–۱۴: اطلاعات مربوط به کارمندی را که شماره تأمین اجتماعی او ارائه شده است نمایش می‌دهند
  • رده‌های ۱۷–۲۰: نرخ‌های مشارکت‌های مختلف را نمایش می‌دهند
  • رده‌های ۲۳–۲۶: نمایش مزایای مرتبط با جدول حقوق کارمند (در اینجا، جدول ۲)
  • خطوط ۲۹–۳۳: نمایش اجزای حقوق پرداختی

برنامه هرگونه خطا را نشان می‌دهد:

فراخوانی بدون پارامتر:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar
Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés

فراخوانی با داده‌های نادرست:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar  254104940426058 150x 20x
Le nombre d'heures travaillées [150x] est erroné
Le nombre de jours travaillés [20x] est erroné

تماس با شماره بیمه ملی نادرست:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar  xx 150 20
L'erreur suivante s'est produite : L'employé de n°[xx] est introuvable

5.4. نحوه کار اپلیکیشن گرافیکی

برنامه گرافیکی امکان محاسبه دستمزد مراقبان کودک را از طریق یک فرم Swing فراهم می‌کند:

  • اطلاعاتی که قبلاً به‌عنوان پارامتر به برنامه کنسول ارسال می‌شد، اکنون از طریق فیلدهای ورودی [1, 2, 3] وارد می‌شود.
  • دکمه [4] محاسبه حقوق را آغاز می‌کند
  • این فرم اجزای مختلف حقوق، تا حقوق خالص قابل پرداخت [5] را نمایش می‌دهد

لیست کشویی [1, 6] شماره کارکنان SS را نمایش نمی‌دهد، بلکه نام کامل آنها را نمایش می‌دهد. در اینجا فرض بر این است که هیچ دو کارمندی نام کامل یکسانی ندارند.

5.5. ایجاد پایگاه داده

ما WampServer را اجرا می‌کنیم و از ابزار PhpMyAdmin [1] استفاده می‌کنیم:

  • در [2]، گزینه [Bases de données] را انتخاب می‌کنیم،
  • برای [3]، یک پایگاه داده به نام [dbpam_hibernate] ایجاد کنید،
  • در [4]، پایگاه داده ایجاد می‌شود. آن را انتخاب کنید،
  • در [5]، شما می‌خواهید یک اسکریپت SQL را وارد کنید،
  • به [6]؛ از دکمه [Parcourir] برای انتخاب فایل استفاده کنید،
  • در [7,8]، اسکریپت SQL را انتخاب کنید،
  • در [9]، آن را اجرا کنید،
  • به [10]، جداول ایجاد شده‌اند. محتویات آن‌ها به شرح زیر است:

جدول EMPLOYES

Image

جدول INDEMNITES

Image

جدول COTISATIONS

Image

5.6. پیاده‌سازی JPA

5.6.1. لایه JPA / Hibernate

ما لایه JPA را در محیط زیر پیکربندی خواهیم کرد:

یک برنامه کنسول با پایگاه داده تعامل خواهد داشت. برای این کار، به موارد زیر نیاز داریم:

  • یک پایگاه داده،
  • داشتن درایور JDBC برای SGBD (در این مورد، MySQL
  • لایه JPA را با استفاده از Hibernate پیاده‌سازی کنید،
  • برنامه کنسول را بنویسید.

ما پروژه Maven [mv-pam-jpa-hibernate] [1] را ایجاد می‌کنیم:

در معماری برنامهٔ ما، به اجزای زیر نیاز داریم:

  • پایگاه داده،
  • درایور JDBC برای SGBD و MySQL،
  • لایه JPA / Hibernate (اشیاء و پیکربندی)،
  • برنامه کنسول تست.

5.6.1.1. پایگاه داده

ابتدا پایگاه داده خالی را ایجاد کنیم. ما WampServer را راه‌اندازی کرده و از ابزارهای PhpMyAdmin و [1] استفاده می‌کنیم:

  • در [2]، گزینه [Bases de données] را انتخاب می‌کنیم،
  • در [3]، یک پایگاه داده از [dbpam_hibernate] ایجاد می‌شود،
  • در [4]، پایگاه داده ایجاد می‌شود.

5.6.1.2. پیکربندی لایه JPA

ارتباط بین لایه JDBC و پایگاه داده در فایل [persistence.xml] برقرار می‌شود که لایه JPA را پیکربندی می‌کند. این فایل را می‌توان با استفاده از NetBeans ایجاد کرد:

  • در برگه [services] [1]، شما با استفاده از درایور JDBC از MySQL [2] به پایگاه داده متصل می‌شوید،
  • در [3]، نام پایگاه داده‌ای است که می‌خواهید به آن متصل شوید.
  • در [4]، URL JDBC پایگاه داده،
  • در [5]، بدون رمز عبور به عنوان root وارد شوید،
  • در [6]، می‌توانید اتصال را آزمایش کنید،
  • در [7]، اتصال با موفقیت انجام شد.
  • اتصال به صورت [8] و [9] ظاهر می‌شود،
  • در [10]، یک عنصر جدید به پروژه اضافه می‌شود،
  • در [11]، دسته [Persistence] انتخاب می‌شود، و در [12]، عنصر [Persistence Unit
  • در [13]، ما به این واحد پایداری یک نام می‌دهیم،
  • در [14]، یک پیاده‌سازی Hibernate را انتخاب کنید،
  • در [15]، ما اتصال را که به تازگی به پایگاه داده MySQL ایجاد کرده‌ایم، مشخص می‌کنیم،
  • در [16]، مشخص می‌کنیم که وقتی لایه JPA نمونه برداری می‌شود، باید جداول متناظر با انتیت‌های JPA در پروژه را ایجاد کند.

در پایان ویزارد، فایل [persistence.xml] ایجاد می‌شود:

  • این فایل در یک شاخه جدید از پروژه، در پوشه‌ای به نام [META-INF] [1]، ظاهر می‌شود،
  • که با پوشه [src/main/resources] در پروژه [2,3] مطابقت دارد.

محتویات آن به شرح زیر است:


<?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="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
    </properties>
  </persistence-unit>
</persistence>
  • خط ۳: نام واحد پایداری و نوع تراکنش. RESOURCE_LOCAL نشان می‌دهد که پروژه خود تراکنش‌ها را مدیریت می‌کند. در این مورد، برنامه کنسول باید این کار را انجام دهد،
  • خط ۴: پیاده‌سازی مورد استفاده JPA، Hibernate است،
  • خطوط ۶–۹: جزئیات اتصال پایگاه داده JDBC،
  • خط ۱۱: ایجاد جداول متناظر با انتیت‌های JPA را درخواست می‌کند. در واقع، NetBeans در اینجا پیکربندی نادرستی تولید می‌کند. پیکربندی باید به صورت زیر باشد:

      <property name="hibernate.hbm2ddl.auto" value="create"/>

با گزینه «create»، Hibernate هنگام ایجاد لایه JPA، جدول‌های مربوط به انتیت‌های JPA را حذف کرده و سپس ایجاد می‌کند. گزینه create-drop همین کار را انجام می‌دهد، اما در پایان چرخه عمر لایه JPA، تمام جدول‌ها را حذف می‌کند. گزینه دیگری نیز وجود دارد:


      <property name="hibernate.hbm2ddl.auto" value="update"/>

این گزینه در صورتی که جدول‌ها وجود نداشته باشند، آن‌ها را ایجاد می‌کند، اما اگر از قبل وجود داشته باشند، آن‌ها را حذف نمی‌کند.

ما سه ویژگی دیگر به پیکربندی Hibernate اضافه خواهیم کرد:


      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>

این‌ها به Hibernate دستور می‌دهند تا دستورات SQL را که به پایگاه داده ارسال می‌کند، نمایش دهد. بنابراین فایل کامل به شرح زیر است:


<?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="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.hbm2ddl.auto" value="create"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
      <property name="use_sql_comments" value="true"/>
    </properties>
  </persistence-unit>
</persistence>

5.6.1.3. وابستگی‌ها

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

ما لایه JPA را از طریق فایل [persistence.xml] پیکربندی کرده‌ایم. پیاده‌سازی منتخب Hibernate بود. این امر وابستگی‌هایی را به پروژه وارد کرده است:

  

این وابستگی‌ها به دلیل گنجاندن Hibernate در پروژه ایجاد شده‌اند. ما باید یک وابستگی دیگر اضافه کنیم: درایور JDBC برای MySQL، که لایه JDBC معماری را پیاده‌سازی می‌کند. فایل [pom.xml] را به شرح زیر به‌روزرسانی می‌کنیم:


<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>    
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
    </dependency>
    ...
    <dependency>
      <groupId>org.hibernate.common</groupId>
      <artifactId>hibernate-commons-annotations</artifactId>
      <version>4.0.1.Final</version>
    </dependency>
  </dependencies>

خطوط ۸–۱۲ وابستگی درایور JDBC به MySQL را اضافه می‌کنند.

5.6.1.4. اشیاء JPA


سؤال: با دنبال کردن رویه در مثال پاراگراف 4.4، موجودیت‌های [Cotisation, Indemnite, Employe] را تولید کنید.


یادداشت‌ها:

  • این انتیت‌ها بخشی از یک بسته به نام [jpa] را تشکیل خواهند داد،
  • هر انتیت یک شماره نسخه خواهد داشت،
  • اگر دو موجوده توسط یک رابطه به هم متصل شوند، تنها رابطه اصلی @ManyToOne ایجاد خواهد شد. رابطه معکوس @OneToMany ایجاد نخواهد شد.

5.6.1.5. کد کلاس اصلی

ما موجودیت‌های پیش‌تر توسعه یافته JPA و [1] را در پروژه گنجانده‌ایم:

سپس ما [2]، کلاس زیر [main.Main] را اضافه می‌کنیم:


package main;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {

  public static void main(String[] args) {
    //ایجاد Entity Manager برای ساخت لایه JPA کافی است
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-hibernatePU");
    EntityManager em=emf.createEntityManager();
    //آزادسازی منابع
    em.close();
    emf.close();
  }
}
  • خط ۱۰: ما EntityManagerFactory را از واحد پایداری با نام [mv-pam-jpa-hibernatePU] ایجاد می‌کنیم. این نام از فایل [persistence.xml] آمده است:

  <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    ...
  </persistence-unit>
  • خط ۱۲: EntityManager ایجاد می‌شود. این کار لایه JPA را ایجاد می‌کند. فایل [persistence.xml] پردازش خواهد شد و در نتیجه جداول پایگاه داده ایجاد می‌شوند،
  • خطوط ۱۴–۱۵: منابع آزاد می‌شوند.

5.6.1.6. Tests

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

تمام لایه‌ها پیاده‌سازی شده‌اند. پروژه [2] را اجرا می‌کنیم.

خروجی کنسول به شرح زیر است:

------------------------------------------------------------------------
Building mv-pam-jpa-hibernate 1.0-SNAPSHOT
------------------------------------------------------------------------

[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 1 resource

[compiler:compile]
Nothing to compile - all classes are up to date

[exec:exec]
juin 21, 2012 4:22:47 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
juin 21, 2012 4:22:47 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.2}
juin 21, 2012 4:22:47 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
juin 21, 2012 4:22:47 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000402: Using Hibernate built-in connection pool (not for production use!)
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000115: Hibernate connection pool size: 20
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000006: Autocommit mode: true
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000401: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/dbpam_hibernate]
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000046: Connection properties: {user=root, autocommit=true, release_mode=auto}
juin 21, 2012 4:22:48 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
juin 21, 2012 4:22:48 PM org.hibernate.engine.jdbc.internal.LobCreatorBuilder useContextualLobCreation
INFO: HHH000423: Disabling contextual LOB creation as JDBC driver reported JDBC version [3] less than 4
juin 21, 2012 4:22:48 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000268: Transaction strategy: org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory
juin 21, 2012 4:22:48 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000227: Running hbm2ddl schema export
Hibernate: 
    alter table EMPLOYES 
        drop 
        foreign key FK75C8D6BC73F24A67
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: HHH000389: Unsuccessful: alter table EMPLOYES drop foreign key FK75C8D6BC73F24A67
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: Table 'dbpam_hibernate.employes' doesn't exist
Hibernate: 
    drop table if exists COTISATIONS
Hibernate: 
    drop table if exists EMPLOYES
Hibernate: 
    drop table if exists INDEMNITES
Hibernate: 
    create table COTISATIONS (
        id bigint not null auto_increment,
        CSGD double precision not null,
        CSGRDS double precision not null,
        RETRAITE double precision not null,
        SECU double precision not null,
        VERSION integer not null,
        primary key (id)
    )
Hibernate: 
    create table EMPLOYES (
        id bigint not null auto_increment,
        SS varchar(15) not null unique,
        ADRESSE varchar(50) not null,
        CP varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(20) not null,
        VERSION integer not null,
        VILLE varchar(30) not null,
        INDEMNITE_ID bigint not null,
        primary key (id)
    )
Hibernate: 
    create table INDEMNITES (
        id bigint not null auto_increment,
        BASE_HEURE double precision not null,
        ENTRETIEN_JOUR double precision not null,
        INDEMNITES_CP double precision not null,
        INDICE integer not null unique,
        REPAS_JOUR double precision not null,
        VERSION integer not null,
        primary key (id)
    )
Hibernate: 
    alter table EMPLOYES 
        add index FK75C8D6BC73F24A67 (INDEMNITE_ID), 
        add constraint FK75C8D6BC73F24A67 
        foreign key (INDEMNITE_ID) 
        references INDEMNITES (id)
juin 21, 2012 4:22:49 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000230: Schema export complete
juin 21, 2012 4:22:49 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH000030: Cleaning up connection pool [jdbc:mysql://localhost:3306/dbpam_hibernate]
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 2.637s
Finished at: Thu Jun 21 16:22:49 CEST 2012
Final Memory: 8M/153M

کنسول فقط شامل لاگ‌های Hibernate است، زیرا برنامه‌ای که در حال اجراست کاری جز ایجاد لایه JPA انجام نمی‌دهد. به نکات زیر توجه کنید:

  • خط ۴۳: Hibernate تلاش می‌کند کلید خارجی را از جدول [EMPLOYES] حذف کند،
  • خطوط ۵۱–۵۵: حذف سه جدول،
  • خط ۵۷: ایجاد جدول [COTISATIONS
  • خط ۶۷: ایجاد جدول [EMPLOYES
  • خط ۸۰: ایجاد جدول [INDEMNITES
  • خط ۹۱: ایجاد کلید خارجی برای جدول [EMPLOYES].

در NetBeans، می‌توانید جداول موجود در اتصال ایجادشده در بالا را مشاهده کنید:

جدول‌های ایجاد شده هم به پیاده‌سازی لایه JPA و هم به SGBD مورد استفاده بستگی دارند. بنابراین، یک پیاده‌سازی JPA / EclipseLink با استفاده از همان پایگاه داده ممکن است جداول متفاوتی تولید کند. این چیزی است که اکنون بررسی خواهیم کرد.

ما قصد داریم یک پروژه جدید Maven را در محیط زیر بسازیم:

ما رویهٔ تشریح‌شده در پاراگراف قبلی را دنبال خواهیم کرد:

  1. یک پایگاه داده با نام MySQL [dbpam_eclipselink] ایجاد کنید. برای ایجاد آن از اسکریپت [dbpam_eclipselink.sql] استفاده خواهیم کرد،
  2. فایل پروژه [persistence.xml] را ایجاد کنید. پیاده‌سازی JPA 2.0 EclipseLink را در نظر بگیرید،
  3. وابستگی به درایور JDBC از MySQL را به وابستگی‌های تولید شده اضافه کنید،
  4. اشیاء JPA و برنامه کنسول را اضافه کنید،
  5. تست‌ها را اجرا کنید.

فایل [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="pam-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="eclipselink.target-database" value="MySQL"/>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • ویژگی‌های ۹ تا ۱۳ توسط جادوگر NetBeans تولید شده‌اند،
  • خط ۱۴: این خاصیت به ما امکان می‌دهد سطح لاگ را برای EclipseLink تنظیم کنیم. سطح FINE به ما امکان می‌دهد تا دستورات SQL را که EclipseLink به پایگاه داده صادر خواهد کرد، مشاهده کنیم،
  • خط ۱۵: هنگامی که لایه JPA / EclipseLink نمونه سازی می‌شود، جداول موجودیت JPA حذف و سپس دوباره ایجاد خواهند شد.

خروجی کنسول به شرح زیر است:

------------------------------------------------------------------------
Building mv-pam-jpa-eclipselink 1.0-SNAPSHOT
------------------------------------------------------------------------

[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 1 resource

[compiler:compile]
Nothing to compile - all classes are up to date

[exec:exec]
[EL Config]: 2012-06-22 14:35:01.852--ServerSession(730572764)--Thread(Thread[main,5,main])--نوع دسترسی برای کلاس دائمی QZXW2HTMLBW2NsYXNzIGpwAYS5Db3Rpc2F0aW9uXQZQX روی [FIELD] تنظیم شده است.
[EL Config]: 2012-06-22 14:35:01.884--ServerSession(730572764)--Thread(Thread[main,5,main])--نوع دسترسی برای کلاس پایدار QZXW2HTMLBW2NsYXNzIGpwAYSXQZQX روی [FIELD] تنظیم شده است.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--کلاس موجودیت هدف (مرجع) برای عنصر نگاشت چند به یک [field indemnite] در حال تنظیم به پیش‌فرض است: کلاس jpa.Indemnite.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--نوع دسترسی برای کلاس دائمی QZXW2HTMLBW2NsYXNzIGpwAYSQZQX به [FIELD] تنظیم شده است.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--نام مستعار برای کلاس انتیت QZXW2HTMLBW2NsYXNzIGpwAYSQX در حال تنظیم به: Cotisation است.
[EL Config]: 2012-06-22 14:35:01.915--ServerSession(730572764)--Thread(Thread[main,5,main])--نام ستون برای عنصر [id] به پیش‌فرض تنظیم می‌شود: ID.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--نام مستعار برای کلاس انتیت QZXW2HTMLBW2NsYXNzIGpwAYS5FbXBsb3llXQZQX در حال تنظیم به: Employe است.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--نام ستون برای عنصر [id] روی پیش‌فرض تنظیم می‌شود: ID.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--نام مستعار برای کلاس انتیت QZXW2HTMLBW2NsYXNzIGpwAYS5JbmRlbW5pdGVdZQX در حال تنظیم به: Indemnite است.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--نام ستون برای عنصر [id] روی پیش‌فرض تنظیم می‌شود: ID.
[EL Config]: 2012-06-22 14:35:01.962--ServerSession(730572764)--Thread(Thread[main,5,main])--نام ستون کلید اصلی برای عنصر نگاشت [field indemnite] در حال تنظیم به پیش‌فرض است: ID.
[EL Info]: 2012-06-22 14:35:02.558--ServerSession(730572764)--Thread(Thread[main,5,main])--EclipseLink، نسخه: Eclipse Persistence Services - 2.3.0.v20110604-r9504
[EL Config]: 2012-06-22 14:35:02.568--ServerSession(730572764)--Connection(1543921451)--Thread(Thread[main,5,main])--در حال اتصال(DatabaseLogin(
    platform=>MySQLPlatform
    user name=> "root"
    datasource URL=> "jdbc:mysql://localhost:3306/dbpam_eclipselink"
))
[EL Config]: 2012-06-22 14:35:02.738--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])-- متصل: jdbc:mysql://localhost:3306/dbpam_eclipselink
    User: root@localhost
    Database: MySQL  Version: 5.5.20-log
    Driver: MySQL-AB JDBC Driver  Version: mysql-connector-java-5.1.6 ( Revision: ${svn.Revision} )
[EL Info]: 2012-06-22 14:35:02.798--ServerSession(730572764)--Thread(Thread[main,5,main])--file:/D:/data/istia-1112/netbeans/glassfish/mv-pam/05/mv-pam-jpa-eclipselink/target/classes/_pam-jpa-eclipselinkPU ورود با موفقیت انجام شد
[EL Fine]: 2012-06-22 14:35:02.818--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES DROP FOREIGN KEY FK_EMPLOYES_INDEMNITE_ID
[EL Fine]: 2012-06-22 14:35:03.088--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--DROP TABLE COTISATIONS
[EL Fine]: 2012-06-22 14:35:03.118--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE COTISATIONS (ID BIGINT NOT NULL, CSGD DOUBLE NOT NULL, CSGRDS DOUBLE NOT NULL, RETRAITE DOUBLE NOT NULL, SECU DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (QZXW2HTMLCSuqZQX))
[EL Fine]: 2012-06-22 14:35:03.198--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--DROP TABLE EMPLOYES
[EL Fine]: 2012-06-22 14:35:03.238--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE EMPLOYES (ID BIGINT NOT NULL, SS VARCHAR(15) NOT NULL UNIQUE, ADRESSE VARCHAR(50) NOT NULL, CP VARCHAR(5) NOT NULL, NOM VARCHAR(30) NOT NULL, PRENOM VARCHAR(20) NOT NULL, VERSION INTEGER NOT NULL, VILLE VARCHAR(۳۰) NOT NULL, INDEMNITE_ID BIGINT NOT NULL, PRIMARY KEY (QZXW2HTMLCSuqZQX))
[EL Fine]: 2012-06-22 14:35:03.318--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--DROP TABLE INDEMNITES
[EL Fine]: 2012-06-22 14:35:03.338--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE INDEMNITES (QZXW2HTMLCSuqZQX BIGINT NOT NULL, BASE_HEURE DOUBLE NOT NULL, ENTRETIEN_JOUR DOUBLE NOT NULL, INDEMNITES_CP DOUBLE NOT NULL, INDICE INTEGER NOT NULL UNIQUE, REPAS_JOUR DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (QZXW2HTMLCSuqZQX))
[EL Fine]: 2012-06-22 14:35:03.418--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES ADD CONSTRAINT FK_EMPLOYES_INDEMNITE_ID FOREIGN KEY (INDEMNITE_ID) REFERENCES INDEMNITES (QZXW2HTMLCSuqZQX)
[EL Fine]: 2012-06-22 14:35:03.568--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(۵۰) NOT NULL, SEQ_COUNT DECIMAL(۳۸), PRIMARY KEY (SEQ_NAME))
[EL Fine]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Thread(Thread[main,5,main])--SELECT 1
[EL Warning]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Thread(Thread[main,5,main])-- استثنا [EclipseLink-4002] (خدمات پایداری اکلیپس - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'sequence' already exists
Error Code: 1050
Call: CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
Query: DataModifyQuery(sql="CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))")
[EL Fine]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--DELETE FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2012-06-22 14:35:03.638--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--SELECT * FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2012-06-22 14:35:03.638--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) مقادیر (SEQ_GEN, 0)
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--disconnect
[EL Info]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Thread(Thread[main,5,main])--file:/D:/data/istia-1112/netbeans/glassfish/mv-pam/05/mv-pam-jpa-eclipselink/target/classes/_pam-jpa-eclipselinkPU logout successful
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Connection(1543921451)--Thread(Thread[main,5,main])--disconnect
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 3.503s
Finished at: Fri Jun 22 14:35:03 CEST 2012
Final Memory: 8M/153M
  • خطوط ۲۶–۳۰: اتصال به پایگاه داده MySQL،
  • خطوط ۳۱–۳۴: تأیید موفقیت اتصال،
  • خط ۳۶: حذف کلید خارجی از جدول [EMPLOYES
  • خط ۳۷: حذف جدول [COTISATIONS
  • خط ۳۸: ایجاد جدول [COTISATIONS]. شایان ذکر است که کلید اصلی ID فاقد ویژگی MySQL auto_increment است. این بدان معناست که این MySQL نیست که مقادیر کلید اصلی را تولید می‌کند،
  • خط ۳۹: حذف جدول [EMPLOYES
  • خط ۴۰: ایجاد جدول [EMPLOYES]. کلید اصلی آن ID فاقد ویژگی MySQL auto_increment است،
  • خط ۴۱: حذف جدول [INDEMNITES],
  • خط ۴۲: ایجاد جدول [INDEMNITES]. کلید اصلی آن ID فاقد ویژگی MySQL auto_increment است،
  • خط ۴۳: ایجاد کلید خارجی از جدول [EMPLOYES] به جدول [INDEMNITES],
  • خط ۴۴: ایجاد یک جدول [SEQUENCE]. این جدول برای تولید کلیدهای اصلی سه جدول قبلی استفاده خواهد شد،
  • خط ۴۷: یک استثنا پرتاب می‌شود زیرا این جدول از قبل وجود داشته است،
  • خطوط ۵۱–۵۳: inicialization جدول [SEQUENCE].

وجود جداول تولیدشده را می‌توان در NetBeans [1] تأیید کرد:

بنابراین، بر اساس همان اشیاء JPA، پیاده‌سازی‌های JPA، Hibernate و EclipseLink جداول یکسانی را تولید نمی‌کنند. در ادامه این سند، زمانی که پیاده‌سازی JPA مورد استفاده قرار می‌گیرد:

  • Hibernate، از پایگاه داده [dbpam_hibernate] استفاده خواهد شد؛
  • EclipseLink، از پایگاه داده [dbpam_eclipselink] استفاده خواهد شد.

5.6.3. وظیفه

طبق همان رویهٔ قبلی،

  1. یک پروژه [mv-pam-jpa-hibernate-oracle] را با استفاده از پیاده‌سازی Hibernate JPA و پیاده‌سازی Oracle SGBD ایجاد و آزمایش کنید،
  2. یک پروژه [mv-pam-jpa-hibernate-mssql] را با استفاده از پیاده‌سازی Hibernate JPA و سرور SGBD SQL ایجاد و آزمایش کنید،
  3. یک پروژه [mv-pam-jpa-eclipselink-oracle] را با استفاده از پیاده‌سازی JPA EclipseLink و یک سرور Oracle SGBD ایجاد و آزمایش کنید،
  4. ایجاد و آزمایش یک پروژه [mv-pam-jpa-eclipselink-mssql] با استفاده از پیاده‌سازی JPA، EclipseLink و یک سرور SGBD، SQL،

5.6.4. کُند یا مشتاق؟

بیایید به یک تعریف احتمالی از موجودیت [Employe] بازگردیم:


package jpa;

...

@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
  
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @Version
  @Column(name="VERSION",nullable=false)
  private int version;
  @Column(name="SS", nullable=false, unique=true, length=15)
  private String SS;
  @Column(name="NOM", nullable=false, length=30)
  private String nom;
  @Column(name="PRENOM", nullable=false, length=20)
  private String prenom;
  @Column(name="ADRESSE", nullable=false, length=50)
  private String adresse;
  @Column(name="VILLE", nullable=false, length=30)
  private String ville;
  @Column(name="CP", nullable=false, length=5)
  private String codePostal;
  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
  private Indemnite indemnite;
  ...
}

خطوط ۲۷–۲۹ کلید خارجی از جدول [EMPLOYES] به جدول [INDEMNITES] را تعریف می‌کنند. ویژگی fetch در خط ۲۷ استراتژی بازیابی فیلد indemnite در خط ۲۹ را تعریف می‌کند. دو حالت وجود دارد:

  • FetchType.LAZY: وقتی یک کارمند جستجو می‌شود، مزایای مربوط به او بازگردانده نمی‌شود. این مزایا زمانی بازگردانده می‌شوند که برای اولین بار به فیلد [Employe].indemnite ارجاع داده شود.
  • FetchType.EAGER: هنگامی که یک کارمند جستجو می‌شود، فوق‌العاده متناظر با او بازگردانده می‌شود. این حالت پیش‌فرض است زمانی که هیچ حالتی مشخص نشده باشد.

برای درک مزیت گزینه FetchType.LAZY، مثال زیر را در نظر بگیرید. فهرستی از کارمندان بدون مزایایشان در یک صفحه وب با یک لینک [Details] نمایش داده می‌شود. کلیک بر روی این لینک سپس مزایای کارمند انتخاب‌شده را نمایش می‌دهد. می‌بینیم که:

  • برای نمایش صفحه اول، نیازی به مزایای کارمندان نیست. بنابراین حالت FetchType.LAZY مناسب است؛
  • برای نمایش صفحه دوم با جزئیات، باید یک پرس‌وجوی اضافی به پایگاه داده ارسال شود تا مزایای کارمند انتخاب‌شده بازیابی شود.

حالت FetchType.LAZY از دریافت بیش از حد داده‌هایی که برنامه فوراً به آن‌ها نیاز ندارد جلوگیری می‌کند. بیایید به یک مثال نگاه کنیم.

پروژه [mv-pam-jpa-hibernate] کپی شده است:

  • به [1]؛ پروژه کپی می‌شود،
  • در [2]، پوشه کپی را مشخص می‌کنیم و در [3]، نام آن را،
  • در [4]، پروژه جدید همان نام پروژه قدیمی را دارد. ما این را تغییر می‌دهیم:
  • به [1]، پروژه را نام‌گذاری مجدد می‌کنیم،
  • به [2]، پروژه را نام‌گذاری مجدد می‌کنیم و artifactId آن،
  • به [3]، پروژه جدید.

ما برنامه [Main.java] را به شرح زیر اصلاح می‌کنیم:


package main;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import jpa.Employe;

public class Main {

  // پرس‌وجوی JPQL زیر یک کارمند را برمی‌گرداند
  //کلید خارجی [Employe].indemnite در FetchType.LAZY قرار دارد
  public static void main(String[] args) {
    //ایجاد مدیر انتیت کافی است تا لایه JPA ساخته شود
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-hibernatePU");
    // اولین تلاش
    EntityManager em = emf.createEntityManager();
    Employe employe = (Employe) em.createQuery("select e from Employe e where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
    em.close();
    //کارمند نمایش داده می‌شود
    try {
      System.out.println(employe);
    } catch (Exception ex) {
      System.out.println(ex);
    }
    // دومین تلاش
    em = emf.createEntityManager();
    employe = (Employe) em.createQuery("select e from Employe e left join fetch e.indemnite where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
    //آزادسازی منابع
    em.close();
    //نمایش کارمند
    try {
      System.out.println(employe);
    } catch (Exception ex) {
      System.out.println(ex);
    }
    //آزادسازی منابع
    emf.close();
  }
}
  • خط ۱۵: لایه JPA را ایجاد کنید و آن را به EntityManagerFactory تبدیل کنید،
  • خط ۱۷: ما EntityManager را به دست می‌آوریم که به ما امکان تعامل با لایه JPA را می‌دهد،
  • خط ۱۸: ما کارمند با نام Jouveinal را درخواست می‌کنیم،
  • خط ۱۹: ما EntityManager را می‌بندیم. این کار، زمینه پایداری را می‌بندد.
  • خط ۲۲: کارمند بازیابی‌شده را نمایش می‌دهیم.

کلاس [Employe] به شرح زیر است:


package jpa;

...

@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
  
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @Version
  @Column(name="VERSION",nullable=false)
  private int version;
  @Column(name="SS", nullable=false, unique=true, length=15)
  private String SS;
  @Column(name="NOM", nullable=false, length=30)
  private String nom;
  @Column(name="PRENOM", nullable=false, length=20)
  private String prenom;
  @Column(name="ADRESSE", nullable=false, length=50)
  private String adresse;
  @Column(name="VILLE", nullable=false, length=30)
  private String ville;
  @Column(name="CP", nullable=false, length=5)
  private String codePostal;
  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
  private Indemnite indemnite;
  
  
  /**
   * Returns a string representation of the object.  This implementation constructs
   * that representation based on the id fields.
   * @return a string representation of the object.
   */
  @Override
  public String toString() {
    return "jpa.Employe[id=" + getId()
    + ",version="+getVersion()
    +",SS="+getSS()
    + ",nom="+getNom()
    + ",prenom="+getPrenom()
    + ",adresse="+getAdresse()
    +",ville="+getVille()
    +",code postal="+getCodePostal()
    +",indice="+getIndemnite().getIndice()
    +"]";
  }
  ...
}
  • خط ۲۷: فیلد indemnite به LAZY بازنشانی می‌شود،
  • خط ۴۷: از فیلد indemnite استفاده می‌کند. اگر متد toString در حالی فراخوانی شود که فیلد indemnite هنوز ریست نشده باشد، در آن لحظه ریست خواهد شد. مگر اینکه همان‌طور که در مثال آمده، کانتکست پایداری بسته شده باشد.

بیایید به کد [Main] بازگردیم:

  • خطوط ۲۱–۲۵: یک استثنا باید رخ دهد. این به این دلیل است که متد toString در شرف فراخوانی است. این متد از فیلد indemnite استفاده خواهد کرد. سیستم تلاش می‌کند این فیلد را بازیابی کند. از آنجایی که زمینه پایداری (persistence context) بسته شده است، انتیت (entity) بازگردانده شده [Employe] دیگر وجود ندارد، و به همین دلیل استثناء رخ می‌دهد.
  • خط ۲۷: یک EntityManager جدید ایجاد می‌شود،
  • خط ۲۸: کارمند Jouveinal با درخواست صریح امتیاز مرتبط در کوئری برای JPQL بازیابی می‌شود. این درخواست صریح ضروری است زیرا حالت جستجو برای این امتیاز LAZY است،
  • خط ۳۰: EntityManager بسته می‌شود،
  • خطوط ۳۲–۳۶: کارمند دوباره نمایش داده می‌شود. نباید استثنایی وجود داشته باشد.

برای اجرای پروژه، به یک پایگاه داده پرشده نیاز است. این پایگاه داده با دنبال کردن رویه در بخش 5.5 ایجاد خواهد شد. علاوه بر این، فایل [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="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
    </properties>
  </persistence-unit>
</persistence>
  • ما گزینه‌ای را که جداول را ایجاد می‌کرد، حذف کرده‌ایم. پایگاه داده در اینجا از قبل وجود دارد و پر شده است،
  • و ما گزینه‌هایی را که باعث می‌شد Hibernate دستورات SQL ارسالی به پایگاه داده را ثبت کند، حذف کرده‌ایم.

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

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
jpa.Employe[id=31,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
  • خط ۱: استثنایی که هنگام تلاش برای بازیابی امتیاز مفقود در حالی که جلسه بسته بود، رخ داد. می‌توانیم ببینیم که امتیاز به دلیل حالت LAZY بازیابی نشده بود،
  • خط ۲: کارمند با امتیاز بازیابی‌شده از طریق پرس‌وجویی که از حالت LAZY عبور کرده است.

5.6.5. وظیفه

با دنبال کردن روندی مشابه آنچه پیش‌تر توضیح داده شد، پروژه‌ای به نام [mv-pam-pa-eclipselink-lazy] ایجاد کنید که نشان دهد EclipseLink در مقایسه با LAZY چگونه رفتار می‌کند.

نتایج زیر به دست می‌آیند:

jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]

در حالت LAZY، هر دو پرس‌وجو مجوز را همراه با کارمند بازگردانده‌اند. هنگام جستجوی آنلاین برای اطلاعات درباره این ناهنجاری، متوجه می‌شویم که حاشیه‌نویسی [FetchType.LAZY] (خط ۱):


  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;

یک دستور نیست بلکه یک پیشنهاد است. پیاده‌سازی JPA ملزم به پیروی از آن نیست. بنابراین می‌توانیم ببینیم که کد گاهی به پیاده‌سازی JPA مورد استفاده وابسته می‌شود. می‌توان EclipseLink را طوری پیکربندی کرد که برای حالت LAZY همان‌طور که انتظار می‌رود رفتار کند.

5.6.6. ادامه

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

برای ادامه این سند، پروژه Maven [mv-pam-jpa-hibernate] را در پروژه [mv-pam-spring-hibernate] [1, 2, 3] کپی خواهیم کرد:

  • سپس نام پروژه جدید را به [4, 5, 6] تغییر خواهیم داد.

وابستگی‌های پروژه جدید را تغییر خواهیم داد. فایل [pom.xml] به شکل زیر درمی‌آید:


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

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

  <name>mv-pam-spring-hibernate</name>
  <url>http://maven.apache.org</url>
  <repositories>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>swing-layout</id>
      <layout>default</layout>
      <name>Repository for library Library[swing-layout]</name>
    </repository>
  </repositories>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <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>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
</project>
  • خطوط ۲۵–۳۱: وابستگی برای تست‌های JUnit،
  • خطوط ۳۲–۴۱: وابستگی‌ها برای استخر اتصال آپاچی DBCP،
  • خطوط ۴۲–۶۵: وابستگی‌ها برای فریم‌ورک Spring،
  • خطوط ۶۷–۷۱: وابستگی‌ها برای پیاده‌سازی JPA / Hibernate،
  • خطوط ۷۲–۷۶: وابستگی برای درایور JDBC از MySQL،
  • خطوط ۷۷–۸۱: وابستگی برای رابط کاربری Swing. این وابستگی به‌طور خودکار توسط NetBeans اضافه می‌شود هنگامی که یک رابط کاربری Swing به پروژه اضافه می‌شود.

علاوه بر این، دو پایگاه داده زیر ایجاد خواهند شد:

  • [dbpam_hibernate] از روی اسکریپت [dbpam_hibernate.sql
  • [dbpam_eclipselink] از اسکریپت [dbpam_eclipselink.sql

5.7. رابط‌های لایه [metier] و [DAO] ( )

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

در معماری فوق، لایه [DAO] باید چه رابطی را برای لایه [metier] فراهم کند، و لایه [metier] باید چه رابطی را برای لایه [ui] فراهم کند؟ یک رویکرد اولیه برای تعریف رابط‌های لایه‌های مختلف، بررسی موارد استفادهٔ گوناگون برنامه است. در اینجا دو مورد داریم که بسته به رابط کاربری انتخابی متفاوت است: کنسول یا فرم گرافیکی.

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

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20

Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé :
Nom : Jouveinal
...

Informations Cotisations :
CSGRDS : 3.49 %
...

Informations Indemnités :
...

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

برنامه سه مورد اطلاعات را از کاربر دریافت می‌کند (به خط ۱ بالا مراجعه کنید)

  • شماره بیمه ملی پرستار کودک
  • تعداد ساعات کاری در طول ماه
  • تعداد روزهای کاری در ماه

با استفاده از این اطلاعات و سایر داده‌های ذخیره‌شده در فایل‌های پیکربندی، برنامه جزئیات زیر را نمایش می‌دهد:

  • خطوط ۴–۶: مقادیر واردشده
  • خطوط ۸–۱۰: جزئیات مربوط به کارمندی که شماره بیمه ملی او ارائه شده است
  • خطوط ۱۲–۱۴: نرخ‌های مربوط به انواع حق بیمه‌های تأمین اجتماعی
  • رده‌های ۱۶–۱۷: مزایای مختلف پرداخت‌شده به پرستار کودک
  • خطوط ۱۹–۲۴: جزئیات فیش حقوقی پرستار کودک

مقدار مشخصی از اطلاعات باید از لایه [metier] به لایه [ui] ارائه شود:

  1. اطلاعات مربوط به یک پرستار کودک که با شماره تأمین اجتماعی او شناسایی شده است. این اطلاعات در جدول [EMPLOYES] یافت می‌شود. این امکان را فراهم می‌کند که خطوط 6–8 نمایش داده شوند.
  2. مقادیر نرخ‌های مختلف مشارکت تأمین اجتماعی که از حقوق ناخالص کسر می‌شوند. این اطلاعات در جدول [COTISATIONS] یافت می‌شود. این امکان را فراهم می‌کند که خطوط ۱۰–۱۲ نمایش داده شوند.
  3. مبالغ مزایای مختلف مربوط به نقش پرستار کودک. این اطلاعات در جدول [INDEMNITES] یافت می‌شود. این امکان را فراهم می‌کند که خطوط 14–15 نمایش داده شوند.
  4. اجزای حقوق نمایش داده شده در سطرهای ۱۸–۲۲.

بر اساس این، می‌توانیم یک ارسال اولیه را از رابط [IMetier]، که توسط لایه [metier] ارائه می‌شود، به لایه [ui] تعیین کنیم:

1
2
3
4
5
6
package metier;

public interface IMetier {
   // دریافت فیش حقوقی
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
}
  • خط ۱: عناصر لایه [metier] در بسته [metier] قرار می‌گیرند
  • خط ۵: متد [ calculerFeuilleSalaire ] سه اطلاعات دریافت‌شده از لایه [ui] را به‌عنوان پارامتر می‌پذیرد و یک شیء از نوع [FeuilleSalaire] را بازمی‌گرداند که حاوی اطلاعاتی است که لایه [ui] در کنسول نمایش خواهد داد. کلاس [FeuilleSalaire] می‌تواند به صورت زیر باشد:
package metier;

import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class FeuilleSalaire {
   // fields خصوصی
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

  ...
}
  • خط ۹: کارمند مشمول فیش حقوقی – اطلاعات شماره ۱ نمایش‌داده‌شده توسط لایه [ui]
  • خط ۱۰: نرخ‌های مختلف مشارکت – اطلاعات شماره ۲ نمایش داده شده توسط لایه [ui]
  • خط ۱۱: مزایای مختلف مرتبط با شاخص کارمند – اطلاعات شماره ۳ نمایش داده شده توسط لایه [ui]
  • خط ۱۲: اجزای حقوق آنها – اطلاعات شماره ۴ نمایش داده شده توسط لایه [ui]

مورد استفاده دوم برای لایه [métier] در رابط کاربری گرافیکی ظاهر می‌شود:

همان‌طور که در بالا نشان داده شده است، لیست کشویی [1, 2] همه کارمندان را نمایش می‌دهد. این لیست باید از لایه [métier] درخواست شود. رابط کاربری برای این لایه سپس به شرح زیر تغییر می‌کند:

package metier;

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

public interface IMetier {
   // دریافت فیش حقوقی
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // فهرست کارمندان
  public List<Employe> findAllEmployes();
}
  • خط [10]: متدی که به لایه [ui] امکان می‌دهد تا لیست تمام کارمندان را از لایه [métier] درخواست کند.

لایه [metier] تنها می‌تواند با پرس‌وجو از لایه [DAO]، فیلدهای [Employe, Cotisation, Indemnite] از شیء [FeuilleSalaire] مذکور را که در جداول پایگاه داده ذخیره شده‌اند، مقداردهی اولیه کند. همین امر در مورد بازیابی فهرست همه کارمندان نیز صدق می‌کند. می‌توان یک رابط واحد، [DAO]، برای مدیریت دسترسی به سه موجودیت [Employe, Cotisation, Indemnite] ایجاد کرد. با این حال، در اینجا تصمیم گرفته‌ایم برای هر موجودیت یک رابط، [DAO]، ایجاد کنیم.

رابط [DAO] برای دسترسی به انتیت‌های [Cotisation] در جدول [COTISATIONS] به شرح زیر خواهد بود:

package dao;

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

public interface ICotisationDao {
       // ایجاد یک مشارکت جدید
  public Cotisation create(Cotisation cotisation);
       // ویرایش یک مشارکت موجود
  public Cotisation edit(Cotisation cotisation);
       // حذف یک مشارکت موجود
  public void destroy(Cotisation cotisation);
       //جستجو برای یک مشارکت خاص
  public Cotisation find(Long id);
       //بازیابی تمام اشیاء Contribution
  public List<Cotisation> findAll();

}
  • در خط ۶، رابط [ICotisationDao] دسترسی به موجودیت [Cotisation] و در نتیجه به جدول [COTISATIONS] در پایگاه داده را مدیریت می‌کند. اپلیکیشن ما تنها به متد [findAll] از خط ۱۶ نیاز دارد که به ما امکان می‌دهد کل محتویات جدول [COTISATIONS] را بازیابی کنیم. در اینجا، ما می‌خواستیم یک مورد کلی‌تر را در نظر بگیریم که در آن تمام عملیات CRUD (ایجاد، خواندن، به‌روزرسانی، حذف) بر روی این انتیت انجام می‌شود.
  • خط ۸: متد [create] یک موجودیت جدید [Cotisation] ایجاد می‌کند
  • خط ۱۰: متد [edit] یک موجودیت موجود [Cotisation] را تغییر می‌دهد
  • خط ۱۲: متد [destroy] یک موجودیت موجود [Cotisation] را حذف می‌کند
  • خط ۱۴: متد [find] یک موجودیت موجود [Cotisation] را با استفاده از شناسه‌ی id آن بازیابی می‌کند
  • خط ۱۶: متد [findAll] فهرستی از تمام موجودیت‌های [Cotisation] موجود را بازمی‌گرداند

بیایید نگاهی دقیق‌تر به امضای متد [create] بیندازیم:

       // ایجاد اشتراک جدید
Cotisation create(Cotisation cotisation);

متد create دارای پارامتر cotisation از نوع Cotisation است. پارامتر cotisation باید پایدارسازی شود، c.a.d. که در جدول [COTISATIONS] ذخیره می‌شود. پیش از این پایداری، پارامتر cotisation دارای شناسه id بدون مقدار است. پس از پایداری، فیلد id دارای مقداری است که کلید اصلی رکورد افزوده‌شده به جدول [COTISATIONS] می‌باشد. بنابراین، پارامتر cotisation یک پارامتر ورودی/خروجی برای متد create است. به نظر نمی‌رسد که لازم باشد متد create نیز پارامتر cotisation را به عنوان نتیجه بازگرداند. از آنجایی که متد فراخوانی‌کننده یک مرجع به شی [Cotisation cotisation] در اختیار دارد، اگر این شی تغییر یابد، متد فراخوانی‌کننده به شی تغییر یافته دسترسی خواهد داشت زیرا مرجع آن را در اختیار دارد. بنابراین می‌تواند مقداری را که متد create به فیلد id از شیء [Cotisation cotisation] اختصاص داده است، تعیین کند. بنابراین امضای متد می‌تواند به صورت زیر ساده‌تر شود:

       //ایجاد یک مشارکت جدید
void create(Cotisation cotisation);

هنگام نوشتن یک رابط، ارزش دارد به خاطر داشته باشید که می‌توان آن را در دو زمینهٔ مختلف استفاده کرد: local و distant. در زمینه local، متد فراخوانی‌کننده و متد فراخوانی‌شونده در همان JVM اجرا می‌شوند:

اگر لایه [metier] متد create را از لایه [DAO] فراخوانی کند، در واقع یک مرجع به پارامتر [Cotisation cotisation] دارد که آن را به متد ارسال می‌کند.

در زمینه distant، متد فراخوانی‌کننده و متد فراخوانی‌شونده در نمونه‌های متفاوتی از JVM اجرا می‌شوند:

در مثال بالا، لایه [metier] در JVM 1 و لایه [DAO] در JVM 2 روی دو ماشین مختلف اجرا می‌شوند. این دو لایه مستقیماً با یکدیگر ارتباط برقرار نمی‌کنند. بین آن‌ها لایه‌ای قرار دارد که آن را لایه ارتباطی [1] می‌نامیم. این لایه شامل یک لایه ارسال، [2]، و یک لایه دریافت، [3]، است. توسعه‌دهنده معمولاً نیازی به نوشتن این لایه‌های ارتباطی ندارد. آنها به‌طور خودکار توسط ابزارهای نرم‌افزاری تولید می‌شوند. لایه [metier] طوری نوشته شده است که گویی در همان JVM که لایه [DAO] در آن اجرا می‌شود، در حال اجراست. بنابراین هیچ تغییری در کد ایجاد نمی‌شود.

مکانیزم ارتباطی بین لایه [metier] و لایه [DAO] به شرح زیر است:

  • لایه [metier] متد create را در لایه [DAO] فراخوانی می‌کند و پارامتر [Cotisation cotisation1] را به آن ارسال می‌نماید.
  • در واقع این پارامتر به لایه انتقال [2] ارسال می‌شود. این لایه مقدار پارامتر cotisation1 را به جای مرجع آن، از طریق شبکه منتقل می‌کند. شکل دقیق این مقدار به پروتکل ارتباطی مورد استفاده بستگی دارد.
  • لایه دریافت‌کننده [3] این مقدار را بازیابی کرده و از آن برای بازسازی یک شیء [Cotisation cotisation2] استفاده می‌کند که نمایانگر پارامتر اولیه ارسال‌شده توسط لایه [metier] است. اکنون ما دو شیء یکسان (از نظر محتوا) در دو نمونه مختلف JVM داریم: cotisation1 و cotisation2.
  • لایه دریافت‌کننده شیء cotisation2 را به متد create از لایه [DAO] ارسال می‌کند که آن را در پایگاه داده پایدار می‌سازد. پس از این عملیات، فیلد id از شیء cotisation2 با کلید اصلی رکورد افزوده شده به جدول [COTISATIONS] مقداردهی اولیه شده است. این امر برای شیء cotisation1 صدق نمی‌کند، که لایه [metier] به آن ارجاع دارد. اگر بخواهیم لایه [metier] به شیء cotisation2 ارجاع دهد، باید آن را به لایه ارسال کنیم. بنابراین باید امضای متد create را در لایه [DAO] تغییر دهیم:
       //ایجاد یک مشارکت جدید
Cotisation create(Cotisation cotisation);
  • با این امضای جدید، متد create شیء پایدار cotisation2 را بازخواهد گرداند. این نتیجه به لایه دریافت‌کننده [3] بازگردانده می‌شود، که لایه [DAO] را فراخوانده بود. لایه دوم مقدار (نه مرجع) cotisation2 را به لایه ارسال‌کننده [2] بازمی‌گرداند.
  • لایه ارسال‌کننده [2] این مقدار را بازیابی کرده و از آن برای بازسازی یک شی [Cotisation cotisation3] استفاده می‌کند که نمایانگر نتیجه‌ای است که توسط متد create از لایه [DAO] بازگردانده شده است.
  • شیء [Cotisation cotisation3] به متد لایه [metier] بازگردانده می‌شود، که فراخوانی متد create در لایه [DAO]، کل این مکانیزم را آغاز کرده بود. لایه [metier] بنابراین می‌تواند مقدار کلید اصلی تخصیص‌یافته به شیء [Cotisation cotisation1] را که برای آن پایداری درخواست شده بود، تعیین کند: این مقدار، مقدار فیلد id در cotisation3 است.

معماری توصیف‌شده در بالا رایج‌ترین نیست. یافتن لایه‌های [metier] و [DAO] در داخل همان JVM رایج‌تر است:

در این معماری، این متدهای لایه [metier] هستند که باید نتایج را بازگردانند، نه متدهای لایه [DAO]. با این حال، امضای زیر برای متد create از لایه [DAO]:

       // ایجاد یک مشارکت جدید
Cotisation create(Cotisation cotisation);

به این معنی است که نیازی نیست در مورد معماری واقعی موجود هیچ فرضیه‌ای مطرح کنیم. استفاده از امضاهایی که صرف‌نظر از معماری انتخاب‌شده—چه محلی و چه راه دور—کار می‌کنند، به این معناست که اگر متد فراخوانی‌شده برخی از پارامترهای خود را تغییر دهد:

  • این‌ها نیز باید بخشی از نتیجه متد فراخوانی‌شده را تشکیل دهند.
  • متد فراخوانی‌کننده باید از نتیجه متد فراخوانی‌شونده استفاده کند، نه از ارجاع به پارامترهای تغییر یافته‌ای که به آن ارسال کرده است.

این امر به ما امکان می‌دهد تا بدون هیچ‌گونه تغییر کدی، از معماری locale به معماری distante منتقل شویم. اکنون بیایید رابط [ICotisationDao] را از این منظر مجدداً بررسی کنیم:

package dao;

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

public interface ICotisationDao {
       // ایجاد یک مشارکت جدید
  public Cotisation create(Cotisation cotisation);
       // ویرایش یک مشارکت موجود
  public Cotisation edit(Cotisation cotisation);
       // حذف یک مشارکت موجود
  public void destroy(Cotisation cotisation);
       // جستجوی یک مشارکت خاص
  public Cotisation find(Long id);
       //بازیابی تمام اشیاء Contribution
  public List<Cotisation> findAll();

}
  • خط ۸: مورد متد create رسیدگی شده است
  • خط ۱۰: متد edit از پارامتر خود [Cotisation cotisation1] برای به‌روزرسانی رکورد در جدول [COTISATIONS] که کلید اصلی آن با شی cotisation یکسان است، استفاده می‌کند. این متد شیء cotisation2 را بازمی‌گرداند که نمایانگر رکورد اصلاح‌شده است. با این حال، پارامتر cotisation1 بدون تغییر باقی می‌ماند. این متد باید cotisation2 را به عنوان نتیجه بازگرداند، صرف نظر از اینکه زمینه معماری distante یا locale باشد.
  • خط ۱۲: متد destroy رکوردی را از جدول [COTISATIONS] حذف می‌کند که کلید اصلی آن با شیء cotisation که به‌عنوان پارامتر ارسال شده، یکسان است. این شیء تغییر نمی‌کند. بنابراین نیازی به بازگرداندن آن نیست.
  • خط ۱۴: پارامتر id از متد find توسط خود متد تغییر نمی‌کند. بنابراین نیازی به درج آن در نتیجه نیست.
  • خط 16: متد findAll هیچ پارامتری ندارد. بنابراین نیازی به بررسی آن نیست.

در نهایت، تنها امضای متد create است که باید برای استفاده در معماری distante تطبیق داده شود. استدلالی که در بالا ارائه شد، برای سایر رابط‌های [DAO] نیز صدق می‌کند. ما آنها را در اینجا تکرار نخواهیم کرد و در عوض از امضاهایی استفاده خواهیم کرد که برای استفاده در هر دو معماری distante و locale مناسب هستند.

رابط [DAO] برای دسترسی به موجودیت‌های [Indemnite] در جدول [INDEMNITES] به شرح زیر خواهد بود:

package dao;

import java.util.List;
import jpa.Indemnite;

public interface IIndemniteDao {
     //ایجاد یک موجودیت «Indemnite»
  public Indemnite create(Indemnite indemnite);
     // ویرایش یک موجودیت «Indemnite»
  public Indemnite edit(Indemnite indemnite);
     //حذف یک موجودیت جبران خسارت
  public void destroy(Indemnite indemnite);
     //جستجو برای یک موجودیت «Indemnite» بر اساس شناسه آن
  public Indemnite find(Long id);
     //بازیابی همهٔ موجودیت‌های Indemnite
  public List<Indemnite> findAll();

}
  • در خط ۶، رابط [IIndemniteDao] دسترسی به موجودیت [Indemnite] و در نتیجه به جدول [INDEMNITES] در پایگاه داده را مدیریت می‌کند. برنامهٔ ما تنها به متد [findAll] در خط ۱۶ نیاز دارد که کل محتویات جدول [INDEMNITES] را بازیابی می‌کند. در اینجا، ما می‌خواستیم یک مورد کلی‌تر را در نظر بگیریم که در آن تمام عملیات CRUD (ایجاد، خواندن، به‌روزرسانی، حذف) روی این انتیت انجام می‌شود.
  • خط ۸: متد [create] یک موجودیت جدید [Indemnite] ایجاد می‌کند
  • خط ۱۰: متد [edit] یک موجودیت موجود [Indemnite] را تغییر می‌دهد
  • خط ۱۲: متد [destroy] یک موجودیت موجود [Indemnite] را حذف می‌کند
  • خط ۱۴: متد [find] یک موجودیت موجود [Indemnite] را از طریق شناسه‌ی آن id بازیابی می‌کند
  • خط ۱۶: متد [findAll] فهرستی از تمام موجودیت‌های موجود [Indemnite] را بازمی‌گرداند

رابط [DAO] برای دسترسی به اشیاء [Employe] در جدول [EMPLOYES] به شرح زیر خواهد بود:

package dao;

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

public interface IEmployeDao {
     //ایجاد یک موجودیت جدید «کارمند»
  public Employe create(Employe employe);
     //ویرایش یک موجودیت «کارمند» موجود
  public Employe edit(Employe employe);
     //حذف یک موجودیت «کارمند»
  public void destroy(Employe employe);
     //جستجو برای یک موجودیت «کارمند» بر اساس شناسه آن
  public Employe find(Long id);
     //جستجو برای یک موجودیت «کارمند» بر اساس شماره آن SS
  public Employe find(String SS);
     //بازیابی تمام اشیاء «کارمند»
  public List<Employe> findAll();
}
  • در خط ۶، رابط [IEmployeDao] دسترسی به موجودیت [Employe] و در نتیجه به جدول [EMPLOYES] در پایگاه داده را مدیریت می‌کند. اپلیکیشن ما تنها به متد [findAll] از خط 16 نیاز دارد که به ما امکان بازیابی کل محتویات جدول [EMPLOYES] را می‌دهد. در اینجا، ما می‌خواستیم یک مورد کلی‌تر را در نظر بگیریم که در آن تمام عملیات CRUD (ایجاد، خواندن، به‌روزرسانی، حذف) روی این انتیت انجام می‌شود.
  • خط ۸: متد [create] یک موجودیت جدید [Employe] ایجاد می‌کند
  • خط ۱۰: متد [edit] یک موجودیت موجود [Employe] را تغییر می‌دهد
  • خط ۱۲: متد [destroy] یک موجودیت موجود [Employe] را حذف می‌کند
  • خط ۱۴: متد [find] یک موجودیت موجود [Employe] را از طریق شناسه آن id بازیابی می‌کند
  • خط ۱۶: متد [find(String SS)] امکان بازیابی یک موجودیت موجود [Employe] را از طریق شماره‌اش SS فراهم می‌کند. ما دیده‌ایم که این متد توسط برنامه کنسولی مورد نیاز بود.
  • خط ۱۸: متد [findAll] فهرستی از تمام موجودیت‌های [Employe] موجود را بازمی‌گرداند. ما دیده‌ایم که این متد توسط برنامهٔ گرافیکی مورد نیاز است.

5.8. کلاس [PamException]

لایه [DAO] با کلاس‌های Java API و JDBC کار خواهد کرد. این API استثناءهای کنترل‌شده از نوع [SQLException] را پرتاب می‌کند که دو عیب دارند:

  • آنها کد را حجیم‌تر می‌کنند، زیرا باید این استثناها را با استفاده از بلوک‌های try/catch مدیریت کند.
  • آنها باید در امضاهای متد اینفترفیس [IDao] با استفاده از «throws SQLException» اعلام شوند. در نتیجه، این امر پیاده‌سازی این رابط را توسط کلاس‌هایی که ممکن است یک استثنای بررسی‌شده از نوعی غیر از [SQLException] را پرتاب کنند، جلوگیری می‌کند.

برای حل این مشکل، لایه [DAO] تنها استثناهای بررسی‌نشده از نوع [PamException] را به لایه بالاتر ارسال می‌کند.

  • لایه [JDBC] استثناهایی از نوع [SQLException] پرتاب می‌کند
  • لایه [JPA] استثناهای مخصوص پیاده‌سازی JPA در حال استفاده را پرتاب می‌کند
  • لایه [DAO] استثناءهای بدون‌گیرنده از نوع [PamException] را پرتاب می‌کند

این دو پیامد دارد:

  • لایه [metier] دیگر نیازی به مدیریت استثناهای لایه [DAO] با استفاده از بلوک‌های try/catch نخواهد داشت. این لایه می‌تواند به سادگی اجازه دهد تا استثناها تا لایه [ui] منتقل شوند.
  • متدهای رابط [IDao] نیازی ندارند که ماهیت استثنای [PamException] را در امضاهای خود مشخص کنند، که این امر امکان پیاده‌سازی این رابط با کلاس‌هایی را باز می‌گذارد که نوع دیگری از استثنای بدون بررسی (unchecked) را پرتاب (throw) می‌کنند.

کلاس [PamException] در پکیج [exception] پروژه NetBeans قرار داده خواهد شد:

کد آن به شرح زیر است:

package exception;

@SuppressWarnings("serial")
public class PamException extends RuntimeException {

   // کد خطا
  private int code;

  public PamException(int code) {
    super();
    this.code = code;
  }

  public PamException(String message, int code) {
    super(message);
    this.code = code;
  }

  public PamException(Throwable cause, int code) {
    super(cause);
    this.code = code;
  }

  public PamException(String message, Throwable cause, int code) {
    super(message, cause);
    this.code = code;
  }

   // گیرنده و تنظیم‌کننده

  public int getCode() {
    return code;
  }

  public void setCode(int code) {
    this.code = code;
  }

}
  • خط ۴: کلاس [PamException] از کلاس [RuntimeException] ارث می‌برد. بنابراین این یک نوع استثنا است که کامپایلر از ما نمی‌خواهد آن را با استفاده از بلوک try/catch مدیریت کنیم یا در امضاهای متد بگنجانیم. به همین دلیل، [PamException] در امضاهای متد رابط [IDao] گنجانده نشده است. این امر به این رابط اجازه می‌دهد تا توسط کلاسی پیاده‌سازی شود که یک نوع استثناء متفاوت را پرتاب می‌کند، به شرطی که آن کلاس نیز از [RuntimeException] ارث ببرد.
  • برای تمایز قائل شدن بین خطاهای احتمالی، از کد خطا در خط ۷ استفاده می‌شود. سه سازنده در خطوط ۱۴، ۱۹ و ۲۴ متعلق به کلاس والد [RuntimeException] هستند که یک پارامتر به آنها اضافه شده است: کد خطایی که باید به استثنا اختصاص داده شود.

از منظر استثناءها، برنامه به شرح زیر عمل خواهد کرد:

  • لایه [DAO] هر استثنای مواجه شده را در یک استثنای از نوع [PamException] قرار داده و دومی را به لایه [métier] مجدداً پرتاب می‌کند.
  • لایه [métier] اجازه می‌دهد استثناءهای پرتاب‌شده توسط لایه [DAO] به سمت بالا منتقل شوند. این لایه هر استثنایی را که در لایه [métier] رخ می‌دهد، در یک استثنای [PamException] می‌پیچد و آن را مجدداً به لایه [ui] پرتاب می‌کند.
  • لایه [ui] تمام استثناهایی را که از لایه‌های [métier] و [DAO] به سمت بالا ارسال می‌شوند، رهگیری می‌کند. این لایه به سادگی استثنا را در کنسول یا رابط کاربری گرافیکی نمایش می‌دهد.

اکنون بیایید پیاده‌سازی لایه‌های [DAO] و [metier] را به ترتیب بررسی کنیم.

5.9. لایه [DAO] از برنامه [PAM]

ما در چارچوب معماری زیر کار می‌کنیم:

5.9.1. پیاده‌سازی

مطالعهٔ پیشنهادی: بخش ۳.۱.۳ از [ref1]


سؤال: با استفاده از Spring integration / JPA، کلاس‌های [CotisationDao, IndemniteDao, EmployeDao] را برای پیاده‌سازی رابط‌های [ICotisationDao, IIndemniteDao, IEmployeDao] بنویسید. هر متد کلاس هر استثنا را می‌گیرد و آن را در یک استثنای نوع [PamException] با کد خطای مخصوص به استثنای گرفته‌شده می‌پیچد.


کلاس‌های پیاده‌سازی بخشی از بسته [dao] را تشکیل خواهند داد:

  

5.9.2. پیکربندی

مطالعهٔ پیشنهادی: بخش ۳.۱.۵ از [ref1]

ادغام DAO / JPA توسط فایل Spring با نام [spring-config-dao.xml] و فایل‌های JPA و [persistence.xml] پیکربندی می‌شود:


سؤال: محتویات این دو فایل را بنویسید. فرض می‌کنیم پایگاه‌دادهٔ مورد استفاده همان پایگاه‌دادهٔ MySQL5 [dbpam_hibernate] است که توسط اسکریپت SQL [dbpam_hibernate.sql] تولید شده است. فایل Spring سه بیون زیر را تعریف خواهد کرد: employeDao از نوع EmployeDao، indemniteDao از نوع IndemniteDao، cotisationDao از نوع CotisationDao. علاوه بر این، پیاده‌سازی JPA که استفاده خواهد شد، Hibernate خواهد بود.


5.9.3. آزمایش‌ها

مطالعه پیشنهادی: بخش‌های ۳.۱.۶ و ۳.۱.۷ از [ref1]

اکنون که لایه [DAO] نوشته و پیکربندی شده است، می‌توانیم آن را آزمایش کنیم. معماری تست به شرح زیر خواهد بود:

5.9.4. InitDB

ما دو برنامهٔ تست برای لایهٔ [DAO] ایجاد خواهیم کرد. این برنامه‌ها در پکیج [dao] [2]، درون شاخه [Test Packages] [1] پروژه NetBeans قرار داده خواهند شد. این شاخه در پروژه‌ای که توسط گزینه [Build project] ایجاد می‌شود، گنجانده نمی‌شود، که این امر تضمین می‌کند برنامه‌های آزمایشی که در آنجا قرار می‌دهیم در فایل نهایی .jar پروژه گنجانده نشوند.

کلاس‌های قرار گرفته در شاخه [Test Packages] به کلاس‌های شاخه [Source Packages] و همچنین به کتابخانه‌های کلاس پروژه دسترسی دارند. اگر تست‌ها به کتابخانه‌هایی غیر از کتابخانه‌های پروژه نیاز داشته باشند، این کتابخانه‌ها باید در شاخه‌های [Test Libraries] و [2] اعلام شوند.

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

  • [JUnitInitDB] هیچ آزمایشی انجام نمی‌دهد. این شاخه پایگاه داده را با چند رکورد پر می‌کند و سپس آن‌ها را در کنسول نمایش می‌دهد.
  • کلاس [JUnitDao] مجموعه‌ای از تست‌ها را اجرا می‌کند و نتایج را بررسی می‌کند.

اسکلت کلاس [JUnitInitDB] به شرح زیر است:

package dao;

...

public class JUnitInitDB {

  private IEmployeDao employeDao = null;
  private ICotisationDao cotisationDao = null;
  private IIndemniteDao indemniteDao = null;

  @BeforeClass
  public void init(){
     //پیکربندی برنامه
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     //لایه‌ها DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
  }

  @Test
  public void initDB(){
     // پر کردن پایگاه داده
...
     // نمایش محتویات پایگاه داده
...
  }

  @Before()
  public void clean(){
     // پاک کردن پایگاه داده
...
  }
}
  • متد [init] قبل از شروع مجموعه تست‌ها اجرا می‌شود (علامت‌گذاری @BeforeClass). این متد لایه [DAO] را ایجاد می‌کند.
  • متد [clean] قبل از هر تست اجرا می‌شود (علامت‌گذاری @Before). این متد پایگاه داده را پاک می‌کند.
  • متد [initDB] یک تست است (علامت @Test). این تنها تست موجود است. یک تست باید شامل دستورات اعتبارسنجی Assert.assertCondition باشد. در اینجا، هیچ‌کدام وجود نخواهد داشت. بنابراین این متد یک تست نمایشی است. هدف آن پر کردن پایگاه داده با چند سطر و سپس نمایش محتویات پایگاه داده در کنسول است. متدهای create و findAll از لایه [DAO] در اینجا استفاده شده‌اند.

سؤال: کد کلاس [JUnitInitDB] را تکمیل کنید. از مثال در بند 3.1.6 از [ref1] به‌عنوان راهنما استفاده کنید. این کد محتوا را که در بند 5.1 نشان داده شده است تولید خواهد کرد.


5.9.5. Implementati ation از تست‌ها

اکنون آماده اجرای [InitDB] هستیم. ما رویه را با استفاده از SGBD و MySQL5 شرح خواهیم داد:

  • کلاس‌های [1]، فایل‌های پیکربندی [2] و کلاس‌های تست برای لایه [DAO] و [3] راه‌اندازی شده‌اند،
  • پروژه ساخته شده است ([4])
  • کلاس [JUnitInitDB] اجرا می‌شود [5]. SGBD MySQL5 با استفاده از یک پایگاه داده موجود [dbpam_hibernate] اجرا می‌شود،
  • پنجره‌های [Test Results] و [6] نشان می‌دهند که آزمایش‌ها با موفقیت انجام شده‌اند. این پیام در اینجا مرتبط نیست، زیرا برنامه [JUnitInitDB] شامل هیچ عبارت اثبات Assert.assertCondition نیست که بتواند باعث شکست تست شود. با این حال، نشان می‌دهد که هیچ استثنایی در طول اجرای تست رخ نداده است.

پنجره [Output] شامل گزارش‌های اجرا، گزارش‌های Spring و خود گزارش‌های تست است. خروجی تولیدشده توسط کلاس [JUnitInitDB] به شرح زیر است:

------------- Standard Output ---------------
Employés ----------------------
jpa.Employe[id=5,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
jpa.Employe[id=6,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La brûlerie,ville=St Marcel,code postal=49014,indice=1]
Indemnités ----------------------
jpa.Indemnite[id=5,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0]
jpa.Indemnite[id=6,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités CP=15.0]
Cotisations ----------------------
jpa.Cotisation[id=3,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88]
------------- ---------------- ---------------

جدول‌های [EMPLOYES, INDEMNITES, COTISATIONS] پر شده‌اند. این موضوع را می‌توان با اتصال به پایگاه داده [dbpam_hibernate] از طریق NetBeans تأیید کرد.

  • در [1]، در برگه [services]، داده‌های جدول [employes] از اتصال [dbpam_hibernate] [2] نمایش داده می‌شود،
  • در [3]، نتیجه.

5.9.6. JUnitDao

اکنون به کلاس دوم تست‌ها، [JUnitDao]، می‌پردازیم:

ساختار کلاس به شرح زیر خواهد بود:

package dao;

import exception.PamException;
...

public class JUnitDao {

//لایه‌ها DAO
  static private IEmployeDao employeDao;
  static private IIndemniteDao indemniteDao;
  static private ICotisationDao cotisationDao;

  @BeforeClass
  public static void init() {
     // لاگ
    log("init");
     // پیکربندی برنامه
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     // لایه‌ها DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
  }

  @AfterClass
  public static void terminate() {
  }

  @Before()
  public void clean() {
...
  }

   // لاگ‌ها
  private static void log(String message) {
    System.out.println("----------- " + message);
  }

   // آزمایش‌ها
  @Test
  public void test01() {
    log("test01");
     // فهرست مشارکت‌ها
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // افزودن مشارکت
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // درخواست
    cotisation = cotisationDao.find(cotisation.getId());
     //
    Assert.assertNotNull(cotisation);
    Assert.assertEquals(3.49, cotisation.getCsgrds(), 1e-6);
    Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
    Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
    Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
     //در حال اصلاح است
    cotisation.setCsgrds(-1);
    cotisation.setCsgd(-1);
    cotisation.setRetraite(-1);
    cotisation.setSecu(-1);
    Cotisation cotisation2 = cotisationDao.edit(cotisation);
     //بررسی‌ها
    Assert.assertEquals(cotisation.getVersion() + 1, cotisation2.getVersion());
    Assert.assertEquals(-1, cotisation2.getCsgrds(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getCsgd(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getRetraite(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getSecu(), 1e-6);
     // درخواست آیتم اصلاح‌شده
    Cotisation cotisation3 = cotisationDao.find(cotisation2.getId());
     //بررسی‌ها
    Assert.assertEquals(cotisation3.getVersion(), cotisation2.getVersion());
    Assert.assertEquals(-1, cotisation3.getCsgrds(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getCsgd(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getRetraite(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getSecu(), 1e-6);
     // عنصر حذف می‌شود
    cotisationDao.destroy(cotisation3);
     //بررسی می‌کند
    Cotisation cotisation4 = cotisationDao.find(cotisation3.getId());
    Assert.assertNull(cotisation4);
    cotisations = cotisationDao.findAll();
    Assert.assertEquals(nbCotisations, cotisations.size());
  }


  @Test
  public void test02(){
    log("test02");
     //درخواست فهرست امتیازات
...
     // افزودن یک امتیاز
..
     // بازیابی معافیت از پایگاه داده – بازیابی معافیت1
..
     //بررسی می‌کند که allowance1 = allowance
...
     // مطالعه شده تغییر می‌کند و تغییر در BD ذخیره می‌شود. این منجر به allowance2 می‌شود
 ...
     //بررسی نسخه indemnity2
    ...
     //بازیابی indemnite2 از پایگاه داده – این مقدار indemnite3 را برمی‌گرداند
    ...
     // ما بررسی می‌کنیم که indemnite3 = indemnite2
    ...
     // تصویر indemnite3 از پایگاه داده حذف می‌شود
    ...
     //بازیابی indemnite3 از پایگاه داده
    ...
     //بررسی می‌کنیم که یک مرجع نامعتبر دریافت شده است
 ...
  }

  @Test
  public void test03(){
    log("test03");
     // آزمایشی مشابه آزمایش‌های قبلی را برای 'Employe' تکرار می‌کنیم
 ...
  }

  @Test
  public void test04(){
    log("test04");
     // متد [IEmployeDao].find(String SS) را آزمایش می‌کنیم
     //ابتدا با یک کارمند موجود
     //سپس با یک کارمند غیرموجود
...
  }

  @Test
  public void test05(){
    log("test05");
     // ما دو کمک‌هزینه را با همان شاخص ایجاد می‌کنیم
     // محدودیت یکتایی ایندکس را نقض می‌کند
     // ما بررسی می‌کنیم که یک استثنا از نوع PamException رخ می‌دهد
     // و اینکه دارای شماره خطای مورد انتظار است
...
  }

  @Test
  public void test06(){
    log("test06");
     // دو کارمند با شماره یکسان SS ایجاد می‌شوند
     // که محدودیت یکتایی عدد SS را نقض می‌کند
     // ما بررسی می‌کنیم که یک استثنا از نوع PamException رخ می‌دهد
     // و اینکه دارای شماره خطای مورد انتظار است
...

  }

  @Test
  public void test07(){
    log("test07");
     // ما دو کارمند با شناسه یکسان SS ایجاد می‌کنیم، اولی با استفاده از 'create' و دومی با استفاده از 'edit'
     // محدودیت یکتایی عدد SS را نقض می‌کند
     // ما تأیید می‌کنیم که یک استثنا از نوع PamException رخ می‌دهد
     //و اینکه دارای شماره خطای مورد انتظار است
...
  }

  @Test
  public void test08(){
    log("test08");
     // حذف یک کارمند موجود در سیستم خطا ایجاد نمی‌کند
     // آن اضافه شده و سپس حذف می‌شود – این مورد تأیید می‌شود
...
  }

  @Test
  public void test09(){
    log("test09");
     // تغییر یک کارمند بدون نسخه صحیح باید باعث استثنا شود
     // ما این را بررسی می‌کنیم
...
  }

  @Test
  public void test10(){
    log("test10");
     //حذف یک کارمند بدون داشتن نسخه صحیح باید یک استثنا ایجاد کند
     // این را بررسی خواهیم کرد
...

  }

   // گیرنده و تنظیم‌کننده
  ...
}

در کلاس تست قبلی، پایگاه داده قبل از هر تست پاک می‌شود.


سؤال: متدهای زیر را بنویسید:

۱ – test02: این بر اساس test01 خواهد بود

۲ - test03: یک کارمند فیلدی از نوع Indemnite دارد. بنابراین باید یک انتیت Indemnite و یک انتیت Employe ایجاد کنید.

3 - test04.


با دنبال کردن همان رویه برای کلاس تست [JUnitInitDB]، نتایج زیر به دست می‌آیند:

  • در [1]، کلاس تست را اجرا کنید
  • برای [2]، نتایج آزمون در پنجره [Test Results] ظاهر می‌شوند

بیایید یک خطا را فعال کنیم تا ببینیم چگونه در صفحه نتایج گزارش می‌شود:

  @Test
  public void test01() {
    log("test01");
     // فهرست مشارکت‌ها
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // افزودن یک مشارکت
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // آن را بازیابی کنید
    cotisation = cotisationDao.find(cotisation.getId());
     // تأیید
    Assert.assertNotNull(cotisation);
    Assert.assertEquals(0, cotisation.getCsgrds(), 1e-6);
    Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
    Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
    Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
     // تغییر آن
....
}

خط ۱۳: عبارت assertion باعث خطا می‌شود، زیرا مقدار Csgrds برابر ۳.۴۹ است (خط ۸). اجرای کلاس تست نتایج زیر را تولید می‌کند:

  • صفحه نتایج [1] اکنون نشان می‌دهد که برخی از تست‌ها شکست خورده‌اند.
  • در [2]، خلاصه‌ای از استثنایی که باعث شکست تست شد، نمایش داده می‌شود. این شامل شماره خط در کد جاوا است که استثنا در آن رخ داده است.

5.10. لایه [metier] از برنامه [PAM]

اکنون که لایه [DAO] نوشته شده است، به بررسی لایه کسب‌وکار [2] می‌پردازیم:

5.10.1. رابط جاوا [IMetier]

این در بخش 5.7 توضیح داده شده است. ما آن را در زیر خلاصه می‌کنیم:

package metier;

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

public interface IMetier {
   // بازیابی فیش حقوقی
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // فهرست کارمندان
  public List<Employe> findAllEmployes();
}

پیاده‌سازی لایه [metier] در پکیجی به نام [metier] انجام خواهد شد:

 

پکیج [metier] شامل، علاوه بر رابط [IMetier] و پیاده‌سازی آن [Metier]، دو کلاس دیگر: [FeuilleSalaire] و [ElementsSalaire]. کلاس [FeuilleSalaire] به‌طور مختصر در بخش 5.7 مورد بحث قرار گرفت. اکنون به آن بازمی‌گردیم.

5.10.2. کلاس [FeuilleSalaire]

متد [calculerFeuilleSalaire] از رابط [IMetier] یک شیء از نوع [FeuilleSalaire] را بازمی‌گرداند که نمایانگر عناصر مختلف یک فیش حقوقی است. تعریف آن به شرح زیر است:

package metier;

import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class FeuilleSalaire implements Serializable{
   // fields private
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

   // سازنده‌ها
  public FeuilleSalaire() {

  }

  public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire elementsSalaire) {
    setEmploye(employe);
    setCotisation(cotisation);
    setElementsSalaire(elementsSalaire);
  }

   //toString
  public String toString() {
    return "[" + employe + "," + cotisation + "," + elementsSalaire + "]";
  }

   // دسترس‌کنندگان
...  
}
  • خط ۷: این کلاس رابط Serializable را پیاده‌سازی می‌کند زیرا نمونه‌های آن می‌توانند از طریق شبکه مبادله شوند.
  • خط ۹: کارمندی که فیش حقوقی او صادر می‌شود
  • خط ۱۰: نرخ‌های مختلف مشارکت
  • خط ۱۱: مزایای مختلف مرتبط با جدول حقوق کارمند
  • خط ۱۲: اجزای حقوق آنها
  • خطوط ۱۴–۲۲: دو سازنده کلاس
  • خطوط ۲۵–۲۷: متد [toString] که یک شیء خاص [FeuilleSalaire] را شناسایی می‌کند
  • خطوط ۲۹ به بعد: دسترسی‌دهنده‌های عمومی به فیلدهای خصوصی کلاس

کلاس [ElementsSalaire]، که در خط ۱۱ از کلاس [FeuilleSalaire] بالا به آن ارجاع شده است، عناصری را که یک فیش حقوقی را تشکیل می‌دهند، گرد هم می‌آورد. تعریف آن به شرح زیر است:

package metier;

public class ElementsSalaire implements Serializable{

   // میدان‌های خصوصی
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;

   // تولیدکنندگان
  public ElementsSalaire() {

  }

  public ElementsSalaire(double salaireBase, double cotisationsSociales,
    double indemnitesEntretien, double indemnitesRepas,
    double salaireNet) {
    setSalaireBase(salaireBase);
    setCotisationsSociales(cotisationsSociales);
    setIndemnitesEntretien(indemnitesEntretien);
    setIndemnitesRepas(indemnitesRepas);
  }

   // toString
  public String toString() {
    return "[salaire base=" + salaireBase + ",cotisations sociales=" + cotisationsSociales + ",indemnités d'entretien="
      + indemnitesEntretien + ",indemnités de repas=" + indemnitesRepas + ",salaire net="
      + salaireNet + "]";
  }

   // دسترس‌کننده‌های عمومی
...  
}
  • خط ۳: کلاس این رابط را پیاده‌سازی می‌کند زیرا جزئی از کلاس FeuilleSalaire است که باید قابل سریال‌سازی باشد.
  • خط ۶: حقوق پایه
  • خط ۷: حق بیمه‌های تأمین اجتماعی پرداخت‌شده بر این حقوق پایه
  • خط ۸: کمک‌هزینه روزانه نگهداری کودک
  • خط ۹: کمک‌هزینه روزانه غذا برای کودک
  • خط ۱۰: حقوق خالص قابل پرداخت به پرستار کودک
  • خطوط ۱۲–۲۴: سازنده‌های کلاس
  • رده‌های ۲۷–۳۱: متد [toString] که یک شیء خاص [ElementsSalaire] را شناسایی می‌کند
  • خطوط ۳۴ به بعد: دسترسی‌های عمومی به فیلدهای خصوصی کلاس

5.10.3. کلاس پیاده‌سازی [Metier] برای لایه [metier]

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

package metier;

...

@Transactional
public class Metier implements IMetier {

   // ارجاع به لایه [DAO]
  private ICotisationDao cotisationDao = null;
  private IEmployeDao employeDao=null;


   //دریافت فیش حقوقی
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
...
  }

   // فهرست کارکنان
   public List<Employe> findAllEmployes() {
     ...
  }

   // گیرنده و تنظیم‌کننده
...
 }
  • خط ۵: تگ @Transactional در Spring تضمین می‌کند که هر متد در کلاس در داخل یک تراکنش اجرا شود.
  • خطوط ۹–۱۰: ارجاع به لایه‌های [DAO] از اشیاء [Cotisation, Employe, Indemnite]
  • خطوط 14–17: متد [calculerFeuilleSalaire]
  • خطوط ۲۰–۲۲: متد [findAllEmployes]
  • از خط ۲۴ به بعد: دسترسی‌دهنده‌های عمومی برای فیلدهای خصوصی کلاس

سؤال: کد متد [findAllEmployes] را بنویسید.



سؤال: کد متد [calculerFeuilleSalaire] را بنویسید.


لطفاً نکات زیر را توجه کنید:

  • روش محاسبه حقوق در بخش ۵.۲ توضیح داده شده است.
  • اگر پارامتر [SS] به هیچ کارمندی مطابقت نداشته باشد (لایه [DAO] یک نشانگر null را بازگردانده باشد)، روش یک استثنای [PamException] را با کد خطای مناسب پرتاب خواهد کرد.

5.10.4. آزمایش لایه [metier]

ما دو برنامه آزمایشی ایجاد می‌کنیم:

کلاس‌های تست [3] در پکیج [metier] [2] و در شاخه [Test Packages] [1] پروژه ایجاد شده‌اند.

کلاس [JUnitMetier_1] می‌تواند به صورت زیر باشد:

package metier;

...

public class JUnitMetier_1 {

//لایه کسب‌وکار
  private IMetier metier;

  @BeforeClass
  public void init(){
     // لاگ
    log("init");
     // پیکربندی برنامه
     // مثال‌سازی لایه [metier]
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
    metier = (IMetier) ctx.getBean("metier");
     // لایه‌های DAO
    IEmployeDao employeDao=(IEmployeDao) ctx.getBean("employeDao");
    ICotisationDao cotisationDao=(ICotisationDao) ctx.getBean("cotisationDao");
    IIndemniteDao indemniteDao=(IIndemniteDao) ctx.getBean("indemniteDao");
     // پایگاه داده تخلیه می‌شود
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // پایگاه داده پر شده است
    Indemnite indemnite1=indemniteDao.create(new Indemnite(1,1.93,2,3,12));
    Indemnite indemnite2=indemniteDao.create(new Indemnite(2,2.1,2.1,3.1,15));
    Employe employe2=employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    Employe employe1=employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    Cotisation cotisation1=cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }

   // لاگ‌ها
  private void log(String message) {
    System.out.println("----------- " + message);
  }

   // آزمایش
  @Test
  public void test01(){
     // محاسبه حقوق و دستمزد
    System.out.println(metier.calculerFeuilleSalaire("260124402111742",30, 5));
    System.out.println(metier.calculerFeuilleSalaire("254104940426058",150, 20));
    try {
      System.out.println(metier.calculerFeuilleSalaire("xx", 150, 20));
    } catch (PamException ex) {
      System.err.println(String.format("PamException[Code=%d, message=%s]",ex.getCode(), ex.getMessage()));
    }
  }
}

در این کلاس هیچ گزاره Assert.assertCondition وجود ندارد. ما صرفاً در تلاشیم چند حقوق را محاسبه کنیم تا سپس بتوانیم آن‌ها را به‌صورت دستی بررسی کنیم. خروجی صفحه نمایش حاصل از اجرای کلاس قبلی به شرح زیر است:

1
2
3
4
5
6
7
Testsuite: metier.JUnitMetier_1
----------- init
....
[jpa.Employe[id=22,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La brûlerie,ville=St Marcel,code postal=49014,indice=1],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88],jpa.Indemnite[id=29,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0],[salaire base=64.85,cotisations sociales=17.45,indemnités d'entretien=10.0,indemnités de repas=15.0,salaire net=72.4]]
[jpa.Employe[id=21,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88],jpa.Indemnite[id=30,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités CP=15.0],[salaire base=362.25,cotisations sociales=97.48,indemnités d'entretien=42.0,indemnités de repas=62.0,salaire net=368.77]]
PamException[Code=101, message=L'employé de n°[xx] est introuvable]
Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,234 sec
  • خط ۴: فیش حقوقی ژوستین لاورتی
  • خط ۵: فیش حقوقی ماری جووینال
  • خط ۶: استثنایی که به دلیل وجود نداشتن کارمند با شناسه SS 'xx' رخ داده است.

سؤال: خط 17 از [JUnitMetier_1] از Spring bean با نام metier استفاده می‌کند. تعریف این bean را در فایل [spring-config-metier-dao.xml] ارائه دهید.


کلاس [JUnitMetier_2] می‌تواند به صورت زیر باشد:

package metier;

...
public class JUnitMetier_2 {

//لایه کسب‌وکار
  private IMetier metier;

  @BeforeClass
  public void init(){
...
  }

   // گزارش‌ها
  private void log(String message) {
    System.out.println("----------- " + message);
  }

   // آزمایش
  @Test
  public void test01(){
...
  }
}

کلاس [JUnitMetier_2] کپی‌ای از کلاس [JUnitMetier_1] است، با این تفاوت که این بار، assertionها به متد test01 اضافه شده‌اند.


وظیفه: متد test01 را بنویسید.


وقتی کلاس [JUnitMetier_2] اجرا می‌شود، در صورت موفقیت‌آمیز بودن، نتایج زیر به دست می‌آیند:

Image

5.11. لایه [ui] از برنامه [PAM] – نسخه کنسول

اکنون که لایه [metier] نوشته شده است، هنوز باید لایه‌های [ui] و [1] را بنویسیم:

ما دو پیاده‌سازی متفاوت از لایه [ui] ایجاد خواهیم کرد: یک نسخه console و یک نسخه گرافیکی swing:

5.11.1. کلاس [ui.console.Main]

ابتدا اپلیکیشن کنسول پیاده‌سازی‌شده توسط کلاس [ui.console.Main] فوق را بررسی می‌کنیم. عملکرد آن در بخش 5.3 شرح داده شده است. اسکلت کلاس [Main] ممکن است به شکل زیر باشد:

package ui.console;

import exception.PamException;
import metier.FeuilleSalaire;
import metier.IMetier;

import java.util.ArrayList;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  /**
   * @param args
   */
  public static void main(String[] args) {
     // داده‌های محلی
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
     // بررسی تعداد پارامترها
...
     // فهرست خطاها
    ArrayList erreurs = new ArrayList();
     // پارامتر دوم باید یک عدد حقیقی >0 باشد
...
     // خطا؟
    if (...) {
      erreurs.add("Le nombre d'heures travaillées [" + args[1]
        + "] est erroné");
    }
     // پارامتر سوم باید یک عدد صحیح بزرگ‌تر از 0 باشد
...
     // خطا؟
    if (...) {
      erreurs.add("Le nombre de jours travaillés [" + args[2]
        + "] est erroné");
    }
     // آیا خطایی وجود دارد؟
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // بسیار خب – می‌توانیم فیش حقوقی را درخواست کنیم
    FeuilleSalaire feuilleSalaire = null;
    try {
       //نمونه‌سازی لایه [metier]
      ...
       //محاسبه برگه پرداخت
      ...
    } catch (PamException ex) {
      System.err.println("L'erreur suivante s'est produite : "+ ex.getMessage());
      return;
    } catch (Exception ex) {
      System.err.println("L'erreur suivante s'est produite : "+ ex.toString());
      return;
    }

     // نمایش تفصیلی
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
    output += ajouteInfo("Nombre d'heures travaillées", args[1]);
    output += ajouteInfo("Nombre de jours travaillés", args[2]);
    output += ajouteInfo("\nInformations Employé", "");
    output += ajouteInfo("Nom", feuilleSalaire.getEmploye().getNom());
    output += ajouteInfo("Prénom", feuilleSalaire.getEmploye().getPrenom());
    output += ajouteInfo("Adresse", feuilleSalaire.getEmploye().getAdresse());
    output += ajouteInfo("Ville", feuilleSalaire.getEmploye().getVille());
    output += ajouteInfo("Code Postal", feuilleSalaire.getEmploye().getCodePostal());
    output += ajouteInfo("Indice", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndice());
    output += ajouteInfo("\nInformations Cotisations", "");
    output += ajouteInfo("CSGRDS", ""+ feuilleSalaire.getCotisation().getCsgrds() + " %");
    output += ajouteInfo("CSGD", ""+ feuilleSalaire.getCotisation().getCsgd() + " %");
    output += ajouteInfo("Retraite", ""+ feuilleSalaire.getCotisation().getRetraite() + " %");
    output += ajouteInfo("Sécurité sociale", ""+ feuilleSalaire.getCotisation().getSecu() + " %");
    output += ajouteInfo("\nInformations Indemnités", "");
    output += ajouteInfo("Salaire horaire", ""+ feuilleSalaire.getEmploye().getIndemnite().getBaseHeure() + " euro");
    output += ajouteInfo("Entretien/jour", ""+ feuilleSalaire.getEmploye().getIndemnite().getEntretienJour() + " euro");
    output += ajouteInfo("Repas/jour", ""+ feuilleSalaire.getEmploye().getIndemnite().getRepasJour() + " euro");
    output += ajouteInfo("Congés Payés", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndemnitesCP()+ " %");
    output += ajouteInfo("\nInformations Salaire", "");
    output += ajouteInfo("Salaire de base", ""+ feuilleSalaire.getElementsSalaire().getSalaireBase()+ " euro");
    output += ajouteInfo("Cotisations sociales", ""+ feuilleSalaire.getElementsSalaire().getCotisationsSociales()+ " euro");
    output += ajouteInfo("Indemnités d'entretien", ""+ feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ " euro");
    output += ajouteInfo("Indemnités de repas", ""+ feuilleSalaire.getElementsSalaire().getIndemnitesRepas()+ " euro");
    output += ajouteInfo("Salaire net", ""+ feuilleSalaire.getElementsSalaire().getSalaireNet() + " euro");

    System.out.println(output);
  }

  static String ajouteInfo(String message, String valeur) {
    return message + " : " + valeur + "\n";
  }
}

سؤال: کد بالا را تکمیل کنید.


5.11.2. اجرا

برای اجرای کلاس [ui.console.Main]، مراحل زیر را انجام دهید:

  • در [1]، ویژگی‌های پروژه را انتخاب کنید،
  • در [2]، ویژگی پروژه [Run] را انتخاب کنید،
  • از دکمه [3] برای مشخص کردن کلاسی که باید اجرا شود (که به عنوان کلاس اصلی شناخته می‌شود) استفاده کنید،
  • کلاس [4] را انتخاب کنید،
  • این کلاس در [5] ظاهر می‌شود. این کلاس برای اجرا به سه آرگومان نیاز دارد (شماره SS، تعداد ساعات کاری، تعداد روزهای کاری). این آرگومان‌ها در [6] وارد می‌شوند،
  • پس از انجام این کار، پروژه [7] قابل اجرا خواهد بود. پیکربندی قبلی به این معناست که کلاس [ui.console.Main] اجرا خواهد شد.

نتایج اجرای برنامه در پنجره [output] نمایش داده می‌شوند:

5.12. لایه [ui] از برنامه [PAM] – نسخه گرافیکی

ما اکنون در حال پیاده‌سازی لایه [ui] با رابط کاربری گرافیکی هستیم:

  • در [1]، کلاس [PamJFrame] رابط کاربری گرافیکی
  • به [2]: رابط گرافیکی

5.12.1. یک آموزش سریع

برای ایجاد رابط کاربری گرافیکی، می‌توانید به شرح زیر اقدام کنید:

  • [1]: با استفاده از دکمه [1] یک فایل جدید ایجاد کنید [New File...]
  • [2]: دسته فایل [Swing GUI Formsc.a.d را انتخاب کنید. فرم‌های گرافیکی
  • [3]: نوع [JFrame Form] را انتخاب کنید، یک نوع فرم خالی
  • [5]: به فرم یک نام بدهید که همچنین یک کلاس خواهد بود
  • [6]: قرار دادن فرم در یک بسته
  • [8]: فرم به درخت پروژه اضافه می‌شود
  • [9]: می‌توان از طریق دو دیدگاه به فرم دسترسی داشت: [Design] و [9] که به شما امکان می‌دهند اجزای مختلف فرم را طراحی کنید، و [Source] و [10 ci-dessous] که دسترسی به کد جاوا فرم را فراهم می‌کنند. در نهایت، یک فرم مانند هر کلاس دیگری در جاوا است. نمای [Design] ابزاری برای طراحی فرم است. هر بار که یک مؤلفه در حالت [Design] اضافه می‌شود، کد جاوا در نمای [Source] برای ادغام آن افزوده می‌شود.
  • [11]: فهرست کامپوننت‌های Swing موجود برای یک فرم را می‌توان در پنجره [Palette] یافت.
  • [12]: پنجره [Inspector] ساختار درختی اجزای فرم را نمایش می‌دهد. اجزایی که نمایش بصری دارند در شاخه [JFrame] قرار می‌گیرند، در حالی که سایر اجزا در شاخه [Other Components] خواهند بود.
  • در [13]، با یک کلیک یک مؤلفه [JLabel] را انتخاب می‌کنیم
  • در [14]، آن را در حالت [Design] روی فرم می‌کشیم
  • در [15]، ما ویژگی‌های JLabel (متن، قلم) را تعریف می‌کنیم.
  • در [16]، نتیجهٔ به‌دست‌آمده.
  • در [17]، پیش‌نمایش فرم را درخواست می‌کنیم
  • در [18]، نتیجه
  • در [19]، برچسب [JLabel1] به درخت مؤلفه در پنجره [Inspector] اضافه شده است
  • در [20] و [21]: در نمای [Source] فرم، کد جاوا برای رسیدگی به JLabel افزوده شده است.

یک آموزش در مورد ساخت فرم‌ها با NetBeans در آدرس [http://www.netbeans.org/kb/trails/matisse.html] موجود است.

5.12.2. رابط کاربری گرافیکی [PamJFrame]

ما رابط کاربری گرافیکی زیر را خواهیم ساخت:

  • در [1]، رابط کاربری گرافیکی
  • در [2]، ساختار درختی اجزای آن: یک JLabel و شش JPanel

JLabel1

JPanel1

JPanel2

JPanel3

JPanel4

JPanel5


تمرین عملی: رابط کاربری گرافیکی قبلی را با استفاده از آموزش [http://www.netbeans.org/kb/trails/matisse.html] بسازید.


5.12.3. رویدادهای رابط کاربری گرافیکی

مطالعهٔ پیشنهادی: فصل [Interfaces graphiques] از [ref2].

ما کلیک روی دکمه [jButtonSalaire] را مدیریت خواهیم کرد. برای ایجاد متدی جهت رسیدگی به این رویداد، می‌توانیم به شرح زیر عمل کنیم:

روندکار کلیک روی دکمه [JButtonSalaire] تولید می‌شود:

1
2
3
    private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {
       // TODO کد رسیدگی خود را اینجا اضافه کنید:
}

کد جاوا که متد قبلی را با کلیک روی دکمه [JButtonSalaire] مرتبط می‌کند نیز تولید می‌شود:

1
2
3
4
5
6
    jButtonSalaire.setText("Salaire");
    jButtonSalaire.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButtonSalaireActionPerformed(evt);
      }
});

خطوط ۲–۵ مشخص می‌کنند که کلیک (نوع رویداد ActionPerformed) روی دکمه [jButtonSalaire] (خط ۲) باید توسط متد [jButtonSalaireActionPerformed] (خط ۴) مدیریت شود.

ما همچنین رویداد [caretUpdate] (حرکت نشانگر ورودی) را روی فیلد ورودی [jTextFieldHT] مدیریت خواهیم کرد. برای ایجاد دست‌پرداز این رویداد، همانند قبل پیش می‌رویم:

پردازشگر رویداد [caretUpdate] روی فیلد ورودی [jTextFieldHT] تولید شده است:

  private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {                                         
 ...
  }

کد جاوا که متد قبلی را با رویداد [caretUpdate] در فیلد ورودی [jTextFieldHT] مرتبط می‌سازد نیز تولید می‌شود:

1
2
3
4
5
    jTextFieldHT.addCaretListener(new javax.swing.event.CaretListener() {
      public void caretUpdate(javax.swing.event.CaretEvent evt) {
        jTextFieldHTCaretUpdate(evt);
      }
});

خطوط ۱–۴ نشان می‌دهند که رویداد [caretUpdate] (خط ۲) روی دکمه [jTextFieldHT] (خط ۱) باید توسط متد [ jTextFieldHTCaretUpdate] (خط ۳) پردازش شود.

5.12.4. ابتکار رابط کاربری گرافیکی

بیایید به معماری برنامهٔ خود بازگردیم:

لایه [ui] به مرجعی به لایه [metier] نیاز دارد. بیایید به یاد بیاوریم که این مرجع چگونه در برنامه console به دست آمد:

1
2
3
    //نمونه‌سازی لایه [metier]
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
IMetier metier = (IMetier) ctx.getBean("metier");

روش در برنامه گرافیکی نیز یکسان است. هنگامی که برنامه راه‌اندازی می‌شود، مرجع [IMetier metier] از خط ۳ بالا نیز باید راه‌اندازی شود. کد تولید شده برای رابط گرافیکی در حال حاضر به شرح زیر است:

package ui.swing;

...
public class PamJFrame extends javax.swing.JFrame {

   /** یک فرم جدید PamJFrame ایجاد می‌کند */
  public PamJFrame() {
    initComponents();
  }

   /** این متد از درون سازنده فراخوانی می‌شود تا
   * initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
   // <editor-fold defaultstate="collapsed" desc="کد تولید شده">
  private void initComponents() {
...
  }// </editor-fold>

  private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {                                         
 ...
  }                                        

  private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {                                               
...
  }                                              

  public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new PamJFrame().setVisible(true);
      }
    });
  }

   // اعلام متغیرها – تغییر ندهید
  private javax.swing.JButton jButtonSalaire;
...
   // پایان اعلام متغیر

}
  • خطوط ۲۹–۳۵: متد استاتیک [main]، که برنامه را راه‌اندازی می‌کند
  • خط ۳۲: یک نمونه از رابط کاربری گرافیکی [PamJFrame] ایجاد و قابل مشاهده می‌شود.
  • خطوط ۷–۹: سازنده رابط کاربری گرافیکی.
  • خط ۸: فراخوانی متد [initComponents] که در خط ۱۷ تعریف شده است. این متد از روی کاری که در حالت [Design] انجام شده، به‌طور خودکار تولید شده است. نباید تغییر داده شود.
  • خط ۲۱: متدی که حرکت نشانگر ورودی را در فیلد [jTextFieldHT] مدیریت می‌کند
  • خط ۲۵: متدی که کلیک روی دکمه [jButtonSalaire] را مدیریت می‌کند

برای افزودن مقداردهی‌های اولیهٔ خود به کد بالا، می‌توانیم به شرح زیر عمل کنیم:

  /** فرم جدید PamJFrame را ایجاد می‌کند */
  public PamJFrame() {
    initComponents();
    doMyInit();
  }

...

   // متغیرهای نمونه
  private IMetier metier=null;
  private List<Employe> employes=null;
  private String[] employesCombo=null;
  private double heuresTravaillées=0;

   // ابتکاری‌سازی‌های سفارشی
  public void doMyInit(){
     // ابتکاری‌سازی زمینه
    try{
       // نمونه سازی لایه [metier]
...
     // فهرست کارمندان
...
    }catch (PamException ex){
     // پیام استثنا در [jTextAreaStatus] ذخیره می‌شود
...
     //بازگشت
      return;
    }
     // دکمه حقوق غیرفعال است
...
     //jScrollPane1 مخفی
...
     // چرخ‌فلک روزهای کاری
    jSpinnerJT.setModel(new SpinnerNumberModel(0,0,31,1));
     // منوی کشویی کارمندان
    employesCombo=new String[employes.size()];
    int i=0;
    for(Employe employe : employes){
      employesCombo[i++]=employe.getPrenom()+" "+employe.getNom();
    }
    jComboBoxEmployes.setModel(new DefaultComboBoxModel(employesCombo));
}
  • خط ۴: ما یک متد اختصاصی را برای انجام инициалиزاسیون‌های خود فراخوانی می‌کنیم. این‌ها توسط کد در خطوط ۱۰–۴۲ تعریف شده‌اند.

سؤال: با استفاده از نظرات به‌عنوان راهنما، کد رویه [doMyInit] را تکمیل کنید.


5.12.5. پردازشگرهای رویداد


سؤال: متد [jTextFieldHTCaretUpdate] را بنویسید. این متد باید تضمین کند که اگر مقدار فیلد [jTextFieldHT] یک عدد حقیقی ≥ 0 نباشد، دکمه [jButtonSalaire] غیرفعال شود.



سؤال: متد [jButtonSalaireActionPerformed] را بنویسید که باید فیش حقوقی کارمند انتخاب‌شده در [jComboBoxEmployes] را نمایش دهد.


5.12.6. اجرای رابط کاربری گرافیکی

برای اجرای رابط کاربری گرافیکی، پیکربندی پروژه [Run] را اصلاح خواهیم کرد:

  • به [1]، و کلاس GUI را تنظیم کنید

پروژه باید به همراه فایل‌های پیکربندی آن (persistence.xml, spring-config-metier-dao.xml) و کلاس GUI کامل باشد. قبل از اجرای پروژه، هدف SGBD را راه‌اندازی کنید.

ما به معماری زیر علاقه‌مند هستیم، که در آن لایه JPA اکنون توسط EclipseLink پیاده‌سازی شده است:

5.13.1. پروژه NetBeans

پروژه جدید NetBeans با کلون کردن پروژه قبلی ایجاد می‌شود:

  • به [1]: پس از کلیک راست روی پروژه Hibernate، Copy را انتخاب کنید
  • با استفاده از دکمه [2]، پوشه والد برای پروژه جدید را انتخاب کنید. نام پوشه در [3] ظاهر می‌شود.
  • در [4]، برای پروژه جدید یک نام انتخاب کنید
  • در [5]، نام پوشه پروژه
  • در [1]، پروژه جدید ایجاد شده است. نام آن با نام اصلی یکسان است،
  • در [2] و [3]، نام آن به [mv-pam-spring-eclipselink] تغییر یافت.

برای تطبیق پروژه با لایه جدید JPA / EclipseLink، باید در دو مکان اصلاح شود:

  1. در [4]، فایل‌های پیکربندی Spring باید اصلاح شوند. این فایل‌ها در حال حاضر پیکربندی لایه JPA را در خود دارند.
  2. در [5]، کتابخانه‌های پروژه باید اصلاح شوند: کتابخانه‌های Hibernate باید با کتابخانه‌های موجود در EclipseLink جایگزین شوند.

بیایید با این نکته آخر شروع کنیم. فایل [pom.xml] برای پروژه جدید به شرح زیر خواهد بود:


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

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

  <name>mv-pam-spring-eclipselink</name>
  <url>http://maven.apache.org</url>
  <repositories>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>swing-layout</id>
      <layout>default</layout>
      <name>Repository for library Library[swing-layout]</name>
    </repository>
    <repository>
      <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
      <id>eclipselink</id>
      <layout>default</layout>
      <name>Repository for library Library[eclipselink]</name>
    </repository>    
  </repositories>
  
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <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>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>eclipselink</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>javax.persistence</artifactId>
      <version>2.0.3</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
</project>
  • خطوط ۷۳–۸۲: وابستگی‌ها برای پیاده‌سازی JPA و EclipseLink،
  • خطوط ۱۹–۲۴: مخزن Maven برای EclipseLink.

فایل‌های پیکربندی Spring باید اصلاح شوند تا منعکس‌کننده تغییر پیاده‌سازی JPA باشند. در هر دو فایل، تنها بخشی که لایه JPA را پیکربندی می‌کند تغییر می‌کند. برای مثال، در [spring-config-metier-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">

   <!-- لایه‌های برنامه -->
   <!- - DAO -->
  <bean id="employeDao" class="dao.EmployeDao" />
  <bean id="indemniteDao" class="dao.IndemniteDao" />
  <bean id="cotisationDao" class="dao.CotisationDao" />
   <!-- کسب‌وکار -->
  <bean id="metier" class="metier.Metier">
    <property name="employeDao" ref="employeDao"/>
    <property name="indemniteDao" ref="indemniteDao"/>
    <property name="cotisationDao" ref="cotisationDao"/>  
  </bean>

   <!--پیکربندی JPA -->
  <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="showSql" value="true" />
    -->
        <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
        <property name="generateDdl" value="true" />
   <!--
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
    <property name="loadTimeWeaver">
      <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </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/dbpam_hibernate" />
    <property name="username" value="root" />
<!--
    <property name="password" value="" />
-->
  </bean>
....  
</beans>

خطوط ۱۹ تا ۳۶ لایه JPA را پیکربندی می‌کنند. پیاده‌سازی مورد استفاده برای JPA، Hibernate (خط ۲۲) است. علاوه بر این، پایگاه داده هدف [dbpam_hibernate] (خط ۴۱) است.

برای تغییر به پیاده‌سازی JPA / EclipseLink، خطوط ۱۹–۳۵ بالا با خطوط زیر جایگزین می‌شوند:

   <!-- پیکربندی JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
        <!--
          <property name="showSql" value="true" />
  -->
        <property name="databasePlatform" value="org.eclipse.persistence.platform.database.MySQLPlatform" />
        <!--
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
    <property name="loadTimeWeaver">
      <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
</bean>
  • خط ۵: پیاده‌سازی مورد استفاده JPA، EclipseLink است
  • خط ۹: خاصیت databasePlatform هدف SGBD را تعیین می‌کند، در این مورد MySQL
  • خط ۱۱: برای ایجاد جداول پایگاه داده هنگام نمونه‌سازی لایه JPA. در اینجا، این خاصیت با علامت # غیرفعال شده است.
  • خط ۷: برای نمایش دستورات SQL صادر شده توسط لایه JPA در کنسول. در اینجا، این خاصیت کامنت شده است.

علاوه بر این، پایگاه داده هدف به [dbpam_eclipselink] تبدیل می‌شود (خط ۴ زیر):

1
2
3
4
5
6
7
8
9
<!--منبع داده 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/dbpam_eclipselink" />
    <property name="username" value="root" />
<!--
    <property name="password" value="" />
-->
  </bean>

5.13.2. پیاده‌سازی تست‌ها

قبل از آزمایش کل برنامه، توصیه می‌شود بررسی کنید که آیا تست‌های JUnit با پیاده‌سازی جدید JPA با موفقیت اجرا می‌شوند یا خیر. قبل از انجام این کار، با حذف جداول از پایگاه داده شروع می‌کنیم. برای این کار، در زبانه [Runtime] در NetBeans، در صورت لزوم، یک اتصال به پایگاه داده dbpam_eclipselink / MySQL5 ایجاد خواهیم کرد. پس از اتصال به پایگاه داده dbpam_eclipselink / MySQL5، می‌توانید طبق مراحل زیر اقدام به حذف جداول کنید:

  • [1]: قبل از حذف
  • [2]: پس از حذف

پس از انجام این کار، اولین تست را می‌توان روی لایه [DAO] اجرا کرد: InitDB که پایگاه داده را پر می‌کند. برای اطمینان از اینکه جداول حذف‌شده قبلی توسط برنامه دوباره ایجاد می‌شوند، باید اطمینان حاصل کنید که در پیکربندی Spring برای JPA / EclipseLink، خط:

        <property name="generateDdl" value="true" />

وجود داشته باشد و کامنت نشده باشد.

ما پروژه را ساخت می‌کنیم و سپس دستور [JUnitInitDB] test را اجرا می‌کنیم:

  • در [1]، تست InitDB اجرا می‌شود.
  • در [2]، این تست شکست می‌خورد. استثنا توسط Spring پرتاب می‌شود و نه توسط تستی که شکست خورده است.

مسبب: org.springframework.beans.factory.BeanCreationException: خطا در ایجاد bean با نام 'entityManagerFactory' تعریف‌شده در منبع مسیر کلاس [spring-config-DAO.xml]: فراخوانی متد init ناموفق بود؛ استثنای داخلی java.lang.IllegalStateException است: برای استفاده از InstrumentationLoadTimeWeaver باید با یک ایجنت جاوا شروع کنید. به مستندات Spring مراجعه کنید.

اسپرینگ نشان می‌دهد که یک مشکل پیکربندی وجود دارد. پیام واضح نیست. دلیل این استثنا در بخش 3.1.9 از [ref1] توضیح داده شده است. برای اینکه پیکربندی Spring / EclipseLink کار کند، JVM که برنامه را اجرا می‌کند، باید با یک پارامتر خاص راه‌اندازی شود: یک ایجنت جاوا. قالب این پارامتر به شرح زیر است:

-javaagent:C:\...\spring-agent.jar

[spring-agent.jar] عامل جاوا (Java agent) مورد نیاز JVM برای مدیریت پیکربندی Spring / EclipseLink است.

هنگام اجرای یک پروژه، امکان ارسال آرگومان به JVM وجود دارد:

  • در [1]، می‌توانید به ویژگی‌های پروژه دسترسی داشته باشید
  • در [2]، ویژگی‌های Run
  • در [3]، پارامتر -javaagent به JVM ارسال می‌شود

5.13.3. InitDB

ما اکنون آماده‌ایم تا [InitDB] را دوباره آزمایش کنیم. این بار، نتایج به‌دست‌آمده به شرح زیر است:

  • در [1]، تست با موفقیت انجام شد
  • در [2]، در برگه [Services]، اتصال NetBeans به پایگاه داده [dbpam_eclipselink] را تازه می‌کنیم
  • در [3]، چهار جدول ایجاد شده است
  • در [5]، محتویات جدول [employes] را مشاهده کنید
  • در [6]، نتیجه.

5.13.4. JUnitDao

اجرای کلاس تست [JUnitDao] ممکن است با شکست مواجه شود، حتی اگر با پیاده‌سازی JPA / Hibernate موفق بوده باشد. برای درک دلیل آن، بیایید یک مثال را تحلیل کنیم.

متد مورد آزمایش، متد زیر IndemniteDao.create است:

package dao;

...
@Transactional(propagation=Propagation.REQUIRED)
public class IndemniteDao implements IIndemniteDao{

  @PersistenceContext
  private EntityManager em;

   // تولیدکننده
  public IndemniteDao() {
  }

   //ایجاد یک مزیت
  public Indemnite create(Indemnite indemnite) {
    try{
      em.persist(indemnite);
    }catch(Throwable th){
      throw new PamException(th,31);
    }
    return indemnite;
  }

...
}
  • خطوط ۱۵–۲۲: متد تحت تست

روش تست به شرح زیر است:


package dao;

...

public class JUnitDao {

// لایه‌ها DAO
  static private IEmployeDao employeDao;
  static private IIndemniteDao indemniteDao;
  static private ICotisationDao cotisationDao;

  @BeforeClass
  public static void init() {
    // لاگ
    log("init");
    // پیکربندی برنامه
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
    //لایه‌ها DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
  }

  @Before()
  public void clean() {
    // خالی کردن پایگاه داده
    for (Employe employe : employeDao.findAll()) {
      employeDao.destroy(employe);
    }
    for (Cotisation cotisation : cotisationDao.findAll()) {
      cotisationDao.destroy(cotisation);
    }
    for (Indemnite indemnite : indemniteDao.findAll()) {
      indemniteDao.destroy(indemnite);
    }
  }

  // لاگ‌ها
  private static void log(String message) {
    System.out.println("----------- " + message);
  }

  // آزمایش‌ها
….
  @Test
  public void test05() {
    log("test05");
    //ایجاد دو امتیاز با یک شاخص یکسان
    // محدودیت یکتایی ایندکس را نقض می‌کند
    boolean erreur = true;
    Indemnite indemnite1 = null;
    Indemnite indemnite2 = null;
    Throwable th = null;
    try {
      indemnite1 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
      indemnite2 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
      erreur = false;
    } catch (PamException ex) {
      th = ex;
      //بررسی‌ها
      Assert.assertEquals(31, ex.getCode());
    } catch (Throwable th1) {
      th = th1;
    }
    //بررسی‌ها
    Assert.assertTrue(erreur);
    // زنجیره استثنا
    System.out.println("Chaîne des exceptions --------------------------------------");
    System.out.println(th.getClass().getName());
    while (th.getCause() != null) {
      th = th.getCause();
      System.out.println(th.getClass().getName());
    }
    //اولین پرداخت جبرانی باید ذخیره شده باشد
    Indemnite indemnite = indemniteDao.find(indemnite1.getId());
    //بررسی
    Assert.assertNotNull(indemnite);
    Assert.assertEquals(1, indemnite.getIndice());
    Assert.assertEquals(1.93, indemnite.getBaseHeure(), 1e-6);
    Assert.assertEquals(2, indemnite.getEntretienJour(), 1e-6);
    Assert.assertEquals(3, indemnite.getRepasJour(), 1e-6);
    Assert.assertEquals(12, indemnite.getIndemnitesCP(), 1e-6);
    // پرداخت دوم جبران خسارت نباید ادامه یافته باشد
    List<Indemnite> indemnites = indemniteDao.findAll();
    int nbIndemnites = indemnites.size();
    Assert.assertEquals(nbIndemnites, 1);
  }

...
}

سؤال: توضیح دهید آزمون test05 چه کاری انجام می‌دهد و نتایج مورد انتظار را بیان کنید.


نتایج به‌دست‌آمده با لایه JPA / Hibernate به شرح زیر است:

----------- test05
4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
ATTENTION: SQL Error: 1062, SQLState: 23000
4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
GRAVE: Duplicate entry '1' for key 2
Chaîne des exceptions --------------------------------------
exception.PamException
javax.persistence.EntityExistsException
org.hibernate.exception.ConstraintViolationException
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

تست با موفقیت انجام می‌شود (c.a.d)، اعتبارسنجی‌ها تأیید می‌شوند و هیچ استثنایی از متد تست پرتاب نمی‌شود.


سؤال: توضیح دهید چه اتفاقی افتاد.


نتایج به‌دست‌آمده با لایه JPA / EclipseLink به شرح زیر است:

----------- test05
[EL Warning]: 2010-06-04 16:48:26.421--UnitOfWork(749304)--استثناء [EclipseLink-4002] (خدمات پایداری اکلیپس - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 2
Error Code: 1062
Call: INSERT INTO INDEMNITES (ID, ENTRETIEN_JOUR, REPAS_JOUR, INDICE, INDEMNITES_CP, BASE_HEURE, VERSION) VALUES (?, ?, ?, ?, ?, ?, ?)
        bind => [108, 2.0, 3.0, 1, 12.0, 1.93, 1]
Query: InsertObjectQuery(jpa.Indemnite[id=108,version=1,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0])
Chaîne des exceptions --------------------------------------
org.springframework.transaction.TransactionSystemException
javax.persistence.RollbackException
org.eclipse.persistence.exceptions.DatabaseException
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

همانند مورد قبلی با Hibernate، تست با موفقیت (c.a.d) به پایان رسید، با اعتبارسنجی ادعاها و بدون پرتاب هیچ استثنایی از متد تست.


سؤال: توضیح دهید چه اتفاقی افتاده است.



سؤال: از این دو مثال چه نتیجه‌ای می‌توان دربارهٔ قابلیت تعویض پیاده‌سازی‌های JPA گرفت؟ آیا در این مورد کامل است؟


5.13.5. تست‌های دیگر

پس از آنکه لایه [DAO] آزمایش و صحیح تشخیص داده شد، می‌توانیم به آزمایش لایه [metier] و خود پروژه در نسخه کنسولی یا گرافیکی آن بپردازیم. تغییر در پیاده‌سازی JPA هیچ تأثیری بر لایه‌های [metier] و [ui] ندارد؛ بنابراین، اگر این لایه‌ها با Hibernate کار می‌کردند، آنها با EclipseLink با چند استثنا کار خواهند کرد: مثال قبلی نشان می‌دهد که استثناهای پرتاب‌شده توسط لایه‌های [DAO] ممکن است متفاوت باشد. بنابراین، هنگام اجرای تست، Spring / JPA / Hibernate یک استثنای از نوع [PamException] را پرتاب می‌کند، استثنایی که مختص برنامه [pam] است، در حالی که Spring / JPA / از سوی دیگر، EclipseLink استثنایی از نوع [TransactionSystemException] را پرتاب می‌کند که یک استثنای فریم‌ورک Spring است. اگر در مورد استفادهٔ آزمایشی، لایهٔ [ui] انتظار استثنایی از نوع [PamException] را داشته باشد، زیرا با استفاده از Hibernate ساخته شده است، پس از تغییر به EclipseLink دیگر کار نخواهد کرد.

5.13.6. وظیفه


وظیفه عملی: مجدداً اجرای تست‌ها برای برنامه‌های console و swing با استفاده از نمونه‌های مختلف SGBD: MySQL5، Oracle XE، سرور SQL.