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)和 [metier] 这两层无需修改。这正是 Spring 集成分层架构的主要优势。
接下来,我们将通过注释功能性解决方案的代码,构建 [dao] 层与 [Spring / NHibernate] 层。 我们不会详尽介绍 [Spring / Nhibernate] 框架的所有配置或使用方式。读者可参考 Spring.NET [http://www.springframework.net/documentation.html](2010 年 6 月)的文档,将所提出的解决方案调整以适应自身的问题。
构建 [dao] 和 [metier] 层所采用的方法,与第 7 节中描述的第 3 版一致。而构建 [présentation] 层所采用的方法,则与第 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] 中,包含项目的各个类
- 在 [entites] 文件夹中,我们找到了在 [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:对应 ORM NHibernate
- MySql.Data:SGBD 的驱动程序 ADO.NET MySQL
- 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] 层所需的实体(对象)已汇总在项目中的 [entites] [1] 文件夹内。 这些实体与项目 [pam-dao-nhibernate] 中的实体基本一致,仅在配置文件 NHibernate 中存在细微差异。以文件 [Employe.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 属性表明文件 [Employe.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>
<!-- 配置部分 -->
<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 配置 -->
<spring>
<parsers>
<parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
</parsers>
<context>
<resource uri="Dao.xml" />
</context>
</spring>
<!-- 本部分包含 log4net 配置设置 -->
<!-- NOTE IMPORTANTE:日志默认处于非活动状态。需通过程序将其启用
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>
<!-- 将默认日志级别设置为 DEBUG -->
<root>
<level value="DEBUG" />
<appender-ref ref="ConsoleAppender" />
</root>
<!-- 为 Spring 设置日志记录。Spring 中的日志器名称对应于命名空间 -->
<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 / NHibernate 的配置(即声明特定 Spring 对象)将在此文件中找到。
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">
<!-- 由主应用上下文配置文件引用 -->
<description>
Application Spring / NHibernate
</description>
<!-- 数据库和 NHibernate 配置 -->
<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>
<!-- 事务管理器 -->
<object id="transactionManager"
type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate12">
<property name="DbProvider" ref="DbProvider"/>
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
</object>
<!-- Hibernate 模板 -->
<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>
<!-- 数据访问对象 -->
<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,此处为 SGBD 的提供程序 MySQL。 这意味着项目引用中必须包含 DLL 和 [Mysql.Data]。
- 数据库连接字符串(服务器、数据库名、连接所有者及其密码)
- 第 15-29 行配置了 SessionFactory 的 NHibernate,该对象用于获取 NHibernate 会话。 需要提醒的是,所有数据库操作均在 NHibernate 会话内部进行。 第15行可见,SessionFactory由位于DLL中的Spring类Spring.Data.NHibernate.LocalSessionFactoryObject实现。
- 第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 行定义了事务管理器。同样,该管理器是 DLL 中的一个 Spring 类。 该处理器需要知道数据库连接参数(第 34 行)以及 SessionFactory 的 NHibernate(第 35 行)。
- 第39-43行定义了HibernateTemplate类的属性,该类同样是Spring类。该类将在实现[dao]层的类中作为辅助类使用。它简化了与NHibernate对象的交互。 该类具有一些需要初始化的属性:
- 第40行:SessionFactory 来自 NHibernate
- 第41行:属性TemplateFlushMode用于设置持久化上下文NHibernate与数据库的同步模式。模式Auto将触发同步:
- 事务结束时
- 在执行 SELECT 操作之前
- 第 42 行:HQL 查询(Hibernate 查询语言)将被缓存。这可能带来性能提升。
- 第 46-48 行定义了 [dao] 层的实现类
- 第 46 行:[dao] 层将由 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 {
// 所有员工身份列表
Employe[] GetAllIdentitesEmployes();
// 特定员工及其津贴
Employe GetEmploye(string ss);
// 所有缴费的列表
Cotisations GetCotisations();
}
}
- 第 1 行:导入 [dao] 层实体的命名空间。
- 第 3 行:[dao] 层位于 [Pam.Dao.Service] 命名空间中。 命名空间 [Pam.Dao.Entites] 中的元素可以创建多个实例。命名空间 [Pam.Dao.Service] 中的元素仅创建一个实例(单例)。这就是选择这些命名空间名称的依据。
- 第 4 行:该接口名为 [IPamDao]。它定义了三个方法:
- 第 6 行,[GetAllIdentitesEmployes] 返回一个 [Employe] 类型的对象数组,该数组以简化形式(姓、名、SS)表示保育员列表。
- 第 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 Cotisations cotisations;
private Employe[] employes;
// 初始化
[Transaction(ReadOnly = true)]
public void init() {
...
}
// 删除对象
public void destroy() {
if (HibernateTemplate.SessionFactory != null) {
HibernateTemplate.SessionFactory.Close();
}
}
// 所有员工身份信息列表
public Employe[] GetAllIdentitesEmployes() {
return employes;
}
// 特定员工及其津贴
[Transaction(ReadOnly = true)]
public Employe GetEmploye(string ss) {
....
}
// 缴费清单
public Cotisations GetCotisations() {
return cotisations;
}
}
}
- 第 9 行:类 [PamDaoSpringNHibernate] 正确实现了 [dao] [IPamDao] 层的接口。它还继承自 Spring 类 [HibernateDaoSupport]。 该类有一个 [HibernateTemplate] 属性,该属性由已完成的 Spring 配置(下文第 2 行)初始化:
<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 会自动对事务进行 Rollback 注解。该注解消除了在方法内部管理事务的必要性。
- 第 16 行:类实例化完成后,Spring 会立即执行 init 方法。我们将看到,该方法的目的是初始化第 11 行和第 12 行中的私有字段。该方法将在事务中执行(第 15 行)。
- 接口 [IPamDao] 的方法分别在第 28、35 和 40 行实现。
- 第28-30行:方法[GetAllIdentitesEmployes]仅负责将第12行的属性(由方法init初始化)返回。
- 第40-42行:方法[GetCotisations]仅将第11行的属性设置为由方法init初始化的值。
13.1.5.2. HibernateTemplate 类的实用方法
我们将使用 HibernateTemplate 类的以下方法:
IList<T> Find<T>(string requete_hql) | 执行查询 HQL 并返回一个类型为 T 的对象列表 |
IList<T> Find<T>(string requete_hql, object[]) | 执行由 ? 参数化定义的查询 HQL。这些参数的值由对象数组提供。 |
IList<T> LoadAll<T>() | 返回所有类型为 T 的实体 |
还有其他一些有用的方法,虽然我们没有机会使用,但它们可以用于检索、保存、更新和删除实体:
T Load<T>(object id) | 将主键为 id 的类型 T 实体放入会话 NHibernate 中。 |
void SaveOrUpdate(object 实体) | 根据对象 entité 是否具有主键 (UPDATE),分别插入 (INSERT) 或更新 (UPDATE) 该对象 (INSERT)。是否存在主键可通过实体配置文件中的属性 unsaved-values 进行配置。 在执行操作 SaveOrUpdate 之后,对象 entité 位于会话 NHibernate 中。 |
void Delete(object 实体) | 从会话 NHibernate 中删除对象 entité。 |
13.1.5.3. init 方法的实现
类 [PamDaoSpringNHibernate] 中的方法 init 根据配置,是在 Spring 实例化该类后执行的方法。 该方法旨在将员工的简化身份信息(姓、名、SS)和缴费率缓存到本地。其代码可能如下所示。
[Transaction(ReadOnly = true)]
public void init() {
try {
// 获取员工简化列表
IList<object[]> lignes = HibernateTemplate.Find<object[]>("select e.SS,e.Nom,e.Prenom from Employe e");
// 将其放入表格中
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++;
}
// 将缴费率放入对象中
cotisations = (HibernateTemplate.LoadAll<Cotisations>())[0];
} catch (Exception ex) {
// 处理异常
throw new PamException(string.Format("Erreur d'accès à la BD : [{0}]", ex.ToString()), 43);
}
}
- 第 5 行:执行 HQL 查询。该查询获取所有 Employé 实体的 SS、姓、名字段,并返回一个对象列表。 如果使用“select e from Employe e”的形式查询员工的完整信息,则会返回一个类型为 Employe 的对象列表。
- 第 7-12 行:将该对象列表复制到一个类型为 Employe 的对象数组中。
- 第 14 行:查询所有类型为 Cotisations 的实体列表。已知该列表仅包含一个元素。因此,我们获取列表中的第一个元素以获取缴费率。
- 第 7 行和第 14 行初始化了该类的两个私有字段。
13.1.5.4. 方法 GetEmploye 的实现
方法 GetEmploye 需返回具有给定编号 SS 的员工实体。其代码可能如下:
[Transaction(ReadOnly = true)]
public Employe GetEmploye(string ss) {
IList<Employe> employés = null;
try {
// 查询
employés = HibernateTemplate.Find<Employe>("select e from Employe e where e.SS=?", new object[]{ss});
} catch (Exception ex) {
// 处理异常
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);
}
// 是否已招募到一名员工?
if (employés.Count == 0) {
// 报告该情况
throw new PamException(string.Format("L'employé de n° ss [{0}] n'existe pas", ss), 42);
} else {
return employés[0];
}
}
- 第 6 行:获取具有给定编号 SS 的员工列表
- 第 12 行:通常如果该员工存在,应返回一个包含一个元素的列表
- 第14行:若不存在,则抛出异常
- 第 16 行:若存在,则返回列表中的首位员工
13.1.5.5. Conclusion
如果比较仅使用
- 仅使用 Spring 框架的情况
- 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 {
// 实例化 [dao] 层
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// 员工身份列表
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
Console.WriteLine(Employe.ToString());
}
// 一名员工及其津贴
Console.WriteLine("------------------------------------");
Console.WriteLine(pamDao.GetEmploye("254104940426058"));
Console.WriteLine("------------------------------------");
// 缴费清单
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
} catch (Exception ex) {
// 异常显示
Console.WriteLine(ex.ToString());
}
//暂停
Console.ReadLine();
}
}
}
- 第 11 行:向 Spring 请求 [dao] 层的引用。
- 第13-15行:测试接口[IPamDao]中的方法[GetAllIdentitesEmployes]
- 第 18 行:测试接口 [IPamDao] 中的方法 [GetEmploye]
- 第 21 行:测试接口 [IPamDao] 中的方法 [GetCotisations]
Spring、NHibernate 和 log4net 由第 13.1.4.1 节中讨论的文件 [App.config] 进行配置。
使用第 6.2 节所述的数据库进行执行,控制台输出如下:
- 第 1-2 行:2 名类型为 [Employe] 的员工,仅包含 [SS, Nom, Prenom] 的信息
- 第4行:类型为[Employe]的员工,其社会保险号为[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] 将不会包含在 DLL [pam-dao-spring-nhibernate]
- 在 [6] 中,类 [NUnit.cs] 将包含在 DLL [pam-dao-spring-nhibernate] 中
测试类 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 {
// 待测试的 [dao] 层
private IPamDao pamDao = null;
// 构造函数
public NunitPamDao() {
// [dao] 层的实例化
pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
}
// 初始化
[SetUp]
public void Init() {
}
[Test]
public void GetAllIdentitesEmployes() {
// 员工人数验证
Expect(2, EqualTo(pamDao.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetCotisations() {
// 缴费率核查
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() {
// 个人核验
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() {
// 不存在的个人验证
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] 配置文件,以调用其他 SGBD(Firebird、MySQL、Postgres、SQL 服务器)
13.2.4. 生成 ,以及DLL(来自[dao]层)
编写并测试完类 [PamDaoNHibernate] 后,将按以下方式从 [dao] 层生成 DLL:
![]() |
- [1],测试程序不包含在项目汇编中
- [2,3],项目配置
- [4],生成项目
- DLL 生成在 [bin/Release] [5] 文件夹中。 我们将它添加到已存在于文件夹 [lib] [6] 中的 DLL 中:
![]() |
13.3. 业务层
让我们回顾一下 [SimuPaie] 应用程序的总体架构:
![]() |
我们现在认为 [dao] 层已实现,并被封装在 DLL [pam-dao-spring-nhibernate.dll] 中。 现在我们关注 [metier] 层。该层负责实现业务规则,此处即工资计算规则。
业务层的 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] 中,项目使用的引用。请注意,DLL 和 [pam-dao-spring-nhibernate] 来自之前研究的 [dao] 层。
问题:构建上述项目 [pam-metier-dao-spring-nhibernate]。将分别通过以下方式进行测试:
-
通过控制台程序 [Main.cs] 在控制台模式下进行
-
通过由框架 NUnit 执行的单元测试 [NUnit.cs]
新建项目 [pam-metier-dao-spring-nhibernate] 只需复制项目 [pam-metier-dao-nhibernate],然后修改需要更改的元素即可。
测试完成后,将从 [metier] 层生成 DLL,并将其命名为 [pam-metier-dao-spring-nhibernate]:
![]() |
- 生成为 [1],测试 NUnit 成功
- 在 [2] 中,由项目生成的 DLL
将 [metier] 层中的 DLL 添加到 [lib] 文件夹中已存在的 DLL 中[3]:
![]() |
13.4. [web] 层
让我们回顾一下 [SimuPaie] 应用程序的总体架构:
![]() |
我们认为 [dao] 和 [métier] 层已构建完成,并封装在 DLL 和 [pam-dao-spring-nhibernate, pam-metier-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] 和 [metier] 层中的 DLL 正是我们刚刚构建的。
文件 [Dao.xml] 是构建图层 [metier] 时所使用的文件。文件 [Web.config] 是 [pam-v7] 的文件,其中添加了 Spring / NHibernate的配置,该配置原存在于[dao]和[metier]图层的[App.config]文件中。 [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配置 -->
<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 行包含 Spring 配置,与之前构建的图层 [dao] 和 [metier] 中的 [App.config] 文件中的配置相同,但有一处差异: 在文件 [App.config] 中,第 17 行写法如下:
<resource uri="Dao.xml" />
配置如下:
![]() |
文件 [Dao.xml] 被复制到 Web 项目文件夹中的 [bin] 文件夹内。语法如下:
<resource uri="Dao.xml" />
将从运行 Web 应用程序的进程当前目录中查找文件 [Dao.xml]。但该目录并非所执行 Web 项目文件夹中的 [bin] 文件夹。应写为:
<resource uri="~/Dao.xml" />
这样,系统才会从正在运行的 Web 项目文件夹中的 [bin] 文件夹内查找 [Dao.xml] 文件。
问题:在机器上部署此Web应用程序。
13.5. Conclusion
我们已从以下架构:
![]() |
关于建筑:
![]() |
此次任务是利用Spring对NHibernate的集成所提供的便利,来实现[dao]这一层。
我们发现这:
- 影响了 [dao] 层。该层的编写虽然更为简单,但需要更复杂的 Spring 配置。
- 对 [metier] 和 [web] 层产生了轻微影响
这再次印证了分层架构的优势。
























