5. نسخهٔ ۱: معماری اسپرینگ / JPA
هدف نوشتن یک برنامه کنسولی و یک برنامه گرافیکی برای تولید فیشهای حقوقی مراقبان کودک است که توسط «مرکز سالهای اولیه» یک نهاد محلی استخدام شدهاند. این برنامه معماری زیر را خواهد داشت:
![]() |
5.1. BD پایگاه داده
دادههای ثابتی که برای تولید فیش حقوقی لازم است در پایگاهدادهٔ dbpam ذخیره خواهد شد. این پایگاهداده میتواند شامل جداول زیر باشد:
ساختار:
کلید اصلی | |
شماره نسخه – با هر تغییر در سطر افزایش مییابد | |
شماره بیمه اجتماعی ملی کارمند – منحصر به فرد | |
نام کارمند | |
نام کوچک کارمند | |
آدرس آنها | |
شهر او | |
کد پستی او | |
کلید خارجی روی فیلد [ID] در جدول [INDEMNITES] |
محتوای آن میتواند به شرح زیر باشد:

ساختار:
کلید اصلی | |
شماره نسخه – با هر اصلاح در سطر افزایش مییابد | |
درصد: مشارکت اجتماعی کلی + مشارکت در بازپرداخت بدهی اجتماعی | |
درصد: سهم اجتماعی عمومی قابل کسر | |
درصد: تأمین اجتماعی، مستمری بیوگی، مستمری پیری | |
درصد: مستمری تکمیلی + بیمه بیکاری |
محتوای آن میتواند به شرح زیر باشد:
![]()
نرخهای حق بیمه تأمین اجتماعی به کارمند بستگی ندارند. جدول بالا تنها شامل یک سطر است.
کلید اصلی | ||
شماره نسخه – با هر اصلاح در سطر افزایش مییابد | ||
شاخص پردازش – یکتا | ||
قیمت خالص به یورو برای یک ساعت آمادهباش | ||
کمکهزینه نگهداری به یورو برای هر روز مراقبت | ||
کمکهزینه غذا به یورو برای هر روز مراقبت از کودک | ||
اضافه حقوق تعطیلات. این درصد است که باید به حقوق پایه اعمال شود. | ||
محتوای آن میتواند به شرح زیر باشد:

شایان ذکر است که مزایای دریافتی ممکن است از یک پرستار کودک به دیگری متفاوت باشد. این مزایا در واقع از طریق گروه حقوق به هر پرستار کودک خاص مرتبط میشوند. برای مثال، خانم ماری جووینال که رتبه حقوق ۲ دارد (جدول EMPLOYES)، دستمزد ساعتی ۲.۱ یورو دارد (جدول INDEMNITES).
5.2. روش محاسبه دستمزد یک پرستار کودک
اکنون روش محاسبه حقوق ماهانه یک پرستار کودک را تشریح میکنیم. این مطلب قصد ندارد روشی را که عملاً استفاده میشود منعکس کند. بهعنوان مثال، حقوق خانم ماری جووینال را در نظر میگیریم که در ماه حقوق، به مدت ۲۰ روز و ۱۵۰ ساعت کار کرده است.
عوامل زیر در نظر گرفته میشوند: | | |
حقوق پایه یک پرستار کودک با استفاده از فرمول زیر محاسبه میشود: | ||
مبلغ معینی از حق بیمه تأمین اجتماعی باید از این کسر شود : | | |
مجموع حق بیمه تأمین اجتماعی: | ||
علاوه بر این، پرستار کودک برای هر روز کاری، مستحق دریافت فوقالعاده معیشت و فوقالعاده غذا است. بنابراین، او مزایای زیر را دریافت میکند: | | |
در نهایت، حقوق خالص پرداختی به مراقب کودک به شرح زیر است: |
5.3. نحوه کارکرد برنامه کنسولی
در اینجا مثالی از اجرای برنامه کنسولی در یک پنجره DOS آورده شده است:
ما برنامهای خواهیم نوشت که اطلاعات زیر را دریافت میکند:
- شماره بیمه ملی پرستار کودک (254104940426058 در مثال – خط 1)
- تعداد کل ساعات کاری (۱۵۰ در مثال – خط ۱)
- تعداد کل روزهای کاری (۲۰ در مثال – خط ۱)
میتوانیم ببینیم که:
- ردههای ۹–۱۴: اطلاعات مربوط به کارمندی را که شماره تأمین اجتماعی او ارائه شده است نمایش میدهند
- ردههای ۱۷–۲۰: نرخهای مشارکتهای مختلف را نمایش میدهند
- ردههای ۲۳–۲۶: نمایش مزایای مرتبط با جدول حقوق کارمند (در اینجا، جدول ۲)
- خطوط ۲۹–۳۳: نمایش اجزای حقوق پرداختی
برنامه هرگونه خطا را نشان میدهد:
فراخوانی بدون پارامتر:
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

