18. مطالعه موردی: Struts 2 / Tiles / Spring / Hibernate / MySQL
ما مقدمهمان را در مورد Struts با یک مطالعه موردی به پایان میرسانیم. برای واقعیتر کردن موضوع، مثالی که بررسی خواهیم کرد به طور قابل توجهی پیچیدهتر از موارد قبلی خواهد بود. برای مبتدیان، احتمالاً بهتر است که قبل از پرداختن به این مطالعه موردی، درک خود را از اصول اولیه Struts 2 از طریق پروژههای شخصی تثبیت کنند.
این برنامه از معماری لایهبندیشده استفاده خواهد کرد:
![]() |
مجموعه لایههای [metier]، [dao] و [jpa/hibernate] به صورت یک آرشیو JAR در اختیار ما قرار خواهد گرفت که عملکرد آن را به تفصیل توضیح خواهیم داد. ادغام لایهها توسط Spring انجام خواهد شد. لایه [web] با استفاده از Struts 2 پیادهسازی خواهد شد.
![]() |
18.1. مسئله
ما پیشنهاد میکنیم یک اپلیکیشن وب برای تولید فیشهای حقوقی برای مراقبان کودک که توسط «مرکز سالهای اولیه» یک نهاد محلی استخدام شدهاند، توسعه دهیم.

