Skip to content

18. Case Study: Struts 2 / Tiles / Spring / Hibernate / MySQL

We will conclude our Struts tutorial with a case study. To ensure realism, the example we will examine will be significantly more complex than those covered previously. For beginners, it is likely best to solidify your understanding of Struts 2 fundamentals through personal projects before tackling this case study.

The application will use a layered architecture:

The set of layers [metier], [dao], and [jpa/hibernate] will be provided to us in the form of an archive jar, whose features we will detail. The integration of the layers will be handled by Spring. The [web] layer will be implemented using Struts 2.

18.1. The Problem

We propose to develop a web application to generate pay stubs for child care providers employed by a municipality’s “Early Childhood Center.”

Image

This case study is presented in the document:

Introduction to Java EE 5 available at Url [http://tahe.developpez.com/java/javaee]

In this document, the case study is implemented using the following multi-tier architecture:

The [web] layer is implemented using the JSF framework (Java Server Faces). We will adopt this same architecture by implementing the [web] layer with Struts 2. To demonstrate the benefits of layered architectures, we will use the jar archive of the [metier, dao, jpa], version, and JSF layers and connect it to a [web / struts2] layer:

We will present the following elements of the [metier, dao, jpa] layers:

  • The [web] layer interfaces with the [métier] layer. We will present this interface.
  • The [jpa] layer accesses a database. We will present it.
  • The [jpa] layer transforms rows from the database tables into JPA entities used by all layers of the application. We will present them.
  • The [metier, dao, jpa] layers are instantiated by Spring. We will present the configuration file that performs this instantiation and integration.

18.2. The database

We will use the following MySQL [dbpam_hibernate] database:

Image

 
  • In [1], the database has three tables:
  • [employes]: a table that records the employees of a daycare center
  • [cotisations]: a table that stores social security rates
  • [indemnites]: a table that stores information used to calculate employee pay

Table [employes]

  • in [2], the employee table, and in [3], the meaning of its fields

The table contents could be as follows:

Image

Table [cotisations]

  • in [4], the table for cotisations, and in [5], the meaning of its fields

The table contents could be as follows:

Image

Table [indemnites]

  • in [6], the table of allowances, and in [7], the meaning of its fields

The table contents could be as follows:

Image

Exporting the database structure to a SQL file yields the following result:

#
# Structure for the `cotisations` table :
#

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;

#
# Structure for the `indemnites` table :
#

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;

#
# Structure for the `employes` table :
#

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. The entities JPA

In the following architecture

The [Jpa] layer acts as a bridge between the objects handled by the [dao] layer and the rows in the database tables handled by the JDBC driver. The rows in the tables read from the database are transformed into objects called JPA entities. Conversely, during write operations, JPA entities are converted into table rows. These entities are handled by all layers, particularly the web layer. We therefore need to understand them:

The [Employe] entity represents a row in the [Employes] table

PRIMARY_KEY primary key of type autoincrementVERSION version number of version for the recordFIRST_NAME first name of the employeeLAST_NAME her last nameADDRESS her addressZIP her ZIP codeCITY her cityINDEMNITY_ID foreign key on INDEMNITES(ID)

Image

The [Employe] entity is as follows:


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()
    +"]";
  }
 
  // getters and setters
   ...  
}

We will ignore the @ annotations intended for the [Jpa] layer. The various fields of the class reflect the various columns of the [EMPLOYES] table. The indemnites field (line 29) reflects the fact that the [EMPLOYES] table has a foreign key on the [INDEMNITES] table. When you manipulate an employee, you also manipulate their benefits.

The entity [Indemnite] is the object expression of a row in the table [INDEMNITES]:

Primary key ID of type autoincrementVERSION number of version for the recordBASE_HOURcost in euros for one hour of on-call dutyENTRETIEN_JOURallowance in euros per day of on-call dutyREPAS_JOURmeal allowance in euros per day of on-call dutyINDEMNITES_CPpaid leave allowances. This is a percentage to be applied to the base salary.

Image

The [Indemnite] entity is as follows:


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()
    + "]";
  }
 
  // getters and setters