جدول INDEMNITES

جدول COTISATIONS
![]()
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] را اجرا میکنیم.
![]() |
خروجی کنسول به شرح زیر است:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
کنسول فقط شامل لاگهای Hibernate است، زیرا برنامهای که در حال اجراست کاری جز ایجاد لایه JPA انجام نمیدهد. به نکات زیر توجه کنید:
- خط ۴۳: Hibernate تلاش میکند کلید خارجی را از جدول [EMPLOYES] حذف کند،
- خطوط ۵۱–۵۵: حذف سه جدول،
- خط ۵۷: ایجاد جدول [COTISATIONS]،
- خط ۶۷: ایجاد جدول [EMPLOYES]،
- خط ۸۰: ایجاد جدول [INDEMNITES]،
- خط ۹۱: ایجاد کلید خارجی برای جدول [EMPLOYES].
در NetBeans، میتوانید جداول موجود در اتصال ایجادشده در بالا را مشاهده کنید:
![]() |
جدولهای ایجاد شده هم به پیادهسازی لایه JPA و هم به SGBD مورد استفاده بستگی دارند. بنابراین، یک پیادهسازی JPA / EclipseLink با استفاده از همان پایگاه داده ممکن است جداول متفاوتی تولید کند. این چیزی است که اکنون بررسی خواهیم کرد.
5.6.2. لایه JPA / EclipseLink
ما قصد داریم یک پروژه جدید Maven را در محیط زیر بسازیم:
![]() |
ما رویهٔ تشریحشده در پاراگراف قبلی را دنبال خواهیم کرد:
- یک پایگاه داده با نام MySQL [dbpam_eclipselink] ایجاد کنید. برای ایجاد آن از اسکریپت [dbpam_eclipselink.sql] استفاده خواهیم کرد،
- فایل پروژه [persistence.xml] را ایجاد کنید. پیادهسازی JPA 2.0 EclipseLink را در نظر بگیرید،
- وابستگی به درایور JDBC از MySQL را به وابستگیهای تولید شده اضافه کنید،
- اشیاء JPA و برنامه کنسول را اضافه کنید،
- تستها را اجرا کنید.
فایل [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 حذف و سپس دوباره ایجاد خواهند شد.
خروجی کنسول به شرح زیر است:
- خطوط ۲۶–۳۰: اتصال به پایگاه داده 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. وظیفه
طبق همان رویهٔ قبلی،
- یک پروژه [mv-pam-jpa-hibernate-oracle] را با استفاده از پیادهسازی Hibernate JPA و پیادهسازی Oracle SGBD ایجاد و آزمایش کنید،
- یک پروژه [mv-pam-jpa-hibernate-mssql] را با استفاده از پیادهسازی Hibernate JPA و سرور SGBD SQL ایجاد و آزمایش کنید،
- یک پروژه [mv-pam-jpa-eclipselink-oracle] را با استفاده از پیادهسازی JPA EclipseLink و یک سرور Oracle SGBD ایجاد و آزمایش کنید،
- ایجاد و آزمایش یک پروژه [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 ارسالی به پایگاه داده را ثبت کند، حذف کردهایم.
اجرای پروژه دو پیام خروجی زیر را در کنسول تولید میکند:
- خط ۱: استثنایی که هنگام تلاش برای بازیابی امتیاز مفقود در حالی که جلسه بسته بود، رخ داد. میتوانیم ببینیم که امتیاز به دلیل حالت LAZY بازیابی نشده بود،
- خط ۲: کارمند با امتیاز بازیابیشده از طریق پرسوجویی که از حالت LAZY عبور کرده است.
5.6.5. وظیفه
با دنبال کردن روندی مشابه آنچه پیشتر توضیح داده شد، پروژهای به نام [mv-pam-pa-eclipselink-lazy] ایجاد کنید که نشان دهد EclipseLink در مقایسه با LAZY چگونه رفتار میکند.
نتایج زیر به دست میآیند:
در حالت 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] فراهم کند؟ یک رویکرد اولیه برای تعریف رابطهای لایههای مختلف، بررسی موارد استفادهٔ گوناگون برنامه است. در اینجا دو مورد داریم که بسته به رابط کاربری انتخابی متفاوت است: کنسول یا فرم گرافیکی.
بیایید بررسی کنیم که چگونه از برنامه کنسولی استفاده میشود:
برنامه سه مورد اطلاعات را از کاربر دریافت میکند (به خط ۱ بالا مراجعه کنید)
- شماره بیمه ملی پرستار کودک
- تعداد ساعات کاری در طول ماه
- تعداد روزهای کاری در ماه
با استفاده از این اطلاعات و سایر دادههای ذخیرهشده در فایلهای پیکربندی، برنامه جزئیات زیر را نمایش میدهد:
- خطوط ۴–۶: مقادیر واردشده
- خطوط ۸–۱۰: جزئیات مربوط به کارمندی که شماره بیمه ملی او ارائه شده است
- خطوط ۱۲–۱۴: نرخهای مربوط به انواع حق بیمههای تأمین اجتماعی
- ردههای ۱۶–۱۷: مزایای مختلف پرداختشده به پرستار کودک
- خطوط ۱۹–۲۴: جزئیات فیش حقوقی پرستار کودک
مقدار مشخصی از اطلاعات باید از لایه [metier] به لایه [ui] ارائه شود:
- اطلاعات مربوط به یک پرستار کودک که با شماره تأمین اجتماعی او شناسایی شده است. این اطلاعات در جدول [EMPLOYES] یافت میشود. این امکان را فراهم میکند که خطوط 6–8 نمایش داده شوند.
- مقادیر نرخهای مختلف مشارکت تأمین اجتماعی که از حقوق ناخالص کسر میشوند. این اطلاعات در جدول [COTISATIONS] یافت میشود. این امکان را فراهم میکند که خطوط ۱۰–۱۲ نمایش داده شوند.
- مبالغ مزایای مختلف مربوط به نقش پرستار کودک. این اطلاعات در جدول [INDEMNITES] یافت میشود. این امکان را فراهم میکند که خطوط 14–15 نمایش داده شوند.
- اجزای حقوق نمایش داده شده در سطرهای ۱۸–۲۲.
بر اساس این، میتوانیم یک ارسال اولیه را از رابط [IMetier]، که توسط لایه [metier] ارائه میشود، به لایه [ui] تعیین کنیم:
- خط ۱: عناصر لایه [metier] در بسته [metier] قرار میگیرند
- خط ۵: متد [ calculerFeuilleSalaire ] سه اطلاعات دریافتشده از لایه [ui] را بهعنوان پارامتر میپذیرد و یک شیء از نوع [FeuilleSalaire] را بازمیگرداند که حاوی اطلاعاتی است که لایه [ui] در کنسول نمایش خواهد داد. کلاس [FeuilleSalaire] میتواند به صورت زیر باشد:
- خط ۹: کارمند مشمول فیش حقوقی – اطلاعات شماره ۱ نمایشدادهشده توسط لایه [ui]
- خط ۱۰: نرخهای مختلف مشارکت – اطلاعات شماره ۲ نمایش داده شده توسط لایه [ui]
- خط ۱۱: مزایای مختلف مرتبط با شاخص کارمند – اطلاعات شماره ۳ نمایش داده شده توسط لایه [ui]
- خط ۱۲: اجزای حقوق آنها – اطلاعات شماره ۴ نمایش داده شده توسط لایه [ui]
مورد استفاده دوم برای لایه [métier] در رابط کاربری گرافیکی ظاهر میشود:
![]() |
همانطور که در بالا نشان داده شده است، لیست کشویی [1, 2] همه کارمندان را نمایش میدهد. این لیست باید از لایه [métier] درخواست شود. رابط کاربری برای این لایه سپس به شرح زیر تغییر میکند:
- خط [10]: متدی که به لایه [ui] امکان میدهد تا لیست تمام کارمندان را از لایه [métier] درخواست کند.
لایه [metier] تنها میتواند با پرسوجو از لایه [DAO]، فیلدهای [Employe, Cotisation, Indemnite] از شیء [FeuilleSalaire] مذکور را که در جداول پایگاه داده ذخیره شدهاند، مقداردهی اولیه کند. همین امر در مورد بازیابی فهرست همه کارمندان نیز صدق میکند. میتوان یک رابط واحد، [DAO]، برای مدیریت دسترسی به سه موجودیت [Employe, Cotisation, Indemnite] ایجاد کرد. با این حال، در اینجا تصمیم گرفتهایم برای هر موجودیت یک رابط، [DAO]، ایجاد کنیم.
رابط [DAO] برای دسترسی به انتیتهای [Cotisation] در جدول [COTISATIONS] به شرح زیر خواهد بود:
- در خط ۶، رابط [ICotisationDao] دسترسی به موجودیت [Cotisation] و در نتیجه به جدول [COTISATIONS] در پایگاه داده را مدیریت میکند. اپلیکیشن ما تنها به متد [findAll] از خط ۱۶ نیاز دارد که به ما امکان میدهد کل محتویات جدول [COTISATIONS] را بازیابی کنیم. در اینجا، ما میخواستیم یک مورد کلیتر را در نظر بگیریم که در آن تمام عملیات CRUD (ایجاد، خواندن، بهروزرسانی، حذف) بر روی این انتیت انجام میشود.
- خط ۸: متد [create] یک موجودیت جدید [Cotisation] ایجاد میکند
- خط ۱۰: متد [edit] یک موجودیت موجود [Cotisation] را تغییر میدهد
- خط ۱۲: متد [destroy] یک موجودیت موجود [Cotisation] را حذف میکند
- خط ۱۴: متد [find] یک موجودیت موجود [Cotisation] را با استفاده از شناسهی id آن بازیابی میکند
- خط ۱۶: متد [findAll] فهرستی از تمام موجودیتهای [Cotisation] موجود را بازمیگرداند
بیایید نگاهی دقیقتر به امضای متد [create] بیندازیم:
متد create دارای پارامتر cotisation از نوع Cotisation است. پارامتر cotisation باید پایدارسازی شود، c.a.d. که در جدول [COTISATIONS] ذخیره میشود. پیش از این پایداری، پارامتر cotisation دارای شناسه id بدون مقدار است. پس از پایداری، فیلد id دارای مقداری است که کلید اصلی رکورد افزودهشده به جدول [COTISATIONS] میباشد. بنابراین، پارامتر cotisation یک پارامتر ورودی/خروجی برای متد create است. به نظر نمیرسد که لازم باشد متد create نیز پارامتر cotisation را به عنوان نتیجه بازگرداند. از آنجایی که متد فراخوانیکننده یک مرجع به شی [Cotisation cotisation] در اختیار دارد، اگر این شی تغییر یابد، متد فراخوانیکننده به شی تغییر یافته دسترسی خواهد داشت زیرا مرجع آن را در اختیار دارد. بنابراین میتواند مقداری را که متد create به فیلد id از شیء [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] تغییر دهیم:
- با این امضای جدید، متد 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]:
به این معنی است که نیازی نیست در مورد معماری واقعی موجود هیچ فرضیهای مطرح کنیم. استفاده از امضاهایی که صرفنظر از معماری انتخابشده—چه محلی و چه راه دور—کار میکنند، به این معناست که اگر متد فراخوانیشده برخی از پارامترهای خود را تغییر دهد:
- اینها نیز باید بخشی از نتیجه متد فراخوانیشده را تشکیل دهند.
- متد فراخوانیکننده باید از نتیجه متد فراخوانیشونده استفاده کند، نه از ارجاع به پارامترهای تغییر یافتهای که به آن ارسال کرده است.
این امر به ما امکان میدهد تا بدون هیچگونه تغییر کدی، از معماری locale به معماری distante منتقل شویم. اکنون بیایید رابط [ICotisationDao] را از این منظر مجدداً بررسی کنیم:
- خط ۸: مورد متد 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] به شرح زیر خواهد بود:
- در خط ۶، رابط [IIndemniteDao] دسترسی به موجودیت [Indemnite] و در نتیجه به جدول [INDEMNITES] در پایگاه داده را مدیریت میکند. برنامهٔ ما تنها به متد [findAll] در خط ۱۶ نیاز دارد که کل محتویات جدول [INDEMNITES] را بازیابی میکند. در اینجا، ما میخواستیم یک مورد کلیتر را در نظر بگیریم که در آن تمام عملیات CRUD (ایجاد، خواندن، بهروزرسانی، حذف) روی این انتیت انجام میشود.
- خط ۸: متد [create] یک موجودیت جدید [Indemnite] ایجاد میکند
- خط ۱۰: متد [edit] یک موجودیت موجود [Indemnite] را تغییر میدهد
- خط ۱۲: متد [destroy] یک موجودیت موجود [Indemnite] را حذف میکند
- خط ۱۴: متد [find] یک موجودیت موجود [Indemnite] را از طریق شناسهی آن id بازیابی میکند
- خط ۱۶: متد [findAll] فهرستی از تمام موجودیتهای موجود [Indemnite] را بازمیگرداند
رابط [DAO] برای دسترسی به اشیاء [Employe] در جدول [EMPLOYES] به شرح زیر خواهد بود:
- در خط ۶، رابط [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 قرار داده خواهد شد:
![]() |
کد آن به شرح زیر است:
- خط ۴: کلاس [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] به شرح زیر است:
- متد [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] به شرح زیر است:
جدولهای [EMPLOYES, INDEMNITES, COTISATIONS] پر شدهاند. این موضوع را میتوان با اتصال به پایگاه داده [dbpam_hibernate] از طریق NetBeans تأیید کرد.
![]() |
- در [1]، در برگه [services]، دادههای جدول [employes] از اتصال [dbpam_hibernate] [2] نمایش داده میشود،
- در [3]، نتیجه.
5.9.6. JUnitDao
اکنون به کلاس دوم تستها، [JUnitDao]، میپردازیم:
![]() |
ساختار کلاس به شرح زیر خواهد بود:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
در کلاس تست قبلی، پایگاه داده قبل از هر تست پاک میشود.
سؤال: متدهای زیر را بنویسید:
۱ – test02: این بر اساس test01 خواهد بود
۲ - test03: یک کارمند فیلدی از نوع Indemnite دارد. بنابراین باید یک انتیت Indemnite و یک انتیت Employe ایجاد کنید.
3 - test04.
با دنبال کردن همان رویه برای کلاس تست [JUnitInitDB]، نتایج زیر به دست میآیند:
![]() |
- در [1]، کلاس تست را اجرا کنید
- برای [2]، نتایج آزمون در پنجره [Test Results] ظاهر میشوند
بیایید یک خطا را فعال کنیم تا ببینیم چگونه در صفحه نتایج گزارش میشود:
خط ۱۳: عبارت assertion باعث خطا میشود، زیرا مقدار Csgrds برابر ۳.۴۹ است (خط ۸). اجرای کلاس تست نتایج زیر را تولید میکند:
![]() |
- صفحه نتایج [1] اکنون نشان میدهد که برخی از تستها شکست خوردهاند.
- در [2]، خلاصهای از استثنایی که باعث شکست تست شد، نمایش داده میشود. این شامل شماره خط در کد جاوا است که استثنا در آن رخ داده است.
5.10. لایه [metier] از برنامه [PAM]
اکنون که لایه [DAO] نوشته شده است، به بررسی لایه کسبوکار [2] میپردازیم:
![]() |
5.10.1. رابط جاوا [IMetier]
این در بخش 5.7 توضیح داده شده است. ما آن را در زیر خلاصه میکنیم:
پیادهسازی لایه [metier] در پکیجی به نام [metier] انجام خواهد شد:
![]() |
پکیج [metier] شامل، علاوه بر رابط [IMetier] و پیادهسازی آن [Metier]، دو کلاس دیگر: [FeuilleSalaire] و [ElementsSalaire]. کلاس [FeuilleSalaire] بهطور مختصر در بخش 5.7 مورد بحث قرار گرفت. اکنون به آن بازمیگردیم.
5.10.2. کلاس [FeuilleSalaire]
متد [calculerFeuilleSalaire] از رابط [IMetier] یک شیء از نوع [FeuilleSalaire] را بازمیگرداند که نمایانگر عناصر مختلف یک فیش حقوقی است. تعریف آن به شرح زیر است:
- خط ۷: این کلاس رابط Serializable را پیادهسازی میکند زیرا نمونههای آن میتوانند از طریق شبکه مبادله شوند.
- خط ۹: کارمندی که فیش حقوقی او صادر میشود
- خط ۱۰: نرخهای مختلف مشارکت
- خط ۱۱: مزایای مختلف مرتبط با جدول حقوق کارمند
- خط ۱۲: اجزای حقوق آنها
- خطوط ۱۴–۲۲: دو سازنده کلاس
- خطوط ۲۵–۲۷: متد [toString] که یک شیء خاص [FeuilleSalaire] را شناسایی میکند
- خطوط ۲۹ به بعد: دسترسیدهندههای عمومی به فیلدهای خصوصی کلاس
کلاس [ElementsSalaire]، که در خط ۱۱ از کلاس [FeuilleSalaire] بالا به آن ارجاع شده است، عناصری را که یک فیش حقوقی را تشکیل میدهند، گرد هم میآورد. تعریف آن به شرح زیر است:
- خط ۳: کلاس این رابط را پیادهسازی میکند زیرا جزئی از کلاس FeuilleSalaire است که باید قابل سریالسازی باشد.
- خط ۶: حقوق پایه
- خط ۷: حق بیمههای تأمین اجتماعی پرداختشده بر این حقوق پایه
- خط ۸: کمکهزینه روزانه نگهداری کودک
- خط ۹: کمکهزینه روزانه غذا برای کودک
- خط ۱۰: حقوق خالص قابل پرداخت به پرستار کودک
- خطوط ۱۲–۲۴: سازندههای کلاس
- ردههای ۲۷–۳۱: متد [toString] که یک شیء خاص [ElementsSalaire] را شناسایی میکند
- خطوط ۳۴ به بعد: دسترسیهای عمومی به فیلدهای خصوصی کلاس
5.10.3. کلاس پیادهسازی [Metier] برای لایه [metier]
کلاس پیادهسازی [Metier] از لایه [metier] میتواند به صورت زیر باشد:
- خط ۵: تگ @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] میتواند به صورت زیر باشد:
در این کلاس هیچ گزاره Assert.assertCondition وجود ندارد. ما صرفاً در تلاشیم چند حقوق را محاسبه کنیم تا سپس بتوانیم آنها را بهصورت دستی بررسی کنیم. خروجی صفحه نمایش حاصل از اجرای کلاس قبلی به شرح زیر است:
- خط ۴: فیش حقوقی ژوستین لاورتی
- خط ۵: فیش حقوقی ماری جووینال
- خط ۶: استثنایی که به دلیل وجود نداشتن کارمند با شناسه SS 'xx' رخ داده است.
سؤال: خط 17 از [JUnitMetier_1] از Spring bean با نام metier استفاده میکند. تعریف این bean را در فایل [spring-config-metier-dao.xml] ارائه دهید.
کلاس [JUnitMetier_2] میتواند به صورت زیر باشد:
کلاس [JUnitMetier_2] کپیای از کلاس [JUnitMetier_1] است، با این تفاوت که این بار، assertionها به متد test01 اضافه شدهاند.
وظیفه: متد test01 را بنویسید.
وقتی کلاس [JUnitMetier_2] اجرا میشود، در صورت موفقیتآمیز بودن، نتایج زیر به دست میآیند:

