Skip to content

18. Vaka çalışması: Struts 2 / Tiles / Spring / Hibernate / MySQL

Struts öğrenimimizi bir vaka çalışmasıyla sonlandıracağız. Gerçekçi olması açısından, inceleyeceğimiz örnek daha önce incelenenlerden çok daha karmaşık olacaktır. Yeni başlayanlar için, bu vaka çalışmasına geçmeden önce kişisel uygulamalarla Struts 2'nin temellerini derinlemesine incelemek şüphesiz daha uygun olacaktır.

Uygulama, katmanlı bir mimari kullanacaktır:

[metier], [dao] ve [jpa/hibernate] katmanlarının tümü, işlevlerini ayrıntılı olarak ele alacağımız bir jar arşivi şeklinde bize sağlanacaktır. Katmanların entegrasyonu Spring tarafından sağlanacaktır. [web] katmanı Struts 2 ile uygulanacaktır.

18.1. Sorun

Bir belediyenin "Erken Çocukluk Merkezi"nde çalışan çocuk bakıcılarının maaş bordrolarını hazırlamaya olanak tanıyan bir web uygulaması yazmayı planlıyoruz.

Image

Bu vaka çalışması şu belgede sunulmaktadır:

Java'ya Giriş EE 5, URL [http://tahe.developpez.com/java/javaee] adresinde mevcuttur

Bu belgede, vaka çalışması aşağıdaki çok katmanlı mimari ile uygulanmıştır:

[web] katmanı, JSF (Java Server Faces) çerçeve yapısı kullanılarak uygulanmıştır. Struts 2 ile [web] katmanını uygulayarak aynı mimariyi kullanacağız. Katmanlı mimarilerin faydalarını göstermek için, JSF sürümüne ait [metier, dao, jpa] katmanlarının jar dosyasını kullanacak ve bunu bir [web / struts2] katmanına bağlayacağız:

[metier, dao, jpa] katmanlarının aşağıdaki öğelerini tanıtacağız:

  • [web] katmanı, [métier] katmanının arayüzüne yöneliktir. Bu arayüzü tanıtacağız.
  • [jpa] katmanı bir veritabanına erişir. Bunu tanıtacağız.
  • [jpa] katmanı, veritabanı tablolarındaki satırları, uygulamanın tüm katmanları tarafından işlenen JPA varlıklarına dönüştürür. Bunları tanıtacağız.
  • [metier, dao, jpa] katmanları Spring tarafından örneklenir. Bu örneklendirmeyi ve entegrasyonu gerçekleştiren yapılandırma dosyasını tanıtacağız.

18.2. Veritabanı

Aşağıdaki MySQL [dbpam_hibernate] veritabanını kullanacağız:

Image

  • [1]'te, veritabanında üç tablo bulunmaktadır:
    • [employes]: Bir kreşin çalışanlarını kaydeden tablo
    • [cotisations]: sosyal güvenlik prim oranlarını kaydeden bir tablo
    • [indemnites]: Çalışanların maaş hesaplamasına yönelik bilgileri kaydeden bir tablo

[employes] tablosu

  • [2], çalışan tablosu; [3] ise bu tablonun alanlarının açıklamalarıdır

Tablonun içeriği şu şekilde olabilir:

 

[cotisations] tablosu

  • [4]'te prim tablosu ve [5]'te bu tablonun alanlarının açıklamaları

Tablonun içeriği şu şekilde olabilir:

 

[indemnites] tablosu

  • [6]'te tazminat tablosu ve [7]'te bu tablonun alanlarının açıklamaları

Tablonun içeriği şu şekilde olabilir:

 

Veritabanı yapısının SQL dosyasına aktarılması sonucunda şu sonuç elde edilir:

