使用 NHibernate 和 Spring.NET 构建中间层组件






4.50/5 (20投票s)
使用 NHibernate 和 Spring.Net 构建高度可插拔的中间层组件。
更新于 2009 年 1 月 29 日
Spring.NET 近期变化很大,与本文使用的版本相比。因此,附件代码与当前 Spring.NET 版本不兼容。Spring.NET 项目经理 Mark Pollack 帮助我更新了本文的源代码。因此,建议将他的代码库作为示例,了解 Spring.NET 目前如何使用 NHibernate。这是 Mark 编写的代码库:此处。
引言
在职业生涯中,我花费了大量时间使用 Visual C++、Java,最近也使用了 .NET 技术。我曾使用过流行的 Spring 框架和 Java 中的 Hibernate。现在,我最近发现这两种技术在 .NET 中也可用。但 Java 中的 Spring 框架比 Spring.NET 功能更丰富。特别是,我喜欢 Spring 与 Hibernate 的集成(*spring.orm.Jar*)。这使得以面向对象的方式操作数据变得非常容易。
背景
与 Java 的 Spring 框架不同,Spring.Net 没有与任何 ORM 技术集成。我的意思是,找不到任何 ORM 集成。至少,到现在为止,我还没有发现任何。对于 Spring.Net,声明式事务划分是通过 COM+ 服务编写的。对于那些熟悉 Java Spring 框架的人来说,我认为他们不会愿意使用 COM+ 服务。编写专业软件时,COM+ 需要高级知识。此外,COM+ 要求包含服务组件的程序集具有强名称。
因此,我尝试实现这种集成,以便无需使用 COM+ 服务即可轻松编写业务层组件。
在实现集成代码时,我借鉴了 Java 的 Spring 框架(一个开源框架)的整个概念。在实现时,我尝试为类和接口保留相同的名称。但我修改了许多方法。我还改变了一些类的行为。因此,我不能声称我的实现是 Java Spring 框架的精确副本。我只是实现了 *Spring.Orm.dll* 作为 NHibernate 和 Spring.Net 之间的集成,就像 Java 世界中的 *spring.orm.jar* 一样。
文章目标
这是我的第一次投稿,请尽量避免非建设性的批评。我对反馈持完全开放的态度,我不自称无所不知,也不自称拥有实现各种目标的最佳方法。在接下来的系列文章中,我想演示一些利用 NHibernate 和 Spring.NET 的 n 层框架。
入门
在开始之前,我将假设读者对以下内容有一些基本了解:
您可能知道,NHibernate 可以通过配置与任何关系型数据库一起使用。在此场合,我使用了 Oracle 9i 数据库作为数据源。当然,您可以通过在 *App.Config* 文件中进行配置,将其更改为任何其他数据库。
从下一节开始,我们将使用 *Spring.Orm.dll* 构建中间层组件,您将看到它有多么简单。而且设计真正具有可插拔性和优雅性。
这是我们的服务器组件的抽象设计
分步指南
首先,启动 Visual Studio .NET。创建一个新的控制台应用程序。为您的业务平台命名。在本教程中,我使用的是“SpringClient”。
将以下程序集引用添加到本文提供的二进制分发版中:
- castle.dynamicproxy
- hashcodeprovider
- iesi.collections
- log4net
- nhibernate
- nunit.framework
- spring.aop
- spring.core
当然,还要添加对以下程序的引用:
- spring.orm
现在,向项目中添加一个 *App.Config* 文件。并写入以下内容:
<?xml version="1.0"encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context"
type ="Spring.Context.Support.ContextHandler, Spring.Core"/>
<section name="objects"
type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<section name="nhibernate"
type="System.Configuration.NameValueSectionHandler, System,
Version=1.0.5000.0,Culture=neutral,
PublicKeyToken=b77a5c561934e089" />
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<spring>
<context><resource uri="config://spring/objects"/></context>
<objects>
<!—WRITE YOUR SPRING CONFIGURATION HERE-->
</objects>
</spring>
<nhibernate>
<add key="hibernate.show_sql" value="true"/>
<add key="hibernate.connection.provider"
value="NHibernate.Connection.DriverConnectionProvider" />
<!-- <add key="hibernate.dialect"
value="NHibernate.Dialect.MsSql2000Dialect" />
<add key="hibernate.connection.driver_class"
value="NHibernate.Driver.SqlClientDriver" />
<add key="hibernate.connection.connection_string"
value="Server=localhost;initial
catalog=nhibernate;Integrated Security=SSPI" />
-->
<!-- This is the System.Data.OracleClient.dll
provider for Oracle from MS -->
<add key="hibernate.dialect"
value="NHibernate.Dialect.Oracle9Dialect" />
<add key="hibernate.connection.driver_class"
value="NHibernate.Driver.OracleClientDriver" />
<add key="hibernate.connection.connection_string"
value="Data Source=ihis;User ID=system;Password=manager;" />
</nhibernate>
<!-- This section contains the log4net configuration settings -->
<log4net>
<!-- Define some output appenders -->
<appender name="rollingFile"
type="log4net.Appender.ConsoleAppender,log4net" >
<param name="File" value="log.txt" />
<param name="AppendToFile" value="true" />
<param name="RollingStyle" value="Date" />
<param name="DatePattern" value="yyyy.MM.dd" />
<param name="StaticLogFileName" value="true" />
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern"
value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n" />
</layout>
</appender>
<!-- Setup the root Category, add the
appenders and set the default priority -->
<root>
<priority value="DEBUG" />
<appender-ref ref="rollingFile" />
</root>
</log4net>
</configuration>
太多 XML 了!吓到了吗?
不用怕。我来解释一下。在这个配置文件中,我们已经配置了 Spring 的 IOC 容器、NHibernate 以及用于日志记录的 Log4net。
在 XML 中找到注释“在此处编写您的 Spring 配置”。在该区域,您将配置由 Spring IOC 加载的对象。如果这对您来说太复杂,请不要放弃。继续,您将能够在不了解 IOC 细节的情况下进行操作。您需要更改数据库配置块中的 DSN(在此示例中,我使用了 DSN 名称“ihis”)、用户名和密码。
向项目中添加一个名为“*hibernate.cfg.xml*”的 XML 文件。NHibernate 将使用它。将此文件的生成操作设置为**嵌入式资源**。
将以下配置插入该文件:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.0">
<session-factory name="NHibernate.Test"/>
</hibernate-configuration>
我们的业务逻辑层将利用声明式事务划分的便利性。所以我们需要一个事务管理器。目前,我们使用 NHibernate 进行 ORM,并将使用 *Spring.Orm* 程序集中的 `Spring.Orm.Hibernate.HibernateTransactionManager` 作为事务管理器。
现在让我们配置事务管理器。将以下配置块插入我用注释标记的点:
<object id="myTransactionManager"
type="Spring.Orm.Hibernate.HibernateTransactionManager, Spring.Orm">
<property name="SessionFactory">
<ref object="mySessionFactory"/>
</property>
</object>
在本例中,我们暂时实现一个业务对象。我将其命名为 `Product`。因此,创建一个名为 `SpringClient.Utility.Product.UProductDTO` 的类。后缀 DTO 代表“数据传输对象”。插入以下代码:
using System;
namespace SpringClient.Utility.Product
{
///
///
/// The Data transfer object for the Product entity.
///
/// Moim Hossain
///
[Serializable()]
public class UProductDTO
{
///
/// Constructor Method
///
public UProductDTO()
{
}
///
/// The Version Number for the Entity.
/// Hibernate will use this version number for ensuring the atomicity
/// of single data object.
///
private int versionNumber;
///
/// Get or set the Data Version Number
///
public int VersionNumber
{
get
{
return versionNumber;
}
set
{
versionNumber = value;
}
}
///
/// The Name of the Product
///
private string name;
///
/// Get or set the name of the Product
///
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
///
/// The ID [PRIMATY KEY] for the product object
///
private long id;
///
/// Get or set the Product ID
///
public long ID
{
get
{
return id;
}
set
{
id = value;
}
}
}
}
现在,由于我们将把 `Product` 实例持久化到数据存储中,因此我们需要为该类提供 Hibernate 映射文件。在同一命名空间中添加一个 XML 文件,名为“*UProductDTO.hbm.xml*”。并将此 XML 资源的生成操作设置为嵌入式资源。Spring 框架始终建议“面向接口编程”的编码实践。我们也不例外。所以,让我们为 `Product` 编写一个数据访问对象接口:
using System;
namespace SpringClient.Utility.Product
{
///
/// The Interface for accessing the Data Store for the Product
/// The Suffix DAO is for Data Access Object.
///
/// This is a typical interface for
/// the Data Access Layer of an Application
///
/// Declares the CRUD for Product Object
///
/// Moim Hossain
///
public interface IUProductDAO
{
///
/// Create a Product.
///
/// The Product That will be Persisted
/// The Product The has saved to the Data Store.
/// Contains the ID (Generated by datastore)
UProductDTO Create(UProductDTO Product);
///
/// Retrive a Product from the Data Store by ID
///
/// The ID that will be searched into the Data store
/// The Retrived Data Object
UProductDTO Retrive( long ProductID );
///
/// Update a product Object
///
/// The Product Instance that should be Updated
void Update( UProductDTO Product );
///
/// Deletes a product
///
/// The product that will be Deleted
void Delete( UProductDTO Product );
///
/// Search for some object by matching a criteria
///
/// The Hibernate Query string
/// The parameter values for the query
/// The List of UProducts that has a match for the given query
UProductDTO[]FindProducts( string queryString ,
object[]Parameters );
}
}
现在我们将实现该接口
using System;
using System.Collections;
using Spring.Orm.Hibernate.Support;
namespace SpringClient.Utility.Product
{
///
/// The Implementation of the IUProductDAO.
///
/// This is a concrete Data Access Object for the Product object.
///
/// Implemented all the methods remains in DAO interface.
///
/// Inherited from for
/// the Data Acccess support provided by the Hibernate ORM.
///
/// Moim Hossain
///
public class UProductDAO : HibernateDaoSupport, IUProductDAO
{
///
/// Constructor
///
public UProductDAO()
{
}
#region IUProductDAO Members
///
/// Create a Product.
///
/// The Product That will be Persisted
/// The Product The has saved to the Data Store.
/// Contains the ID (Generated by datastore)
public UProductDTO Create(UProductDTO Product)
{
HibernateTemplate.Save( Product ) ;
return Product;
}
///
/// Retrive a Product from the Data Store by ID
///
/// The ID that will be searched into the Data store
/// The Retrived Data Object
public UProductDTO Retrive( long ProductID )
{
return HibernateTemplate.Load( typeof( UProductDTO ) ,
ProductID ) as UProductDTO;
}
///
/// Update a product Object
///
/// The Product Instance that should be Updated
public void Update( UProductDTO Product )
{
HibernateTemplate.Update( Product );
}
///
/// Deletes a product
///
/// The product that will be Deleted
public void Delete( UProductDTO Product )
{
HibernateTemplate.Delete( Product );
}
///
/// Search for some object by matching a criteria
///
/// The Hibernate Query string
/// The parameter values for the query
/// The List of UProducts that has a match for the given query
public UProductDTO[]FindProducts( string queryString ,
object[]Parameters )
{
IList resultList =
HibernateTemplate.Find( queryString ,
Parameters ) as IList;
if( null != resultList )
{
UProductDTO[]products =
new UProductDTO[resultList.Count];
resultList.CopyTo( products , 0 );
return products;
}
return null;
}
#endregion
}
}
请注意,我们的数据访问类继承自 `Spring.Orm.Hibernate.Support.HibernateDaoSupport`,以便通过 `HibernateTemplate` 轻松访问 NHibernate 方法。现在是时候编写业务逻辑层了。让我们为我们的产品业务逻辑层编写一个接口:
using System;
namespace SpringClient.Utility.Product
{
///
/// The Interface for the Product Service.
/// This is a interface for the Business Logic
/// Layer of Product Object.
///
/// Contains all the methods that are related
/// to the business issues of Product entity.
///
/// When Implemented, Concrete objects
/// will implement the Business Logic for the interaction
/// with product object.
///
/// Moim Hossain
///
public interface IUProductService
{
///
/// Get or set the Data Access Object for the Product object.
///
IUProductDAO ProductDAO
{
get;
set;
}
///
/// Save a Product
///
/// The Product to be persisted
/// Saved product
UProductDTO SaveProduct( UProductDTO Product );
///
/// Retrives a product from a data store
///
/// The ID of the product to be retrived
/// The retrived product
UProductDTO RetriveProduct( long ProductID );
///
/// Update a Product Instance
///
/// The Product to be Updated
void UpdateProduct( UProductDTO Product );
///
/// Deletes a product
///
/// The product to be deleted
void DeleteProduct( UProductDTO Product );
///
/// Search for some object by matching a criteria
///
/// The Hibernate Query string
/// The parameter values for the query
/// The List of UProducts that has a match for the given query
UProductDTO[]FindProducts( string queryString ,
object[]Parameters );
}
}
现在我将提供实现
using System;
namespace SpringClient.Utility.Product
{
///
/// The Concrete Business Logic layer for the product Object.
///
/// Implemented the inteface.
///
/// Invoke by the service class for the Product Entity.
///
/// Moim Hossain
///
public class UProductServiceImpl : IUProductService
{
///
/// Constructor
///
public UProductServiceImpl()
{
}
///
/// The Product DAO Interface
///
private IUProductDAO productDAO;
#region IUProductService Members
///
/// Get or set the Data Access Object for the Product object.
///
public IUProductDAO ProductDAO
{
get
{
return productDAO;
}
set
{
productDAO = value;
}
}
///
/// Save a Product
///
/// The Product to be persisted
/// Saved product
public UProductDTO SaveProduct(UProductDTO Product)
{
return productDAO.Create( Product );
}
///
/// Retrives a product from a data store
///
/// The ID of the product to be retrived
/// The retrived product
public UProductDTO RetriveProduct( long ProductID )
{
return productDAO.Retrive( ProductID );
}
///
/// Update a Product Instance
///
/// The Product to be Updated
public void UpdateProduct( UProductDTO Product )
{
productDAO.Update( Product );
}
///
/// Deletes a product
///
/// The product to be deleted
public void DeleteProduct( UProductDTO Product )
{
productDAO.Delete( Product );
}
///
/// Search for some object by matching a criteria
///
/// The Hibernate Query string
/// The parameter values for the query
/// The List of UProducts that has a match for the given query
public UProductDTO[]FindProducts( string queryString ,
object[]Parameters )
{
return productDAO.FindProducts( queryString , Parameters );
}
#endregion
}
}
请注意,`UProductServiceImpl` 不继承任何 Spring 特定类,但此类有能力在方法上传播事务。这是与 COM+ 的关键区别。在 COM+ 中,您的业务类必须继承 `ServicedComponent` 类。此时,我们已完成业务组件的开发。现在,我们将为我们的业务类配置 Spring 事务支持。将以下 XML 插入 *AppConfig.xml* 中,位于您之前插入的事务管理器配置块正下方:
<object id="mySessionFactory"
type="Spring.Orm.Hibernate.LocalSessionFactoryObject, Spring.Orm">
<property name="MappingResources">
<list>
<value>SpringClient.Utility.Product.UProductDTO, SpringClient</value>
</list>
</property>
<property name="ExportSchema" value="true"/>
</object>
这里我们正在构建 Session Factory 对象。这只是 NHibernate Session factory 的一个代理。`ExportSchema` 属性设置为 true
,以便在运行服务器时,将为您创建所需的数据库表。将其设置为 false
可阻止表创建。现在,添加以下 XML 片段:
<!--UProduct Hibernate+ Spring conf -->
<object id="UProductTarget"
type="SpringClient.Utility.Product.UProductServiceImpl, SpringClient">
<property name="ProductDAO">
<ref object="ProductDAO"/>
</property>
</object>
<object id="UProductService"
type="Spring.Transaction.Interceptor.
TransactionProxyFactoryObject, Spring.Orm">
<property name="TransactionManager">
<ref object="myTransactionManager">
</property>
<property name="Target">
<ref object="UProductTarget">
</property>
<property name="TransactionAttributes">
<name-values>
<add key="Save*" value="PROPAGATION_REQUIRES_NEW"/>
<add key="Update*" value="PROPAGATION_REQUIRED"/>
<add key="Delete*" value="PROPAGATION_REQUIRED"/>
</name-values>
</property>
</object>
这是什么?首先,我们为业务类配置了一个提供事务支持的代理。在这里,我们还指定了事务传播。我们向 Spring 指定,我们需要为“`Save`”方法提供“Requires New”传播,为“`Update`”和“`Delete`”方法提供“Required”传播模式。请注意,您可以使用正则表达式指定方法名称。
现在,是时候考虑如何建立客户端应用程序和业务类之间的通信了?这完全取决于您的选择。您可以选择 Web 服务、Remoting - 任何您喜欢的方式。为了简单起见,我使用了 .NET Remoting 实现了一个示例。因此,让我们编写一个用于公开 Remoting 对象的类。此类还充当 IOC 加载对象的 fachada。
using System;
using Spring.Context;
using Spring.Context.Support;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
namespace SpringClient.Service
{
///
/// A Service factory used to resolve
/// and provide the service for a service name.
/// This class is tightly coupled with Spring Ioc.
///
/// Responsible for Constructing
/// the Spring Context and retains a static instance
/// of the spring context.
///
/// Moim Hossain
///
public class ServiceFactory
{
///
/// preventing the Out side world from
/// creating an instance of this class.
///
private ServiceFactory()
{
}
///
/// Expose the Services
///
public static void ExposeServices( params Type[]services)
{
HttpServerChannel channel = new HttpServerChannel(8080);
ChannelServices.RegisterChannel( channel );
foreach( System.Type service in services )
{ // Iterate through all the services
System.Runtime.Remoting.RemotingConfiguration.
RegisterWellKnownServiceType(
service , "ProductService",
System.Runtime.Remoting.
WellKnownObjectMode.SingleCall
);
}
}
///
/// Provide the service object by the name.
///
/// The Service Name to be resolved
/// The Service Object
public static object ProvideService( string serviceName )
{
if( null == m_AppContext )
throw new ApplicationException(
"Spring not initialized yet.", null );
// return the service
return m_AppContext[serviceName];
}
///
/// The Spring Context reference
///
private static IApplicationContext m_AppContext = null;
///
/// Constructs the Spring Container
///
public static void BuildSpringContext( )
{
// Make this method work as Single-ton fashion.
if( null != m_AppContext ) return ;
// No need to Build again
try
{ // Get the Handler for the spring Context
m_AppContext = ContextRegistry.GetContext();
if( null != m_AppContext )
{ // Success !
System.Console.WriteLine("Spring" +
" Initialized Successfully.");
}
}
catch(Exception ex )
{
System.Console.WriteLine( "Failed to" +
" Initialize the Spring context." );
System.Diagnostics.Trace.WriteLine( ex.Message );
}
}
}
}
让我们编写一个服务类,它将业务访问接口暴露给外部世界
using System;
using System.Web.Services;
using SpringClient.Service;
namespace SpringClient.Utility.Product
{
///
/// This the class with which the client will interact.
/// For the Sake of Simplicity
/// I have made this class as a simple C# Class.
///
/// But when implemented in a enterprise
/// level software, this class will be implemented as a
/// Remoting Server, derived from .
/// Or, as a Web service contains some web method.
///
/// Moim Hossain
///
public class RpcUProductService : MarshalByRefObject
{
///
/// Constructor
///
public RpcUProductService()
{
}
///
/// The Service Name that will be used
/// to resolve the Business Service Interface.
///
private string SERVICE_NAME = "UProductService";
///
/// Save a product
///
/// The product to be saved.
/// The Saved product.
/// Thows if any error occured while
public UProductDTO SaveProduct( UProductDTO Product )
{
return ( ServiceFactory.ProvideService(SERVICE_NAME)
as IUProductService ).SaveProduct( Product );
}
///
/// Retrive a product by the id
///
/// The Id of the product
/// the product
/// Thows if any error occured while
public UProductDTO RetriveProduct( long ProductID )
{
return ( ServiceFactory.ProvideService(SERVICE_NAME)
as IUProductService ).RetriveProduct( ProductID );
}
///
/// Update a product
///
/// The product to be updated
/// Thows if any error occured while
public void UpdateProduct( UProductDTO Product )
{
( ServiceFactory.ProvideService(SERVICE_NAME)
as IUProductService ).UpdateProduct( Product );
}
///
/// Delete a product
///
/// The product to be deleted
/// Thows if any error occured while
public void DeleteProduct( UProductDTO Product )
{
( ServiceFactory.ProvideService(SERVICE_NAME)
as IUProductService ).DeleteProduct( Product );
}
///
/// Search for some object by matching a criteria.
///
/// This method is tightly coupled
/// with Hibernate specific query string.
/// But You can easily provide some
/// strong type method to encapsulate the
/// hibernate specific code inside your service layer.
///
/// The Hibernate Query string
/// The parameter values for the query
/// The List of UProducts that has a match for the given query
/// ///
///
/// // I am Searching Only those
/// // objects that matchs with a given ID
/// // For More Inforamtion about writing
/// // Hibernate Query string and about Hibernate
/// // please search at http://nhibernate.sourceforge.net/.
/// string QueryString = "select UProduct from" +
/// " UProduct in class UProductDTO where UProduct.ID = ?";
/// // The Given ID is 1.
/// object[]Parameters = new Object[]{ Convert.ToInt64(1) };
///
/// UProductDTO[]Results =
/// new RpcUProductService().FindProducts(
/// QueryString , Parameters ); ///
///
///
public UProductDTO[]FindProducts( string queryString ,
object[]parameters )
{
return ( ServiceFactory.ProvideService( SERVICE_NAME )
as IUProductService ).FindProducts( queryString,
parameters );
}
}
}
现在,将您应用程序的 Main 方法更改为:
using System;
using SpringClient.Service;
using SpringClient.Utility.Product;
namespace SpringClient
{
///
/// The Test Class
///
/// Moim Hossain
///
public class MainClass
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
// log4net.Config.XmlConfigurator.Configure();
ServiceFactory.BuildSpringContext();
ServiceFactory.ExposeServices( typeof(
SpringClient.Utility.Product.RpcUProductService) );
System.Console.WriteLine("Service Exported. " +
"Server is running.");
System.Console.ReadLine();
}
}
}
完成了。您已经使用声明式事务功能完成了业务层。在这里,我提供了我的项目结构的截图。
编写一个客户端来测试我们的服务器
让我们创建一个名为 RemoteClient 的 Windows 应用程序。为 Remoting 组件创建 Web 引用。从“项目”菜单中选择“添加 Web 引用”,然后在 URL 框中输入:*https://:8080/ProductService?WSDL*。我假设您在同一台机器上运行服务器程序。否则,请在 `localhost` 处插入服务器机器的 IP 地址。为此示例,我已将其命名为“ProductService”。
现在,您可以像这样调用您在产品服务中编写的方法:
try
{
RemoteClient.ProductService.RpcUProductServiceService rpc =
new RemoteClient.ProductService.RpcUProductServiceService();
UProductDTO product = new UProductDTO();
product.name = "MyProduct";
rpc.SaveProduct( product ); // Saved !!!
}
catch(Exception Ex )
{
Console.WriteLine( Ex.Message );
}
就这样。很简单,不是吗?
未来开发的关注点
Java 的 Spring 框架提供了一些我在此版本中未实现但更吸引人的功能,例如保存点管理、将 Hibernate 特定异常转换为 Spring 的通用数据访问异常,以及其他一些非常重要的功能。不过,我希望将来有机会实现这些部分。
结论
我已阅读并尝试在编写本文时遵循 Java Spring 框架的设计。但在许多方面,由于语言不一致和其他原因,我不得不打破常规。不过,感谢 Spring 框架团队。
为了执行业务组件,您需要包含 Spring 和 NHibernate 模块的程序集。虽然我已将这些程序集与本文一起提供,但您也可以从以下链接下载:
希望您喜欢这篇文章。我将非常感谢任何人的建议和提议。谢谢。