این مطالعه موردی در سند ارائه شده است:
مقدمهای بر جاوا EE 5 موجود در URL [http://tahe.developpez.com/java/javaee]
در این سند، مطالعه موردی با استفاده از معماری چندلایه زیر پیادهسازی شده است:
![]() |
لایه [web] با استفاده از چارچوب JSF (Java Server Faces) پیادهسازی شده است. ما همین معماری را با پیادهسازی لایه [web] با Struts 2 به کار خواهیم گرفت. برای نشان دادن مزایای معماری لایهای، ما از آرشیو JAR برای لایههای [metier, dao, jpa] از نسخه JSF استفاده خواهیم کرد و آن را به یک لایه [web / struts2] متصل میکنیم:
![]() |
ما عناصر زیر از لایههای [metier, dao, jpa] را ارائه خواهیم داد:
- لایه [web] با لایه [métier] رابطه برقرار میکند. ما این رابط را ارائه خواهیم داد.
- لایه [jpa] به یک پایگاه داده دسترسی دارد. آن را ارائه خواهیم کرد.
- لایه [jpa] سطرها را از جداول پایگاه داده به اِنتیتیهای JPA تبدیل میکند که در تمام لایههای برنامه استفاده میشوند. ما این انتیتیها را ارائه خواهیم داد.
- لایههای [metier, dao, jpa] توسط Spring ایجاد میشوند. ما فایل پیکربندیای را که این ایجاد و یکپارچهسازی را انجام میدهد، ارائه خواهیم کرد.
18.2. پایگاه داده
ما از پایگاه داده زیر استفاده خواهیم کرد: MySQL [dbpam_hibernate]:

- در [1]، پایگاه داده سه جدول دارد:
- [employes]: جدولی که کارکنان یک مهدکودک را ثبت میکند
- [cotisations]: جدولی که نرخهای مشارکت تأمین اجتماعی را ثبت میکند
- [indemnites]: جدولی که اطلاعات مورد استفاده برای محاسبه حقوق کارمندان را ذخیره میکند
جدول [employes]
![]() |
- [2] جدول کارمندان است و [3] معنای فیلدهای آن را توضیح میدهد
محتویات جدول ممکن است به شرح زیر باشد:
جدول [cotisations]
![]() |
- در [4]، جدول مشارکتها، و در [5]، معانی فیلدهای آن
محتویات جدول ممکن است به شرح زیر باشد:
جدول [indemnites]
![]() |
- در [6]، جدول مزایا، و در [7]، معانی فیلدهای آن
محتویات جدول ممکن است به شرح زیر باشد:
صادرات ساختار پایگاه داده به یک فایل SQL نتیجه زیر را تولید میکند:
18.3. اشیاء JPA
در معماری زیر
![]() |
لایه [Jpa] بهعنوان پلی میان اشیایی که توسط لایه [dao] مدیریت میشوند و سطرهای جدولهای پایگاه داده که توسط درایور JDBC پردازش میشوند، عمل میکند. سطرهای خواندهشده از پایگاه داده به اشیایی تبدیل میشوند که بهعنوان انتیتهای JPA شناخته میشوند. برعکس، در عملیات نوشتن، انتیتهای JPA دوباره به سطرهای جدول تبدیل میشوند. این انتیتها توسط همه لایهها، بهویژه لایه وب، پردازش میشوند. بنابراین باید با آنها آشنا باشیم:
انتیتی [Employe] یک سطر در جدول [Employes] را نشان میدهد.
![]() |
|
ذات [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
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
public Employe() {
}
public Employe(String SS, String nom, String prenom, String adresse, String ville, String codePostal, Indemnite indemnite){
setSS(SS);
setNom(nom);
setPrenom(prenom);
setAdresse(adresse);
setVille(ville);
setCodePostal(codePostal);
setIndemnite(indemnite);
}
@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()
+"]";
}
// گیرندهها و تنظیمکنندهها
...
}
ما @annotationهایی را که برای لایه [Jpa] در نظر گرفته شدهاند، نادیده میگیریم. فیلدهای مختلف کلاس نمایانگر ستونهای مختلف جدول [EMPLOYES] هستند. میدان 'indemnites' (خط ۲۹) نشاندهنده این واقعیت است که جدول [EMPLOYES] دارای یک کلید خارجی به جدول [INDEMNITES] است. هنگامی که یک کارمند ویرایش میشود، مزایای او نیز بهروزرسانی میشوند.
اِنتیتهی [Indemnite]، بیان شیءی یک سطر در جدول [INDEMNITES] است:
![]() |
|
واحد [Indemnite] به شرح زیر است:
package jpa;
...
@Entity
@Table(name="INDEMNITES")
public class Indemnite implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
@Column(name="VERSION",nullable=false)
private int version;
@Column(name="INDICE", nullable=false,unique=true)
private int indice;
@Column(name="BASE_HEURE",nullable=false)
private double baseHeure;
@Column(name="ENTRETIEN_JOUR",nullable=false)
private double entretienJour;
@Column(name="REPAS_JOUR",nullable=false)
private double repasJour;
@Column(name="INDEMNITES_CP",nullable=false)
private double indemnitesCP;
public Indemnite() {
}
public Indemnite(int indice, double baseHeure, double entretienJour, double repasJour, double indemnitesCP){
setIndice(indice);
setBaseHeure(baseHeure);
setEntretienJour(entretienJour);
setRepasJour(repasJour);
setIndemnitesCP(indemnitesCP);
}
@Override
public String toString() {
return "jpa.Indemnite[id=" + getId()
+ ",version="+getVersion()
+",indice="+getIndice()
+",base heure="+getBaseHeure()
+",entretien jour"+getEntretienJour()
+",repas jour="+getRepasJour()
+",indemnités CP="+getIndemnitesCP()
+ "]";
}
// گیرندهها و تنظیمکنندهها
....
}
فیلدهای مختلف در کلاس نمایانگر ستونهای مختلف در جدول [INDEMNITES] هستند.
پدیده [Cotisation] عبارت شیء برای یک سطر در جدول [COTISATIONS] است:
![]() |
|
واحد [Cotisation] به شرح زیر است:
package jpa;
...
@Entity
@Table(name="COTISATIONS")
public class Cotisation implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
@Column(name="VERSION",nullable=false)
private int version;
@Column(name="CSGRDS",nullable=false)
private double csgrds;
@Column(name="CSGD",nullable=false)
private double csgd;
@Column(name="SECU",nullable=false)
private double secu;
@Column(name="RETRAITE",nullable=false)
private double retraite;
public Cotisation() {
}
public Cotisation(double csgrds, double csgd, double secu, double retraite){
setCsgrds(csgrds);
setCsgd(csgd);
setSecu(secu);
setRetraite(retraite);
}
@Override
public String toString() {
return "jpa.Cotisation[id=" + getId() + ",version=" + getVersion()+",csgrds="+getCsgrds()+"" +
",csgd="+getCsgd()+",secu="+getSecu()+",retraite="+getRetraite()+"]";
}
// گیرنده و تنظیمکننده
...
}
میدانهای مختلف در کلاس با ستونهای مختلف در جدول [INDEMNITES] مطابقت دارند.
18.4. روش محاسبه حقوق پرستار کودک
اپلیکیشن وبی که قصد داریم بنویسیم به ما امکان میدهد حقوق یک کارمند را بر اساس سه مورد اطلاعات محاسبه کنیم:
- شاخص کارمند
- تعداد روزهای کاری
- تعداد ساعات کاری
در اینجا یک تصویر از صفحه محاسبه حقوق آورده شده است:

