18. Приклад із практики: Struts 2 / Tiles / Spring / Hibernate / MySQL
Ми завершимо наше вивчення Struts аналізом конкретного випадку. Щоб приклад був реалістичним, він буде значно складнішим за ті, що ми розглядали раніше. Початківцям, безсумнівно, краще поглибити знання основ Struts 2 на власних додатках, перш ніж переходити до цього аналізу конкретного випадку.
Додаток використовуватиме багаторівневу архітектуру:
![]() |
Набір шарів [metier], [dao], [jpa/hibernate] буде надано нам у вигляді архіву jar, функціональні можливості якого ми детально розглянемо. Інтеграцію шарів забезпечить Spring. Шар [web] буде реалізовано за допомогою Struts 2.
![]() |
18.1. Задача
Ми пропонуємо створити веб-додаток, що дозволяє формувати розрахункові листки для виховательок, які працюють у «Дому раннього дитинства» однієї з громад.

Цей приклад з практики представлено в документі:
«Вступ до Java» 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()
+"]";
}
// методи отримання та встановлення значень
...
}
Анотації @, призначені для шару [Jpa], ігноруються. Різні поля класу відповідають різним стовпцям таблиці [EMPLOYES]. Поле «indemnites» (рядок 29) відображає те, що таблиця [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. Спосіб розрахунку заробітної плати няні
Веб-додаток, який ми будемо розробляти, дозволить нам розраховувати заробітну плату працівниці на основі трьох даних:
- індекс працівниці
- кількість відпрацьованих днів
- кількість відпрацьованих годин
Ось знімок екрана з розрахунком заробітної плати:

Тепер ми розглянемо спосіб розрахунку місячної заробітної плати няні. Він не претендує на те, щоб відповідати реальному. Як приклад візьмемо заробітну плату пані Марі Жувенал, яка пропрацювала 150 годин протягом 20 днів у місяці, за який виплачується заробітна плата.
Враховуються такі елементи: | ||
Базова заробітна плата виховательки визначається за такою формулою : | ||
Певна сума соціальних внесків мають бути утримані з цієї базової заробітної плати : | ||
Загальна сума соціальних внесків: | ||
Крім того, вихователька має право, за кожен відпрацьований день на компенсацію на утримання, а також на компенсацію харчування. З цієї причини вона отримує : | ||
У підсумку чиста заробітна плата, що підлягає виплаті няні, становить: |
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();
}
- рядок 8: метод, який дозволить нам обчислити зарплату співробітника
- рядок 10: метод, який дозволить нам заповнити комбінований список співробітників
Метод calculerFeuillesalaire повертає екземпляр класу [FeuilleSalaire] наступного вигляду:
package metier;
import java.io.Serializable;
import jpa.Cotisation;
import jpa.Employe;
public class FeuilleSalaire implements Serializable {
// приватні поля
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 + "]";
}
// методи getter та setter
...
}
Табель оплати праці містить таку інформацію:
- рядок 10: інформація про працівника, для якого розраховується заробітна плата
- рядок 11: різні ставки відрахувань
- рядок 12: складові заробітної плати
Клас [ElementsSalaire] має такий вигляд:
package metier;
import java.io.Serializable;
public class ElementsSalaire implements Serializable{
// приватні поля
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 + "]";
}
// геттери та сеттери
...
}
- рядки 8–12: складові заробітної плати
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]. Отже, наш веб-додаток, який буде базуватися на цих шарах, повинен використовувати цю конфігурацію. Зазначимо, що рядки 32–37 визначають параметри Jdbc для бази даних. Читач, який бажає змінити базу даних, повинен відредагувати ці рядки.
Для отримання додаткової інформації щодо цієї конфігурації слід ознайомитися з документом «Вступ до Java EE 5», доступним на URL [http://tahe.developpez.com/java/javaee].











