13. [SimuPaie] 应用程序 – 第 9 版 – Spring/NHibernate 集成
在此,我们建议重新审视第 7 版 [pam-v7-3tier-nhibernate-multivues-multipages] 中的三层 ASP.NET 应用程序。该应用程序的分层架构如下:
![]() |
在上述版本中,[DAO]层是使用NHibernate框架实现的。Spring框架仅用于各层之间的集成。Spring框架提供了用于与NHibernate框架交互的工具类。使用这些类可以使[DAO]层的代码更易于编写。之前的架构演变如下:
![]() |
由于采用了分层结构,实现 Spring/NHibernate 集成只需修改 [DAO] 层。 [Presentation](Web/ASP.NET)和 [Business] 层无需修改。这是集成 Spring 的分层架构的主要优势。
在接下来的章节中,我们将通过注释一个可运行的解决方案的代码,来构建基于 [Spring/NHibernate] 的 [DAO] 层。 我们不会试图涵盖 [Spring/NHibernate] 框架的所有配置选项或用法。读者可以参考 Spring.NET 文档 [http://www.springframework.net/documentation.html](2010 年 6 月版),根据自身需求调整本方案。
构建 [DAO] 和 [业务] 层所采用的方法是第 7 段中描述的第 3 版方法。构建 [展示] 层所采用的方法是第 11 段中描述的第 7 版方法。
13.1. [DAO] 数据访问层
![]() |
13.1.1. [dao] 层的 Visual Studio C# 项目
[dao] 层的 Visual Studio 项目如下:
![]() |
- 在 [1] 中,整个项目
- [pam] 文件夹包含项目类以及 NHibernate 实体的配置
- [App.config] 和 [Dao.xml] 文件用于配置 Spring/NHibernate 框架。我们需要详细说明这两个文件的内容。
- 在 [2] 中,项目的各类类
- 在 [entities] 文件夹中,我们可以找到 [pam-dao-nhibernate] 项目中研究的 NHibernate 实体
- 在 [service] 文件夹中,我们可以找到 [IPamDao] 接口及其基于 Spring/NHibernate 框架的实现 [PamDaoSpringNHibernate]。我们需要编写这个 [IPamDao] 接口的新实现
- [tests] 文件夹包含与 [pam-dao-nhibernate] 项目相同的测试用例。它们测试的是相同的 [IPamDao] 接口。
- 在 [3] 中,项目引用了 Spring/NHibernate 集成所需的两个新 DLL:[Spring.Data] 和 [Spring.Data.NHibernate12]。这些 DLL 可在 Spring.Net 框架中获取,并已添加到 [lib] 文件夹中的 DLL [4] 中:
![]() |
在项目引用 [3] 中,您将找到以下 DLL:
- NHibernate:用于 NHibernate ORM
- MySql.Data:用于 MySQL 数据库管理系统 (DBMS) 的 ADO.NET 驱动程序
- Spring.Core:用于 Spring 框架,负责各层面的集成
- log4net:日志记录库
- nunit.framework:一个单元测试库
- Spring.Data 和 Spring.Data.NHibernate12:提供 Spring/NHibernate 支持。
这些引用取自 [lib] 文件夹 [4]。请确保所有这些引用的“本地副本”属性均设置为“True” [5]:
13.1.2. 配置 C# 项目
该项目的配置如下:
![]() |
- 在 [1] 中,项目程序集名称为 [pam-dao-spring-nhibernate]。该名称出现在各种项目配置文件中。
13.1.3. [dao] 层中的实体
![]() |
[dao] 层所需的实体(对象)已汇总在项目的 [entities] 文件夹 [1] 中。这些实体与 [pam-dao-nhibernate] 项目中的实体相同,仅在 NHibernate 配置文件中有一处例外。以 [Employee.hbm.xml] 文件为例:
- 在 [2] 中,该文件已配置为包含在项目程序集内
其内容如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-spring-nhibernate">
<class name="Employe" table="EMPLOYES">
<id name="Id" column="ID">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="SS" column="SS" length="15" not-null="true" unique="true"/>
<property name="Nom" column="NOM" length="30" not-null="true"/>
<property name="Prenom" column="PRENOM" length="20" not-null="true"/>
<property name="Adresse" column="ADRESSE" length="50" not-null="true" />
<property name="Ville" column="VILLE" length="30" not-null="true"/>
<property name="CodePostal" column="CP" length="5" not-null="true"/>
<many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="all" lazy="false"/>
</class>
</hibernate-mapping>
- 第 2 行:assembly 属性表示 [Employee.hbm.xml] 文件位于 [pam-dao-spring-nhibernate] 程序集内
13.1.4. Spring / NHibernate 配置
让我们回到 Visual C# 项目:
![]() |
- 在 [1] 中,[App.config] 和 [Dao.xml] 文件配置了 Spring / NHibernate 的集成
13.1.4.1. [ App.config] 文件
[App.config] 文件内容如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- configuration sections -->
<configSections>
<sectionGroup name="spring">
<section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
</sectionGroup>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<!-- spring configuration -->
<spring>
<parsers>
<parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
</parsers>
<context>
<resource uri="Dao.xml" />
</context>
</spring>
<!-- This section contains the log4net configuration settings -->
<!-- NOTE IMPORTANTE: logs are not active by default. They must be activated by program
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5level %logger - %message%newline" />
</layout>
</appender>
<!-- Set default logging level to DEBUG -->
<root>
<level value="DEBUG" />
<appender-ref ref="ConsoleAppender" />
</root>
<!-- Set logging for Spring. Logger names in Spring correspond to the namespace -->
<logger name="Spring">
<level value="INFO" />
</logger>
<logger name="Spring.Data">
<level value="DEBUG" />
</logger>
<logger name="NHibernate">
<level value="DEBUG" />
</logger>
</log4net>
</configuration>
上面的 [App.config] 文件配置了 Spring(第 5–9 行、第 15–22 行)和 log4net(第 10 行、第 28–53 行),但未配置 NHibernate。Spring 对象不在 [App.config] 中配置,而是在 [Dao.xml] 文件中配置(第 20 行)。 因此,包含特定 Spring 对象声明的 Spring/NHibernate 配置将位于此文件中。
13.1.4.2. [Dao.xml] 文件
包含 Spring 管理对象的 [Dao.xml] 文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:db="http://www.springframework.net/database">
<!-- Referenced by main application context configuration file -->
<description>
Application Spring / NHibernate
</description>
<!-- Database and NHibernate Configuration -->
<db:provider id="DbProvider"
provider="MySql.Data.MySqlClient"
connectionString="Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;"/>
<object id="NHibernateSessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate12">
<property name="DbProvider" ref="DbProvider"/>
<property name="MappingAssemblies">
<list>
<value>pam-dao-spring-nhibernate</value>
</list>
</property>
<property name="HibernateProperties">
<dictionary>
<entry key="hibernate.dialect" value="NHibernate.Dialect.MySQLDialect"/>
<entry key="hibernate.show_sql" value="false"/>
</dictionary>
</property>
<property name="ExposeTransactionAwareSessionFactory" value="true" />
</object>
<!-- transaction manager -->
<object id="transactionManager"
type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate12">
<property name="DbProvider" ref="DbProvider"/>
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
</object>
<!-- Hibernate Template -->
<object id="HibernateTemplate" type="Spring.Data.NHibernate.Generic.HibernateTemplate">
<property name="SessionFactory" ref="NHibernateSessionFactory" />
<property name="TemplateFlushMode" value="Auto" />
<property name="CacheQueries" value="true" />
</object>
<!-- Data Access Objects -->
<object id="pamdao" type="Pam.Dao.Service.PamDaoSpringNHibernate, pam-dao-spring-nhibernate" init-method="init" destroy-method="destroy">
<property name="HibernateTemplate" ref="HibernateTemplate"/>
</object>
</objects>
- 第 11–13 行配置了与 [dbpam_nhibernate] 数据库的连接。其中包括:
- 连接所需的 ADO.NET 提供程序,本例中为 MySQL 数据库管理系统 (DBMS) 提供程序。这要求在项目引用中包含 [Mysql.Data] DLL。
- 数据库连接字符串(服务器、数据库名称、连接所有者和密码)
- 第 15–29 行配置了 NHibernate SessionFactory,该对象用于获取 NHibernate 会话。请注意,所有数据库操作都在 NHibernate 会话内进行。在第 15 行中,我们可以看到 SessionFactory 由 Spring 类 Spring.Data.NHibernate.LocalSessionFactoryObject 实现,该类位于 Spring.Data.NHibernate12 DLL 中。
- 第 16 行:DbProvider 属性用于设置数据库连接参数(ADO.NET 提供程序和连接字符串)。此处,该属性引用了之前在第 11–13 行中定义的 DbProvider 对象。
- 第 17–20 行:指定包含 [*.hbm.xml] 文件的程序集列表,这些文件用于配置由 NHibernate 管理的实体。第 19 行表明这些文件将位于项目程序集中。请注意,此名称可在 C# 项目属性中找到。另请注意,所有 [*.hbm.xml] 文件均已配置为包含在项目程序集中。
- 第 22–27 行:NHibernate 专用的属性。
- 第 24 行:将使用的 SQL 方言为 MySQL
- 第 25 行:NHibernate 生成的 SQL 语句不会出现在控制台日志中。将此属性设置为 true 可让您查看 NHibernate 生成的 SQL 语句。这有助于您理解,例如,应用程序在访问数据库时为何运行缓慢。
- 第 28 行:将 ExposeTransactionAwareSessionFactory 属性设置为 true 将导致 Spring 管理 C# 代码中发现的事务管理注解。我们在编写实现 [DAO] 层的类时将再次讨论这一点。
- 第 32–36 行定义了事务管理器。同样,该管理器是来自 Spring.Data.NHibernate12 DLL 的 Spring 类。该管理器需要数据库连接参数(第 34 行)以及 NHibernate SessionFactory(第 35 行)。
- 第 39–43 行定义了 HibernateTemplate 类的属性,该类也是一个 Spring 类。该类将在实现 [DAO] 层的类中作为辅助类使用,用于简化与 NHibernate 对象的交互。该类包含一些必须初始化的属性:
- 第 40 行:NHibernate SessionFactory
- 第 41 行:TemplateFlushMode 属性用于设置 NHibernate 持久化上下文与数据库的同步模式。Auto 模式确保同步操作发生于:
- 在事务结束时
- 在 SELECT 操作之前
- 第 42 行:HQL(Hibernate 查询语言)查询将被缓存。这有助于提升性能。
- 第 46–48 行定义了 [dao] 层的实现类
- 第 46 行:[dao] 层将由 [pam-dao-spring-nhibernate] DLL 中的 [PamdaoSpringNHibernate] 类实现。该类实例化后,其 init 方法将立即执行。当 Spring 容器关闭时,该类的 destroy 方法将被执行。
- 第 47 行:[PamDaoSpringNHibernate] 类将拥有一个 HibernateTemplate 属性,该属性将使用第 39 行中的 HibernateTemplate 属性进行初始化。
13.1.5. [dao] 层的实现
13.1.5.1. 实现类的骨架
[IPamDao] 接口与 [pam-dao-nhibernate] 项目中的相同:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// an individual employee with benefits
Employe GetEmploye(string ss);
// list of all contributions
Cotisations GetCotisations();
}
}
- 第 1 行:我们导入了 [dao] 层中实体的命名空间。
- 第 3 行:[dao] 层位于 [Pam.Dao.Service] 命名空间中。[Pam.Dao.Entities] 命名空间中的元素可以创建多个实例,而 [Pam.Dao.Service] 命名空间中的元素则作为单例(singleton)创建。这正是命名空间命名选择的依据。
- 第 4 行:接口命名为 [IPamDao]。它定义了三个方法:
- 第 6 行:[GetAllIdentitesEmployes] 返回一个 [Employe] 类型的对象数组,以简化格式(姓、名、社保号)表示托儿服务提供者的列表。
- 第 8 行:[GetEmploye] 返回一个 [Employe] 对象:该对象对应的是作为方法参数传入的社会保险号所属的员工,并包含与其薪级相关的福利。
- 第 10 行,[GetCotisations] 返回 [Cotisations] 对象,该对象封装了从毛薪中扣除的各项社会保障缴费率。
使用 Spring/NHibernate 实现此接口的类骨架可能如下所示:
using System;
using System.Collections;
using System.Collections.Generic;
using Pam.Dao.Entites;
using Spring.Data.NHibernate.Generic.Support;
using Spring.Transaction.Interceptor;
namespace Pam.Dao.Service {
public class PamDaoSpringNHibernate : HibernateDaoSupport, IPamDao {
// private fields
private Cotisations cotisations;
private Employe[] employes;
// init
[Transaction(ReadOnly = true)]
public void init() {
...
}
// delete object
public void destroy() {
if (HibernateTemplate.SessionFactory != null) {
HibernateTemplate.SessionFactory.Close();
}
}
// list of all employee identities
public Employe[] GetAllIdentitesEmployes() {
return employes;
}
// an individual employee with benefits
[Transaction(ReadOnly = true)]
public Employe GetEmploye(string ss) {
....
}
// list of contributions
public Cotisations GetCotisations() {
return cotisations;
}
}
}
- 第 9 行:[PamDaoSpringNHibernate] 类正确实现了 [dao] 层接口 [IPamDao]。它还继承自 Spring 类 [HibernateDaoSupport]。该类有一个 [HibernateTemplate] 属性,该属性由下方第 2 行设置的 Spring 配置进行初始化:
<object id="pamdao" type="Pam.Dao.Service.PamDaoSpringNHibernate, pam-dao-spring-nhibernate" init-method="init" destroy-method="destroy">
<property name="HibernateTemplate" ref="HibernateTemplate"/>
</object>
- 在上文第 1 行中,我们可以看到 [pamdao] 对象的定义指定了 [PamDaoSpringNHibernate] 类的 init 和 destroy 方法必须在特定时间执行。这两个方法确实存在于该类的第 16 行和第 21 行。
- 第 15 行、第 34 行:这些注解确保被注解的方法将在事务内执行。ReadOnly=true 属性表示该事务为只读事务。在事务内执行的方法可能会抛出异常。在这种情况下,Spring 会自动回滚事务。此注解消除了在方法内部管理事务的必要性。
- 第 16 行:类实例化后,Spring 会立即执行 init 方法。我们将看到,其目的是初始化第 11 行和第 12 行的私有字段。该方法将在事务内运行(第 15 行)。
- [IPamDao] 接口的方法在第 28、35 和 40 行中实现。
- 第 28–30 行:[GetAllIdentitiesEmployees] 方法仅返回第 12 行定义的属性,该属性由 init 方法初始化。
- 第 40–42 行:[GetCotisations] 方法仅返回第 11 行中的属性,该属性由 init 方法初始化。
13.1.5.2. HibernateTemplate 类的常用方法
我们将使用 HibernateTemplate 类的以下方法:
IList<T> Find<T>(string hql_query) | 执行 HQL 查询并返回类型为 T 的对象列表 |
IList<T> Find<T>(string hql_query, object[]) | 执行一个通过 ? 进行参数化的 HQL 查询。这些参数的值由对象数组提供。 |
IList<T> LoadAll<T>() | 返回所有类型为 T 的实体 |
还有其他一些有用的方法,虽然我们可能没有机会使用,但它们允许您检索、保存、更新和删除实体:
T Load<T>(object id) | 将主键为 id 的类型为 T 的实体添加到 NHibernate 会话中。 |
void SaveOrUpdate(object entity) | 根据实体对象是否具有主键(有主键则执行 UPDATE,无主键则执行 INSERT),对其进行插入(INSERT)或更新(UPDATE)。是否存在主键可通过实体配置文件中的 `unsaved-values` 属性进行配置。执行 `SaveOrUpdate` 操作后,该实体对象将处于 NHibernate 会话中。 |
void Delete(object entity) | 将实体对象从 NHibernate 会话中移除。 |
13.1.5.3. init 方法的实现
根据配置,[PamDaoSpringNHibernate] 类的 init 方法是在 Spring 实例化该类后执行的方法。其目的是将简化的员工身份信息(姓氏、名字、社会安全号)和缴费率缓存到本地。其代码如下所示。
[Transaction(ReadOnly = true)]
public void init() {
try {
// on récupère la liste simplifiée des employés
IList<object[]> lignes = HibernateTemplate.Find<object[]>("select e.SS,e.Nom,e.Prenom from Employe e");
// on la met dans un tableau
employes = new Employe[lignes.Count];
int i = 0;
foreach (object[] ligne in lignes) {
employes[i] = new Employe() { SS = ligne[0].ToString(), Nom = ligne[1].ToString(), Prenom = ligne[2].ToString() };
i++;
}
// on met les taux de cotisations dans un objet
cotisations = (HibernateTemplate.LoadAll<Cotisations>())[0];
} catch (Exception ex) {
// on transforme l'exception
throw new PamException(string.Format("Erreur d'accès à la BD : [{0}]", ex.ToString()), 43);
}
}
- 第 5 行:执行了一个 HQL 查询。该查询从所有 Employee 实体中检索 SS、LastName 和 FirstName 字段,并返回一个对象列表。如果我们使用 "select e from Employee e" 查询整个 Employee 记录,则会返回一个 Employee 类型的对象列表。
- 第 7–12 行:将该对象列表复制到一个 Employee 类型的对象数组中。
- 第 14 行:我们检索所有类型为 Contributions 的实体列表。我们知道该列表仅包含一个元素。因此,我们检索列表的第一个元素以获取贡献率。
- 第 7 行和第 14 行初始化了类的两个私有字段。
13.1.5.4. GetEmployee 方法的实现
GetEmployee 方法必须返回具有给定 SSN 的 Employee 实体。其代码如下所示:
[Transaction(ReadOnly = true)]
public Employe GetEmploye(string ss) {
IList<Employe> employés = null;
try {
// requête
employés = HibernateTemplate.Find<Employe>("select e from Employe e where e.SS=?", new object[]{ss});
} catch (Exception ex) {
// on transforme l'exception
throw new PamException(string.Format("Erreur d'accès à la BD lors de la demande de l'employé de n° ss [{0}] : [{1}]", ss, ex.ToString()), 41);
}
// a-t-on récupéré un employé ?
if (employés.Count == 0) {
// on signale le fait
throw new PamException(string.Format("L'employé de n° ss [{0}] n'existe pas", ss), 42);
} else {
return employés[0];
}
}
- 第 6 行:根据给定的社会安全号码检索员工列表
- 第 12 行:通常,如果该员工存在,我们应得到一个包含一个元素的列表
- 第 14 行:如果不存在,则抛出异常
- 第 16 行:若出现这种情况,则返回列表中的第一个员工
13.1.5.5. 结论
如果比较使用
- Spring / NHibernate 框架
- Spring / NHibernate 框架
,我们会发现第二种方案使代码更加简洁。
13.2. 测试 [DAO] 层
13.2.1. Visual Studio 项目
Visual Studio 项目此前已介绍过。在此重申:
![]() |
- 在 [1] 中,展示了整个项目
- 在 [2] 中,展示了该项目的各个类。[tests] 文件夹包含一个控制台测试 [Main.cs] 和一个单元测试 [NUnit.cs]。
- 在 [3] 中,编译程序 [Main.cs]。
![]() |
- 在 [4] 中,[NUnit.cs] 文件未生成。
- 该项目是一个控制台应用程序。正在执行的类是 [5] 中指定的类,即 [Main.cs] 文件中的类。
13.2.2. 控制台测试程序 [Main.cs]
测试程序 [Main.cs] 在以下架构下运行:
![]() |
它负责测试 [IPamDao] 接口的方法。一个基本示例如下:
using System;
using Pam.Dao.Entites;
using Pam.Dao.Service;
using Spring.Context.Support;
namespace Pam.Dao.Tests {
public class MainPamDaoTests {
public static void Main() {
try {
// layer instantiation [dao]
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// list of employee identities
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
Console.WriteLine(Employe.ToString());
}
// an employee with benefits
Console.WriteLine("------------------------------------");
Console.WriteLine(pamDao.GetEmploye("254104940426058"));
Console.WriteLine("------------------------------------");
// list of contributions
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
} catch (Exception ex) {
// exception display
Console.WriteLine(ex.ToString());
}
//break
Console.ReadLine();
}
}
}
- 第 11 行:我们向 Spring 请求 [dao] 层的引用。
- 第 13–15 行:测试 [IPamDao] 接口的 [GetAllEmployeeIDs] 方法
- 第 18 行:测试 [IPamDao] 接口的 [GetEmployee] 方法
- 第 21 行:测试 [IPamDao] 接口的 [GetCotisations] 方法
Spring、NHibernate 和 log4net 通过第 13.1.4.1 节中讨论的 [ App.config] 文件进行配置。
使用第 6.2 节所述的数据库运行代码,将产生以下控制台输出:
- 第1-2行:2名类型为[Employee]的员工,仅包含[SS, 姓, 名]信息
- 第4行:类型为[Employee]、社会保险号为[254104940426058]的员工
- 第 5 行:缴费率
13.2.3. 使用 NUnit 进行单元测试
接下来我们将进行 NUnit 单元测试。DAO 层的 Visual Studio 项目将演变为如下结构:
![]() |
- 在 [1] 中,测试程序 [NUnit.cs]
- 在 [2,3] 中,项目将生成一个名为 [pam-dao-spring-nhibernate.dll] 的 DLL
- 在 [4] 中,对 NUnit 框架 DLL 的引用:[nunit.framework.dll]
- 在 [5] 中,[Main.cs] 类将不会被包含在 [pam-dao-spring-nhibernate] DLL 中
- 在 [6] 中,[NUnit.cs] 类将被包含在 [pam-dao-spring-nhibernate] DLL 中
NUnit 测试类如下:
using System.Collections;
using NUnit.Framework;
using Pam.Dao.Service;
using Pam.Dao.Entites;
using Spring.Objects.Factory.Xml;
using Spring.Core.IO;
using Spring.Context.Support;
namespace Pam.Dao.Tests {
[TestFixture]
public class NunitPamDao : AssertionHelper {
// the [dao] layer to be tested
private IPamDao pamDao = null;
// manufacturer
public NunitPamDao() {
// layer instantiation [dao]
pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
}
// init
[SetUp]
public void Init() {
}
[Test]
public void GetAllIdentitesEmployes() {
// audit no. of employees
Expect(2, EqualTo(pamDao.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetCotisations() {
// checking contribution rates
Cotisations cotisations = pamDao.GetCotisations();
Expect(3.49, EqualTo(cotisations.CsgRds).Within(1E-06));
Expect(6.15, EqualTo(cotisations.Csgd).Within(1E-06));
Expect(9.39, EqualTo(cotisations.Secu).Within(1E-06));
Expect(7.88, EqualTo(cotisations.Retraite).Within(1E-06));
}
[Test]
public void GetEmployeIdemnites() {
// individual verification
Employe employe1 = pamDao.GetEmploye("254104940426058");
Employe employe2 = pamDao.GetEmploye("260124402111742");
Expect("Jouveinal", EqualTo(employe1.Nom));
Expect(2.1, EqualTo(employe1.Indemnites.BaseHeure).Within(1E-06));
Expect("Laverti", EqualTo(employe2.Nom));
Expect(1.93, EqualTo(employe2.Indemnites.BaseHeure).Within(1E-06));
}
[Test]
public void GetEmployeIdemnites2() {
// non-existent individual verification
bool erreur = false;
try {
Employe employe1 = pamDao.GetEmploye("xx");
} catch {
erreur = true;
}
Expect(erreur, True);
}
}
}
第 7.3.4 节中已讨论过该类。
项目生成会在 [bin/Release] 文件夹中创建 DLL [pam-dao-spring-nhibernate.dll]。
![]() |
我们使用版本为 2.4.6 的 [NUnit-Gui] 工具加载 DLL [pam-dao-spring-nhibernate.dll],并运行测试:

如上所示,测试已成功通过。
实践练习:
在一台机器上实现 [PamDaoSpringNHibernate] 类的测试。- 使用不同的配置文件 [Dao.xml] 来支持其他数据库管理系统(Firebird、MySQL、Postgres、SQL Server)
13.2.4. 生成 [dao] 层 DLL
一旦编写并测试了 [PamDaoNHibernate] 类,[dao] 层 DLL 将按以下方式生成:
![]() |
- [1],测试程序不包含在项目程序集中
- [2,3],项目配置
- [4],项目构建
- 生成的 DLL 位于 [bin/Release] 文件夹中 [5]。我们将它添加到 [lib] 文件夹中已有的 DLL 中 [6]:
![]() |
13.3. 业务层
让我们重新回顾一下 [SimuPaie] 应用程序的总体架构:
![]() |
我们现在假设 [dao] 层已经完成,并封装在 DLL [pam-dao-spring-nhibernate.dll] 中。接下来我们将重点关注 [business] 层。该层负责实现业务规则,在本例中即计算薪资的规则。
业务层的 Visual Studio 项目可能如下所示:
![]() |
- 在 [1] 中,整个项目由 [App.config] 和 [Dao.xml] 文件进行配置。[App.config] 文件与 [dao] 层项目 [pam-dao-spring-nhibernate] 中的内容完全一致。[Dao.xml] 文件也是如此,只是它额外声明了一个 ID 为 pammetier 的 Spring 对象。 该对象的声明与 [pam-metier-dao-nhibernate] 项目中的 [App.config] 文件完全一致。
- 在 [2] 中,[pam] 文件夹与 [pam-metier-dao-nhibernate] 项目的 [metier] 层中的内容完全相同
- 在 [3] 中,是该项目使用的引用。请注意前面讨论过的来自 [dao] 层的 [pam-dao-spring-nhibernate] DLL。
问题:构建上述 [pam-metier-dao-spring-nhibernate] 项目。将分别进行以下测试:
-
通过控制台程序 [Main.cs] 在控制台模式下进行
-
通过 NUnit 框架执行的单元测试 [NUnit.cs]
只需复制 [pam-metier-dao-nhibernate] 项目,然后修改需要更改的元素,即可构建新的 [pam-metier-dao-spring-nhibernate] 项目。
测试完成后,我们将生成 [business] 层 DLL,并将其命名为 [pam-metier-dao-spring-nhibernate]:
![]() |
- 在 [1] 中,NUnit 测试成功
- 在 [2] 中,项目生成的 DLL
我们将把 [business] 层 DLL 添加到 [lib] 文件夹中已有的 DLL 中 [3]:
![]() |
13.4. [web] 层
让我们重新审视 [SimuPaie] 应用程序的总体架构:
![]() |
我们假设 [dao] 和 [business] 层已经完成,并封装在 DLL 文件 [pam-dao-spring-nhibernate, pam-business-dao-spring-nhibernate] 中。接下来我们将描述 Web 层。
首先,通过简单复制 Web 项目文件夹 [pam-v7-3tier-nhibernate-multivues-multipages],即可获取 [web] 层的 Visual Web Developer 项目。随后将该项目重命名为 [pam-v9-3tier-spring-nhibernate-multivues-multipages]:
![]() |
新的 Web 项目 [pam-v9-3tier-spring-nhibernate-multivues-multipages] 与 [pam-v7-3tier-nhibernate-multivues-multipages] 项目在以下方面有所不同:
- 在[1]中,配置通过[Dao.xml]和[Web.config]文件进行。[Dao.xml]在[pam-v7]中并不存在,且[Web.config]文件必须包含Spring/NHibernate的配置,而在[pam-v7]中,它仅配置了NHibernate。
- 在 [2] 中,[dao] 和 [business] 层的 DLL 即是我们刚刚构建的那些。
[Dao.xml]文件是构建[business]层时使用的。而[Web.config]文件则来自[pam-v7],我们向其中添加了[dao]和[business]层[App.config]文件中的Spring/NHibernate配置。[pam-v9]的[Web.config]文件如下:
<configuration>
<configSections>
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
........
</sectionGroup>
<sectionGroup name="spring">
<section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
</sectionGroup>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<!-- spring configuration -->
<spring>
<parsers>
<parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
</parsers>
<context>
<resource uri="~/Dao.xml" />
</context>
</spring>
............. le reste est identique au fichier [Web.config] de [pam-v7]
第 7–11 行和第 16–23 行包含之前构建的 [dao] 和 [business] 层的 [App.config] 文件中的 Spring 配置,但有一处不同:在 [App.config] 文件中,第 17 行写法如下:
<resource uri="Dao.xml" />
配置如下:
![]() |
将 [Dao.xml] 文件复制到 Web 项目文件夹内的 [bin] 文件夹中。语法如下:
<resource uri="Dao.xml" />
,系统将尝试在运行 Web 应用程序的进程当前目录中查找 [Dao.xml] 文件。实际上,该目录并非正在执行的 Web 项目的 [bin] 文件夹。您必须写成:
<resource uri="~/Dao.xml" />
这样 [Dao.xml] 文件才会从正在执行的 Web 项目的 [bin] 文件夹中被查找。
问题:将此 Web 应用程序部署到一台机器上。
13.5. 结论
我们已经从以下架构:
![]() |
转变为以下架构:
![]() |
目标是利用 Spring 与 NHibernate 集成所提供的功能来实现 [DAO] 层。
我们发现,这:
- 影响了[DAO]层。该层的代码编写更为简单,但需要更复杂的Spring配置。
- 对[业务]和[Web]层的影响微乎其微
这再次印证了分层架构的优势。