اکنون توضیح میدهیم که چگونه حقوق ماهانه یک پرستار کودک محاسبه میشود. این مطلب قصد ندارد روش مورد استفاده در عمل را منعکس کند. بهعنوان مثال، حقوق خانم ماری جووینال را در نظر میگیریم که در ماه حقوق ۱۵۰ ساعت در طول ۲۰ روز کار کرده است.
عوامل زیر در نظر گرفته میشوند: | ||
حقوق پایه برای دستیار مهدکودک توسط ... داده میشود : | ||
مبلغ معینی از حق بیمه تأمین اجتماعی باید از این کسر شود : | ||
مجموع حق بیمه تأمین اجتماعی: | ||
علاوه بر این، پرستار کودک مستحق است، برای هر روز کاری، به یک و فوقالعاده غذا علاوه بر آن، او مستحق دریافت است : | ||
در نهایت، حقوق خالص قابل پرداخت به پرستار کودک به شرح زیر است: |
18.5. رابط لایه [métier]
بیایید بار دیگر نگاهی به معماری برنامهای که در حال ساخت آن هستیم بیندازیم:
![]() |
لایه [web / struts 2] با رابط لایه [métier] ارتباط برقرار میکند. این به شرح زیر است:
package metier;
import java.util.List;
import jpa.Employe;
public interface IMetier {
// بازیابی فیش حقوقی
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// فهرست کارمندان
List<Employe> findAllEmployes();
}
- خط ۸: متدی که به ما امکان محاسبه حقوق کارمند را میدهد
- خط ۱۰: متدی که به ما امکان میدهد لیست کشویی کارمندان را پر کنیم
متد calculerFeuillesalaire یک نمونه از کلاس زیر را برمیگرداند: [FeuilleSalaire]:
package metier;
import java.io.Serializable;
import jpa.Cotisation;
import jpa.Employe;
public class FeuilleSalaire implements Serializable {
// fields خصوصی
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
@Override
public String toString() {
return "[" + employe + "," + cotisation + ","
+ elementsSalaire + "]";
}
// گیرندهها و تنظیمکنندهها
...
}
برگه حقوق شامل اطلاعات زیر است:
- خط ۱۰: اطلاعات مربوط به کارمندی که حقوق او در حال محاسبه است
- خط ۱۱: نرخهای مختلف مشارکت
- خط ۱۲: اجزای حقوق
کلاس [ElementsSalaire] به شرح زیر است:
package metier;
import java.io.Serializable;
public class ElementsSalaire implements Serializable{
// fields خصوصی
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);
setSalaireNet(salaireNet);
}
// toString
@Override
public String toString() {
return "[salaire base=" + salaireBase + ",cotisations sociales=" + cotisationsSociales + ",indemnités d'entretien="
+ indemnitesEntretien + ",indemnités de repas=" + indemnitesRepas + ",salaire net="
+ salaireNet + "]";
}
// گیرندهها و تنظیمکنندهها
...
}
- خطوط ۸–۱۲: اجزای حقوق
18.6. فایل پیکربندی Spring
ادغام لایههای [métier, dao, jpa] توسط فایل پیکربندی Spring زیر انجام میشود:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<!-- لایههای کاربردی -->
<!-- منطق کسبوکار -->
<bean id="metier" class="metier.Metier">
<property name="employeDao" ref="employeDao"/>
<property name="cotisationDao" ref="cotisationDao"/>
</bean>
<!-- DAO -->
<bean id="employeDao" class="dao.EmployeDao" />
<bean id="indemniteDao" class="dao.IndemniteDao" />
<bean id="cotisationDao" class="dao.CotisationDao" />
<!--پیکربندی 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="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
</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>
<!-- مدیر تراکنش -->
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- نقشهبرداری استثنا -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- پایداری -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>
ما تلاش نمیکنیم این پیکربندی را توضیح دهیم. این پیکربندی برای نمونهسازی و یکپارچهسازی لایههای [métier, dao, jpa] ضروری است. اپلیکیشن وب ما که به این لایهها متکی خواهد بود، بنابراین باید این پیکربندی را به کار گیرد. توجه داشته باشید که خطوط ۳۲ تا ۳۷، ویژگیهای JDBC پایگاه داده را پیکربندی میکنند. خوانندگانی که مایل به تغییر پایگاه داده هستند باید این خطوط را اصلاح کنند.
برای اطلاعات بیشتر در مورد این پیکربندی، لطفاً به سند «Introduction to Java EE 5» مراجعه کنید که در URL [http://tahe.developpez.com/java/javaee] موجود است.