....  
}

The various fields of the class reflect the various columns of the table [INDEMNITES].

The entity [Cotisation] is the object expression of a row in the table [COTISATIONS]:

IDprimary key of type autoincrementVERSIONversion record numberSECU (percentage) of the social security contribution RETIREMENT contribution rate for retirement CSGD contribution rate for the deductible general social contribution CSGRD contribution rate for the general social contribution and the contribution to the repayment of the social debt

Image

The [Cotisation] entity is as follows:


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()+"]";
  }
 
  // getters and setters
  ...
}

The various fields of the class reflect the various columns of table [INDEMNITES].

18.4. How to calculate a child care provider's salary

The web application we are going to write will allow us to calculate an employee’s salary based on three pieces of information:

  • the employee's index
  • the number of days worked
  • the number of hours worked

Here is a screenshot of a salary calculation:

Image

We will now present the method for calculating a child care provider’s monthly salary. This is not intended to be the method used in real life. As an example, we will use the salary of Ms. Marie Jouveinal, who worked 150 hours over 20 days during the pay period.

The following factors are taken into account:

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

[TOTALJOURS]: total des jours travaillés dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
The base salary of the
is given by the following formula
:

[SALAIREBASE]=([TOTALHEURES]*[BASEHEURE])*(1+[INDEMNITESCP]/100)
[SALAIREBASE]=(150*[2.1])*(1+0.15)= 362,25
A certain number of social security contributions
must be deducted from this base salary
:

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
Social Security: 34.02
Pension: 28.55
Total Social Security Contributions:

[COTISATIONSSOCIALES]=[SALAIREBASE]*(CSGRDS+CSGD+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
In addition, the child care provider is entitled,
for each day worked, to a
as well as a meal allowance
. For this purpose, she receives the
:

[Indemnités]=[TOTALJOURS]*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
In the end, the salary net to be paid to the child care provider is as follows:

[SALAIREBASE]-[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

18.5. The [métier] layer interface

Let’s revisit the architecture of the application we are building:

The [web / struts 2] layer communicates with the interface of the [métier] layer. This interface is as follows:


package metier;
 
import java.util.List;
import jpa.Employe;
 
public interface IMetier {
  // get your payslip
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // list of employees
  List<Employe> findAllEmployes();
}
  • line 8: the method that will allow us to calculate an employee's salary
  • line 10: the method that will allow us to populate the employee dropdown

The calculerFeuillesalaire method returns an instance of the following [FeuilleSalaire] class:


package metier;
 
import java.io.Serializable;
import jpa.Cotisation;
import jpa.Employe;
 
public class FeuilleSalaire implements Serializable {
  // private fields
 
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;
 
  // manufacturers
  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 + "]";
  }
 
  // getters and setters
  ...
}

The pay slip contains the following information:

  • line 10: information about the employee whose salary is being calculated
  • line 11: the various rates for cotisations
  • line 12: salary components

The [ElementsSalaire] class is as follows:


package metier;
 
import java.io.Serializable;
 
public class ElementsSalaire implements Serializable{
 
  // private fields
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;
 
  // manufacturers
  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 + "]";
  }
 
  // getters and setters
  ...
}
  • lines 8-12: salary components

18.6. The Spring configuration file

The integration of the [métier, dao, jpa] layers is handled by the following Spring configuration file:


<?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">
 
  <!-- application layers -->
 
<!-- business -->
  <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" />
 
  <!-- configuration 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>
 
  <!-- the DBCP data source -->
  <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>
 
  <!-- transaction manager -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>
 
  <!-- translation of exceptions -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
 
  <!-- persistence -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
 
</beans>

We will not attempt to explain this configuration. It is necessary for the instantiation and integration of the [métier, dao, jpa] layers. Our web application, which will rely on these layers, must therefore adopt this configuration. Note that lines 32–37 configure the JDBC properties of the database. Readers who wish to change the database must modify these lines.

For more information on this configuration, refer to the document Introduction to Java EE 5 available at Url [http://tahe.developpez.com/java/javaee].