Skip to content

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. 结论

如果比较使用

  1. Spring / NHibernate 框架
  2. 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] 方法

SpringNHibernatelog4net 通过第 13.1.4.1 节中讨论的 [ App.config] 文件进行配置。

使用第 6.2 节所述的数据库运行代码,将产生以下控制台输出:

1
2
3
4
5
6
[254104940426058,Jouveinal,Marie,,,,]
[260124402111742,Laverti,Justine,,,,]
------------------------------------
[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203,[2, 2,1, 2,1, 3,1, 15]]
------------------------------------
[3,49,6,15,9,39,7,88]
  • 第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],并运行测试:

Image

如上所示,测试已成功通过。

实践练习


  • 在一台机器上实现 [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]层的影响微乎其微

这再次印证了分层架构的优势。