5.11. لایه [ui] از برنامه [PAM] – نسخه کنسول
اکنون که لایه [metier] نوشته شده است، هنوز باید لایههای [ui] و [1] را بنویسیم:
![]() |
ما دو پیادهسازی متفاوت از لایه [ui] ایجاد خواهیم کرد: یک نسخه console و یک نسخه گرافیکی swing:
![]() |
5.11.1. کلاس [ui.console.Main]
ابتدا اپلیکیشن کنسول پیادهسازیشده توسط کلاس [ui.console.Main] فوق را بررسی میکنیم. عملکرد آن در بخش 5.3 شرح داده شده است. اسکلت کلاس [Main] ممکن است به شکل زیر باشد:
سؤال: کد بالا را تکمیل کنید.
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 Forms]، c.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] تولید میشود:
کد جاوا که متد قبلی را با کلیک روی دکمه [JButtonSalaire] مرتبط میکند نیز تولید میشود:
خطوط ۲–۵ مشخص میکنند که کلیک (نوع رویداد ActionPerformed) روی دکمه [jButtonSalaire] (خط ۲) باید توسط متد [jButtonSalaireActionPerformed] (خط ۴) مدیریت شود.
ما همچنین رویداد [caretUpdate] (حرکت نشانگر ورودی) را روی فیلد ورودی [jTextFieldHT] مدیریت خواهیم کرد. برای ایجاد دستپرداز این رویداد، همانند قبل پیش میرویم:
![]() |
پردازشگر رویداد [caretUpdate] روی فیلد ورودی [jTextFieldHT] تولید شده است:
کد جاوا که متد قبلی را با رویداد [caretUpdate] در فیلد ورودی [jTextFieldHT] مرتبط میسازد نیز تولید میشود:
خطوط ۱–۴ نشان میدهند که رویداد [caretUpdate] (خط ۲) روی دکمه [jTextFieldHT] (خط ۱) باید توسط متد [ jTextFieldHTCaretUpdate] (خط ۳) پردازش شود.
5.12.4. ابتکار رابط کاربری گرافیکی
بیایید به معماری برنامهٔ خود بازگردیم:
![]() |
لایه [ui] به مرجعی به لایه [metier] نیاز دارد. بیایید به یاد بیاوریم که این مرجع چگونه در برنامه console به دست آمد:
روش در برنامه گرافیکی نیز یکسان است. هنگامی که برنامه راهاندازی میشود، مرجع [IMetier metier] از خط ۳ بالا نیز باید راهاندازی شود. کد تولید شده برای رابط گرافیکی در حال حاضر به شرح زیر است:
- خطوط ۲۹–۳۵: متد استاتیک [main]، که برنامه را راهاندازی میکند
- خط ۳۲: یک نمونه از رابط کاربری گرافیکی [PamJFrame] ایجاد و قابل مشاهده میشود.
- خطوط ۷–۹: سازنده رابط کاربری گرافیکی.
- خط ۸: فراخوانی متد [initComponents] که در خط ۱۷ تعریف شده است. این متد از روی کاری که در حالت [Design] انجام شده، بهطور خودکار تولید شده است. نباید تغییر داده شود.
- خط ۲۱: متدی که حرکت نشانگر ورودی را در فیلد [jTextFieldHT] مدیریت میکند
- خط ۲۵: متدی که کلیک روی دکمه [jButtonSalaire] را مدیریت میکند
برای افزودن مقداردهیهای اولیهٔ خود به کد بالا، میتوانیم به شرح زیر عمل کنیم:
- خط ۴: ما یک متد اختصاصی را برای انجام инициалиزاسیونهای خود فراخوانی میکنیم. اینها توسط کد در خطوط ۱۰–۴۲ تعریف شدهاند.
سؤال: با استفاده از نظرات بهعنوان راهنما، کد رویه [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 را راهاندازی کنید.
5.13. پیادهسازی لایه JPA با EclipseLink
ما به معماری زیر علاقهمند هستیم، که در آن لایه JPA اکنون توسط EclipseLink پیادهسازی شده است:
![]() |
5.13.1. پروژه NetBeans
پروژه جدید NetBeans با کلون کردن پروژه قبلی ایجاد میشود:
![]() |
- به [1]: پس از کلیک راست روی پروژه Hibernate، Copy را انتخاب کنید
- با استفاده از دکمه [2]، پوشه والد برای پروژه جدید را انتخاب کنید. نام پوشه در [3] ظاهر میشود.
- در [4]، برای پروژه جدید یک نام انتخاب کنید
- در [5]، نام پوشه پروژه
![]() |
- در [1]، پروژه جدید ایجاد شده است. نام آن با نام اصلی یکسان است،
- در [2] و [3]، نام آن به [mv-pam-spring-eclipselink] تغییر یافت.
برای تطبیق پروژه با لایه جدید JPA / EclipseLink، باید در دو مکان اصلاح شود:
- در [4]، فایلهای پیکربندی Spring باید اصلاح شوند. این فایلها در حال حاضر پیکربندی لایه JPA را در خود دارند.
- در [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] داریم:
خطوط ۱۹ تا ۳۶ لایه JPA را پیکربندی میکنند. پیادهسازی مورد استفاده برای JPA، Hibernate (خط ۲۲) است. علاوه بر این، پایگاه داده هدف [dbpam_hibernate] (خط ۴۱) است.
برای تغییر به پیادهسازی JPA / EclipseLink، خطوط ۱۹–۳۵ بالا با خطوط زیر جایگزین میشوند:
- خط ۵: پیادهسازی مورد استفاده JPA، EclipseLink است
- خط ۹: خاصیت databasePlatform هدف SGBD را تعیین میکند، در این مورد MySQL
- خط ۱۱: برای ایجاد جداول پایگاه داده هنگام نمونهسازی لایه JPA. در اینجا، این خاصیت با علامت # غیرفعال شده است.
- خط ۷: برای نمایش دستورات SQL صادر شده توسط لایه JPA در کنسول. در اینجا، این خاصیت کامنت شده است.
علاوه بر این، پایگاه داده هدف به [dbpam_eclipselink] تبدیل میشود (خط ۴ زیر):
5.13.2. پیادهسازی تستها
قبل از آزمایش کل برنامه، توصیه میشود بررسی کنید که آیا تستهای JUnit با پیادهسازی جدید JPA با موفقیت اجرا میشوند یا خیر. قبل از انجام این کار، با حذف جداول از پایگاه داده شروع میکنیم. برای این کار، در زبانه [Runtime] در NetBeans، در صورت لزوم، یک اتصال به پایگاه داده dbpam_eclipselink / MySQL5 ایجاد خواهیم کرد. پس از اتصال به پایگاه داده dbpam_eclipselink / MySQL5، میتوانید طبق مراحل زیر اقدام به حذف جداول کنید:
- [1]: قبل از حذف
- [2]: پس از حذف
![]() |
پس از انجام این کار، اولین تست را میتوان روی لایه [DAO] اجرا کرد: InitDB که پایگاه داده را پر میکند. برای اطمینان از اینکه جداول حذفشده قبلی توسط برنامه دوباره ایجاد میشوند، باید اطمینان حاصل کنید که در پیکربندی Spring برای JPA / EclipseLink، خط:
وجود داشته باشد و کامنت نشده باشد.
ما پروژه را ساخت میکنیم و سپس دستور [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 که برنامه را اجرا میکند، باید با یک پارامتر خاص راهاندازی شود: یک ایجنت جاوا. قالب این پارامتر به شرح زیر است:
[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;
...
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 به شرح زیر است:
تست با موفقیت انجام میشود (c.a.d)، اعتبارسنجیها تأیید میشوند و هیچ استثنایی از متد تست پرتاب نمیشود.
سؤال: توضیح دهید چه اتفاقی افتاد.
نتایج بهدستآمده با لایه JPA / EclipseLink به شرح زیر است:
همانند مورد قبلی با 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.





















































































