18. 案例研究:Struts 2 / Tiles / Spring / Hibernate / MySQL
我们将通过一个案例研究来结束对 Struts 的介绍。为了更贴近实际,我们将探讨的示例将比之前介绍的那些复杂得多。对于初学者来说,在着手这个案例研究之前,最好先通过个人项目巩固对 Struts 2 基础知识的理解。
该应用程序将采用分层架构:
![]() |
[业务]、[DAO] 和 [JPA/Hibernate] 层将以 JAR 归档的形式提供给我们,我们将详细介绍其功能。Spring 将负责这些层的集成。而 [Web] 层将使用 Struts 2 进行实现。
![]() |
18.1. 问题
我们计划开发一个网络应用程序,用于为受雇于某市“幼儿中心”的保育员生成工资单。

本案例研究详见以下文档:
《Java EE 5 入门》一书,可访问网址 [http://tahe.developpez.com/java/javaee]
在本文档中,该案例研究采用以下多层架构实现:
![]() |
[Web] 层采用 JSF(Java Server Faces)框架实现。我们将采用相同的架构,使用 Struts 2 实现 [Web] 层。为了展示分层架构的优势,我们将使用 JSF 版本中包含 [业务、DAO、JPA] 层的 JAR 归档文件,并将其连接到 [Web / Struts 2] 层:
![]() |
我们将介绍[业务、DAO、JPA]层的以下组件:
- [Web]层与[业务]层进行交互。我们将展示这一交互接口。
- [JPA]层用于访问数据库。我们将对此进行介绍。
- [JPA] 层将数据库表中的行转换为 JPA 实体,这些实体被应用程序的所有层所使用。我们将介绍它们。
- [业务、DAO、JPA] 层由 Spring 进行实例化。我们将展示执行此实例化和集成配置的配置文件。
18.2. 数据库
我们将使用以下 MySQL 数据库 [dbpam_hibernate]:

- 在 [1] 中,该数据库包含三个表:
- [employees]:记录日托中心员工的表
- [contributions]:用于存储社会保险缴费率的表
- [indemnites]:用于存储计算员工薪酬所需信息的表
[employees] 表
![]() |
- 在 [2] 中是 employees 表,在 [3] 中是其字段的含义
该表的内容可能如下所示:
![]()
表 [contributions]
![]() |
- 在 [4] 中,是贡献表;而在 [5] 中,则是该表各字段的含义
该表格的内容可能如下所示:
![]()
表 [indemnites]
![]() |
- 在 [6] 中,即津贴表,以及在 [7] 中,其字段的含义
该表格的内容可能如下:
![]()
将数据库结构导出到 SQL 文件后,结果如下:
18.3. JPA 实体
在以下架构中
![]() |
[Jpa] 层充当了 [dao] 层处理的对象与 Jdbc 驱动程序处理的数据库表中的行之间的桥梁。从数据库表中读取的行会被转换为称为 JPA 实体的对象。反之,在写入过程中,JPA 实体会被转换为表行。这些实体由所有层处理,尤其是 Web 层。因此,我们需要了解它们:
[Employee] 实体代表 [Employees] 表中的一行
ID:主键,类型为自动递增VERSION:记录版本号FIRST_NAME:员工名LAST_NAME:员工姓ADDRESS:员工地址ZIP:员工邮政编码CITY:员工所在城市INDEMNITY_ID:指向INDEMNITIES表(ID)的外键

[Employee] 实体如下:
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
...
}
我们将忽略针对 [Jpa] 层的 @ 注解。该类的各个字段对应 [EMPLOYEES] 表的各个列。indemnities 字段(第 29 行)反映了 [EMPLOYEES] 表与 [INDEMNITIES] 表之间存在外键关系这一事实。当我们操作一名员工时,也会同时操作其相关赔偿金。
[Indemnite] 实体是 [INDEMNITIES] 表中某一行数据的对象表示:
ID:自增主键VERSION:记录版本号BASE_HEURE:每小时待命值班的费用(单位:欧元)DAILY_MAINTENANCE:每日津贴(单位:欧元)MEAL_DAY:每日护理餐费津贴(单位:欧元)PAID_LEAVE_ALLOWANCES:带薪休假津贴。此为应用于基本工资的百分比。

[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()
+ "]";
}
// getters and setters
....
}
该类的各个字段反映了 [INDEMNITIES] 表的各个列。
[Cotisation] 实体是 [COTISATIONS] 表中某一行在对象层面的表示:
ID:主键,类型为自动递增VERSION:记录版本号SECU:社会保险缴费率(百分比)socialPENSION:养老金缴费率CSGD:一般社会保险缴费率Generalized Deductible:一般社会保险免赔额CSGRDS:社会债务偿还缴费率

[Contribution] 实体的结构如下:
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
...
}
该类的各个字段对应 [INDEMNITES] 表中的不同列。
18.4. 基于...的方法来计算保育员的薪资
我们将编写一个 Web 应用程序,该应用程序允许我们根据三项信息计算员工的薪资:
- 员工的指数
- 工作天数
- 工作小时数
以下是薪资计算的截图:

接下来我们将介绍计算保育员月薪的方法。此方法并非用于实际应用。作为示例,我们将以玛丽·朱维纳尔女士的薪资为例,她在该薪资周期内工作了20天,共计150小时。
计算时考虑了以下因素: | | |
该 由以下公式计算 : | ||
需从该基本工资中扣除一定数额的社会保险费 必须从该基本工资中扣除 : | | |
社会保障缴费总额: | ||
此外,保育员有权 每日 以及一笔餐费补贴 。因此,她获得的 : | ||
最终,支付给保育员的净薪资如下: |
18.5. [业务]层接口
让我们来看看我们正在构建的应用程序的架构:
![]() |
[Web / Struts 2] 层与 [业务] 层的接口进行通信。具体如下:
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();
}
- 第 8 行:用于计算员工工资的方法
- 第 10 行:用于填充员工下拉列表的方法
calculatePaystub 方法返回以下 [Paystub] 类的实例:
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
...
}
工资单包含以下信息:
- 第10行:关于正在计算薪资的员工的信息
- 第11行:各项缴费率
- 第12行:工资构成
[ElementsSalaire] 类如下:
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
...
}
- 第 8-12 行:薪资构成
18.6. Spring 配置文件
[业务、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">
<!-- 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>
我们不会尝试解释此配置。它是 [业务、DAO、JPA] 层实例化和集成的必要条件。因此,依赖于这些层的 Web 应用程序必须采用此配置。请注意,第 32– –37 行配置了数据库的 JDBC 属性。希望更改数据库的读者必须修改这些行。
有关此配置的更多信息,请参阅文档《Java EE 5 入门》,该文档可通过以下网址获取:[http://tahe.developpez.com/java/javaee]。