#
# `cotisations` tablosunun yapısı: 
#

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` tablosunun yapısı: 
#

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;

#
# `employes` tablosunun yapısı: 
#

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 varlıkları

Aşağıdaki mimaride

[Jpa] katmanı, [dao] katmanı tarafından işlenen nesneler ile Jdbc sürücüsü tarafından işlenen veritabanı tablolarındaki satırlar arasında köprü görevi görür. Veritabanından okunan tablo satırları, JPA varlıkları olarak adlandırılan nesnelere dönüştürülür. Tersine, yazma işleminde JPA varlıkları tablo satırlarına dönüştürülür. Bu varlıklar tüm katmanlar tarafından, özellikle de web katmanı tarafından işlenir. Bu nedenle bunları bilmemiz gerekir:

[Employe] varlığı, [Employes] tablosundaki bir satırı temsil eder

ID
otomatik artan türünde birincil anahtar
VERSION
kayıt sürüm numarası
PRENOM
çalışanın adı
NOM
soyadı
ADRESSE
adresi
CP
posta kodu
VILLE
şehri
INDEMNITE_ID
INDEMNITES (ID) üzerindeki yabancı anahtar

[Employe] varlığı şöyledir:


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()
    +"]";
  }
  
  // alıcı ve ayarlayıcılar
   ...  
}

[Jpa] katmanına yönelik @ ek açıklamaları göz ardı edilecektir. Sınıfın çeşitli alanları, [EMPLOYES] tablosunun çeşitli sütunlarını yansıtmaktadır. indemnites alanı (29. satır), [EMPLOYES] tablosunun [INDEMNITES] tablosuna bir yabancı anahtara sahip olduğunu gösterir. Bir çalışanı düzenlediğinizde, onun tazminatları da düzenlenir.

[Indemnite] varlığı, [INDEMNITES] tablosundaki bir satırın nesne ifadesidir:

ID
otomatik artan türünde birincil anahtar
VERSION
kayıt sürüm numarası
BASE_HEURE
bir saatlik nöbetin euro cinsinden maliyeti
ENTRETIEN_JOUR
Nöbet tutma başına günlük tazminat (Euro)
REPAS_JOUR
Nöbet tutulan her gün için euro cinsinden yemek ödeneği
INDEMNITES_CP
Ücretli izin ödenekleri. Bu,
uygulanacak bir yüzde.

[Indemnite] öğesi şu şekildedir:


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()
    + "]";
  }
  
   // getter ve setter'lar
....  
}

Sınıfın çeşitli alanları, [INDEMNITES] tablosunun çeşitli sütunlarını yansıtmaktadır.

[Cotisation] varlığı, [COTISATIONS] tablosundaki bir satırın nesne ifadesidir:

ID
otomatik artan türünde birincil anahtar
VERSION
kayıt sürüm numarası
SECU
sosyal güvenlik katkı payı oranı (yüzde)
sosyal güvenlik
RETRAITE
emeklilik katkı payı oranı
CSGD
genel sosyal katkı payı oranı
indirimli genel katkı payı oranı
CSGRDS
genel sosyal katkı payı
ve sosyal borç geri ödemesi
için katkı payı oranı

[Cotisation] varlığı şöyledir:


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()+"]";
  }
  
  // getter ve setter'lar
  ... 
}

Sınıfın çeşitli alanları, [INDEMNITES] tablosunun çeşitli sütunlarını yansıtmaktadır.

18.4. Bir çocuk bakıcısının maaşının hesaplanma yöntemi

Yazacağımız web uygulaması, üç bilgiye dayanarak bir çalışanın maaşını hesaplamamızı sağlayacaktır:

  • çalışanın endeksi
  • çalışılan gün sayısı
  • çalışılan saat sayısı

İşte maaş hesaplamasına ait bir ekran görüntüsü:

Image

Şimdi bir çocuk bakıcısının aylık maaşının hesaplanma yöntemini sunuyoruz. Bu, gerçekte kullanılan yöntem olduğunu iddia etmemektedir. Örnek olarak, ödeme yapılacak ay boyunca 20 gün boyunca 150 saat çalışmış olan Bayan Marie Jouveinal'ın maaşını ele alıyoruz.

Aşağıdaki unsurlar dikkate alınmaktadır:
[TOTALHEURES]: total des heures
 travaillées dans le mois
[TOTALJOURS]: total des jours
 travaillés dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
Anaokulu asistanının
aşağıdaki formülle hesaplanır
şeklindedir:
[SALAIREBASE]=([TOTALHEURES]
*[BASEHEURE])*(1+[INDEMNITESCP]/
100)
[SALAIREBASE]=(150*[2.1])
*(1+0.15)= 362,25
Bir miktar sosyal güvenlik primi
bu temel maaştan
:
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
Toplam sosyal güvenlik primleri:
[COTISATIONSSOCIALES]=[SALAIREBASE]
*(CSGRDS+CSGD+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
Ayrıca, çocuk bakıcısı,
çalıştığı her gün için bakım
ve yemek ödeneği
yemek ödeneği almaya hak kazanır. Bu kapsamda,
şunlardır:
[Indemnités]=[TOTALJOURS]
*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
Sonuç olarak, çocuk bakıcısına ödenecek net maaş şu şekildedir:
[SALAIREBASE]-
[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

18.5. [métier] katmanının arayüzü

Geliştirmekte olduğumuz uygulamanın mimarisine tekrar göz atalım:

[web / struts 2] katmanı, [métier] katmanının arayüzüyle iletişim kurar. Bu arayüz şöyledir:


package metier;

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

public interface IMetier {
  // maaş bordrosunu alma
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // çalışan listesi
  List<Employe> findAllEmployes();
}
  • 8. satır: Bir çalışanın maaşını hesaplamamızı sağlayacak yöntem
  • 10. satır: çalışanlar açılır menüsünü doldurmamızı sağlayacak yöntem

calculerFeuillesalaire yöntemi, aşağıdaki [FeuilleSalaire] sınıfının bir örneğini döndürür:


package metier;

import java.io.Serializable;
import jpa.Cotisation;
import jpa.Employe;

public class FeuilleSalaire implements Serializable {
  // özel alanlar

  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

  // oluşturucular
  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 + "]";
  }

  // alıcı ve ayarlayıcılar
  ...
}

Maaş bordrosu aşağıdaki bilgileri içerir:

  • 10. satır: Maaşı hesaplanan çalışanla ilgili bilgiler
  • 11. satır: çeşitli prim oranları
  • 12. satır: maaş bileşenleri

[ElementsSalaire] sınıfı şu şekildedir:


package metier;

import java.io.Serializable;

public class ElementsSalaire implements Serializable{
  
  // özel alanlar
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;
  
  // yapıcılar
  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 + "]";
  }
  
  // getter ve setter'lar
  ...
}
  • 8-12. satırlar: maaş bileşenleri

18.6. Spring yapılandırma dosyası

[métier, dao, jpa] katmanlarının entegrasyonu, aşağıdaki Spring yapılandırma dosyası ile sağlanır:


<?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">
  
  <!-- uygulama katmanları -->
 
<!-- iş alanı -->
  <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" />
  
  <!-- yapılandırma 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>
  
  <!-- veri kaynağı 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>
  
  <!-- işlem yöneticisi -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>
  
  <!-- istisna çevirisi -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
  
  <!-- kalıcılık -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
  
</beans>

Bu yapılandırmayı açıklamaya çalışmayacağız. Bu yapılandırma, [métier, dao, jpa] katmanlarının örneklenmesi ve entegrasyonu için gereklidir. Bu katmanlara dayanacak web uygulamamızda bu yapılandırmayı kullanmamız gerekecektir. 32-37. satırların veritabanının JDBC özelliklerini yapılandırdığına dikkat edilmelidir. Veritabanını değiştirmek isteyen okuyucular bu satırları düzenlemelidir.

Bu yapılandırma hakkında daha fazla bilgi için, URL [http://tahe.developpez.com/java/javaee] adresinde bulunan “Java EE 5’e Giriş” belgesini inceleyebilirsiniz.