Skip to content

18. مطالعه موردی: Struts 2 / Tiles / Spring / Hibernate / MySQL

ما مقدمه‌مان را در مورد Struts با یک مطالعه موردی به پایان می‌رسانیم. برای واقعی‌تر کردن موضوع، مثالی که بررسی خواهیم کرد به طور قابل توجهی پیچیده‌تر از موارد قبلی خواهد بود. برای مبتدیان، احتمالاً بهتر است که قبل از پرداختن به این مطالعه موردی، درک خود را از اصول اولیه Struts 2 از طریق پروژه‌های شخصی تثبیت کنند.

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

مجموعه لایه‌های [metier]، [dao] و [jpa/hibernate] به صورت یک آرشیو JAR در اختیار ما قرار خواهد گرفت که عملکرد آن را به تفصیل توضیح خواهیم داد. ادغام لایه‌ها توسط Spring انجام خواهد شد. لایه [web] با استفاده از Struts 2 پیاده‌سازی خواهد شد.

18.1. مسئله

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

Image

این مطالعه موردی در سند ارائه شده است:

مقدمه‌ای بر جاوا 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]:

Image

  • در [1]، پایگاه داده سه جدول دارد:
    • [employes]: جدولی که کارکنان یک مهدکودک را ثبت می‌کند
    • [cotisations]: جدولی که نرخ‌های مشارکت تأمین اجتماعی را ثبت می‌کند
    • [indemnites]: جدولی که اطلاعات مورد استفاده برای محاسبه حقوق کارمندان را ذخیره می‌کند

جدول [employes]

  • [2] جدول کارمندان است و [3] معنای فیلدهای آن را توضیح می‌دهد

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

 

جدول [cotisations]

  • در [4]، جدول مشارکت‌ها، و در [5]، معانی فیلدهای آن

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

 

جدول [indemnites]

  • در [6]، جدول مزایا، و در [7]، معانی فیلدهای آن

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

 

صادرات ساختار پایگاه داده به یک فایل SQL نتیجه زیر را تولید می‌کند:

#
#ساختار جدول `cotisations`: 
#

CREATE TABLE `cotisations` (
  `ID` bigint(20) NOT NULL auto_increment,
  `SECU` double NOT NULL,
  `RETRAITE` double NOT NULL,
  `CSGD` double NOT NULL,
  `CSGRDS` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

#
#ساختار جدول `indemnites`: 
#

CREATE TABLE `indemnites` (
  `ID` bigint(20) NOT NULL auto_increment,
  `ENTRETIEN_JOUR` double NOT NULL,
  `REPAS_JOUR` double NOT NULL,
  `INDICE` int(11) NOT NULL,
  `INDEMNITES_CP` double NOT NULL,
  `BASE_HEURE` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `INDICE` (`INDICE`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

#
#ساختار جدول `کارمندان`: 
#

CREATE TABLE `employes` (
  `ID` bigint(20) NOT NULL auto_increment,
  `PRENOM` varchar(20) NOT NULL,
  `SS` varchar(15) NOT NULL,
  `ADRESSE` varchar(50) NOT NULL,
  `CP` varchar(5) NOT NULL,
  `VILLE` varchar(30) NOT NULL,
  `NOM` varchar(30) NOT NULL,
  `VERSION` int(11) NOT NULL,
  `INDEMNITE_ID` bigint(20) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `SS` (`SS`),
  KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`),
  CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;

18.3. اشیاء JPA

در معماری زیر

لایه [Jpa] به‌عنوان پلی میان اشیایی که توسط لایه [dao] مدیریت می‌شوند و سطرهای جدول‌های پایگاه داده که توسط درایور JDBC پردازش می‌شوند، عمل می‌کند. سطرهای خوانده‌شده از پایگاه داده به اشیایی تبدیل می‌شوند که به‌عنوان انتیت‌های JPA شناخته می‌شوند. برعکس، در عملیات نوشتن، انتیت‌های JPA دوباره به سطرهای جدول تبدیل می‌شوند. این انتیت‌ها توسط همه لایه‌ها، به‌ویژه لایه وب، پردازش می‌شوند. بنابراین باید با آن‌ها آشنا باشیم:

انتیتی [Employe] یک سطر در جدول [Employes] را نشان می‌دهد.

ID
کلید اصلی از نوع افزایش خودکار
VERSION
شماره نسخه رکورد
PRENOM
نام کوچک کارمند
NOM
نام خانوادگی او
ADRESSE
آدرس او
CP
کد پستی او
VILLE
شهر او
INDEMNITE_ID
کلید خارجی روی INDEMNITES(ID)

ذات [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] است:

ID
کلید اصلی از نوع افزایش خودکار
VERSION
شماره نسخه رکورد
BASE_HEURE
هزینه به یورو برای یک ساعت آماده‌به‌کاری
ENTRETIEN_JOUR
اضافه‌کاری روزانه به یورو برای آماده‌به‌کار بودن
REPAS_JOUR
مبلغ روزانه یارانه غذا به یورو در حین انجام وظیفه
INDEMNITES_CP
اضافه‌پرداخت‌های تعطیلات با حقوق. این یک درصد است
برای اعمال بر حقوق پایه.

واحد [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] است:

ID
کلید اصلی از نوع افزایش خودکار
VERSION
شماره نسخه رکورد
SECU
نرخ مشارکت تأمین اجتماعی (درصد)
تأمین اجتماعی
RETRAITE
نرخ مشارکت بازنشستگی
CSGD
نرخ مشارکت اجتماعی
سهم اجتماعی عمومی قابل کسر
CSGRDS
نرخ مشارکت در حق بیمه تأمین اجتماعی عمومی
و سهم در بازپرداخت
بدهی اجتماعی

واحد [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. روش محاسبه حقوق پرستار کودک

اپلیکیشن وبی که قصد داریم بنویسیم به ما امکان می‌دهد حقوق یک کارمند را بر اساس سه مورد اطلاعات محاسبه کنیم:

  • شاخص کارمند
  • تعداد روزهای کاری
  • تعداد ساعات کاری

در اینجا یک تصویر از صفحه محاسبه حقوق آورده شده است:

Image

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

عوامل زیر در نظر گرفته می‌شوند:
[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

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 مراجعه کنید که در URL [http://tahe.developpez.com/java/javaee] موجود است.