NHibernate是一个基于.Net的针对关系型数据库的对象持久化(ORM)类库。NHibernate来源于非常优秀的基于Java的NHibernate关系型持久化工具。从数据库底NHibernate来持久化你的.Net 对象到关系型数据库。NHibernate为你处理这些,远胜于你不得不写SQL去从数据库存取对象。你的代码仅仅和对象关联,NHibernate自动产生SQL语句,并确保对象提交到正确的表和字段中去。
Spring.NET对NHibernate提供了很好的支持与封装。Spring.Data.NHibernate.Generic.Support和Spring.Data.NHibernate.Support下的HibernateDaoSupport是Spring.NET提供的数据库访问对象(DAO)的基类,两者的却别在于对泛型的支持程度。我们以Spring.Data.NHibernate.Generic.Support.HibernateDaoSupport为例,讲解Spring.NET整合NHibernate开发。
我归纳了一下,分为三个步骤:
一、实体对象的建立及配置
二、数据访问对象建立及配置
三、业务处理层建立及配置
首先让我们学习一下NHibernate的实体对象的映射:我建立两个实体“用户信息”和“公司信息”。图1所示。
图1
public class User
{
public virtual int? UserID { get; set; }
public virtual string UserName { get; set; }
public virtual int UserAge { get; set; }
public virtual bool UserSex { get; set; }
public virtual Company CurrentCompany { get; set; }
}
public class Company
{
public virtual int? CompanyID { get; set; }
public virtual string CompanyName { get; set; }
public virtual IList<User> UserList { get; set; }
}
NHibernate要求实体必须是带有无参构造函数和带有virtual修饰的属性。两个实体的关系是双向(一对多——多对一)映射关系。
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Model" namespace="Model">
< class name ="Model.User, Model" table ="UserInfo" >
< id name ="UserID" column ="UserID" type ="int" >
< generator class ="native" />
</ id >
< property name ="UserName" column ="UserName" type ="string" length ="50" not-null ="true" />
< property name ="UserAge" column ="UserAge" type ="int" />
< property name ="UserSex" column ="UserSex" type ="bool" />
< many-to-one name ="CurrentCompany" class ="Model.Company, Model" foreign-key ="FK_UserInfo_CompanyInfo" >
< column name ="CompanyID" not-null ="true" />
</ many-to-one >
</ class >
</ hibernate-mapping >
< hibernate-mapping xmlns ="urn:nhibernate-mapping-2.2" assembly ="Model" namespace ="Model" >
< class name ="Model.Company, Model" table ="CompanyInfo" >
< id name ="CompanyID" column ="CompanyID" type ="int" >
< generator class ="native" />
</ id >
< property name ="CompanyName" column ="Name" type ="string" not-null ="true" length ="50" />
< bag name ="UserList" inverse ="true" cascade ="all-delete-orphan" table ="UserInfo" >
< key column ="CompanyID" foreign-key ="FK_UserInfo_CompanyInfo" />
< one-to-many class ="Model.User, Model" />
</ bag >
</ class >
</ hibernate-mapping >
以上就是实体对象与数据的映射文件,提供的配置我不详细说明,请查看NHibernate的帮助手册。
接下来,我建立数据库访问对象(DAO)层。在这里我使用了泛型Repository模式。
public interface IRepository<T>
{
void Delete(T entity);
T Get( object id);
object Save(T entity);
void Update(T entity);
}
public class NHibernateRepository<T> : HibernateDaoSupport, IRepository<T>
{
public object Save(T entity)
{
return this.HibernateTemplate.Save(entity);
}
public T Get( object id)
{
return this.HibernateTemplate.Get<T>(id);
}
public void Update(T entity)
{
this.HibernateTemplate.Update(entity);
}
public void Delete(T entity)
{
this.HibernateTemplate.Delete(entity);
}
}
数据库访问对象我们可以让它继承于HibernateDaoSupport类,该类的HibernateTemplate属性我们可以通过Spring.NET从外部注入。
<?xml version="1.0" encoding="utf-8" ?>
< objects xmlns ="http://www.springframework.net"
xmlns:db ="http://www.springframework.net/database" >
<!-- 用以我们在其它的应用程序中,配置数据访问 -->
< object type ="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core" >
< property name ="ConfigSections" value ="databaseSettings" />
</ object >
<!-- 数据库和Nhibernate的相关配置 -->
< db:provider id ="DbProvider" provider ="SqlServer-1.1"
connectionString ="Server=${db.datasource};database=${db.database};uid=${db.user};pwd=${db.password};" />
<!-- SessionFactory对象,其中包括一些比较重要的属性 -->
< object id ="NHibernateSessionFactory" type ="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate21" >
< property name ="DbProvider" ref ="DbProvider" />
< property name ="MappingAssemblies" >
< list >
< value >Model </ value >
</ list >
</ property >
< property name ="HibernateProperties" >
< dictionary >
< entry key ="hibernate.connection.provider" value ="NHibernate.Connection.DriverConnectionProvider" />
< entry key ="dialect" value ="NHibernate.Dialect.MsSql2000Dialect" />
< entry key ="hibernate.connection.driver_class" value ="NHibernate.Driver.SqlClientDriver" />
< entry key ="use_outer_join" value ="true" />
< entry key ="show_sql" value ="false" />
<!-- 自动建表(反向映射) -->
< entry key ="hbm2ddl.auto" value ="update" />
< entry key ="adonet.batch_size" value ="10" />
< entry key ="command_timeout" value ="60" />
<!-- 显式启用二级缓存 -->
< entry key ="cache.use_second_level_cache" value ="true" />
<!-- 启动查询缓存 -->
< entry key ="cache.use_query_cache" value ="false" />
< entry key ="query.substitutions" value ="true 1, false 0, yes 'Y', no 'N" />
< entry key ="proxyfactory.factory_class" value ="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle" />
</ dictionary >
</ property >
< property name ="ExposeTransactionAwareSessionFactory" value ="true" />
</ object >
< 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 ="repository.user" type ="Repository.NHibernateRepository<Model.User>, Repository" >
< property name ="HibernateTemplate" ref ="HibernateTemplate" />
</ object >
< object id ="repository.company" type ="Repository.NHibernateRepository<Model.Company>, Repository" >
< property name ="HibernateTemplate" ref ="HibernateTemplate" />
</ object >
</ objects >
db:provider节点是数据的连接字符串配置,我们引入xmlns:db="http://www.springframework.net/database这项命名空间便可以使用它。其中provider属性为数据库提供者的名称。以下是provider的详细情况:
名称 | 介绍 |
| Microsoft SQL Server, provider V1.0.5.0 in framework .NET V1.1 |
| Microsoft SQL Server, provider V2.0.0.0 in framework .NET V2.0 |
| Microsoft SQL Server Compact Edition, provider V9.0.242.0 |
| Microsoft SQL Server Compact Edition, provider V3.5.1.0 |
| provider V1.0.5000.0 in framework .NET V1.1 |
| provider V2.0.0.0 in framework .NET V2.0 |
| Oracle, Microsoft provider V2.0.0.0 |
| Oracle, Oracle provider V2.102.2.20 |
| MySQL provider 1.0.10.1 |
| MySQL provider 1.0.9 |
| MySQL provider 5.0.7.0 |
| MySQL provider 5.0.8.1 |
| MySQL provider 5.1.2.2 |
| MySQL provider 5.1.2.2 |
| MySQL provider 5.2.3.0 |
| Postgresql provider 1.0.0.0 (and 1.0.0.1 - were build with same version info) |
| Postgresql provider 1.98.1.0 beta 1 |
| Postgresql provider 2.0.0.0 |
| IBM DB2 Data Provider 9.0.0 for .NET Framework 1.1 |
| IBM DB2 Data Provider 9.0.0 for .NET Framework 2.0 |
| IBM DB2 Data Provider 9.1.0 for .NET Framework 1.1 |
| IBM DB2 Data Provider 9.1.0 for .NET Framework 2. |
| SQLite provider 1.0.43 for .NET Framework 2.0 |
| SQLite provider 1.0.43 for .NET Framework 2.0 |
| Sybase ASE provider for ASE 12.x |
| Sybase ASE provider for ASE 15.x |
| Sybase ADO.NET 2.0 provider for ASE 12.x and 15.x |
| ODBC provider V1.0.5000.0 in framework .NET V1.1 |
| ODBC provider V2.0.0.0 in framework .NET V2 |
InterSystems.Data.CacheClient | Caché provider Version 2.0.0.1 in framework .NET V2 |
可以根据自己的数据库选择不同的提供者名称。connectionString属性为数据库的连接字符串,这里用${xxx}的方式来表示一个占位符,因为我们经常将Spring.NET的配置文件设置为“嵌入系统资源”,这样一来在程序编译后就不能够修改,所以我们就要在应用程序配置文件中填写连接字符串,而不是在Spring.NET的配置文件中填写。
<configuration>
< configSections >
< section name ="databaseSettings" type ="System.Configuration.NameValueSectionHandler" />
</ configSections >
<!-- 数据库连接字符串 -->
< databaseSettings >
< add key ="db.datasource" value ="." />
< add key ="db.user" value ="sa" />
< add key ="db.password" value ="" />
< add key ="db.database" value ="SpringNet_Lesson18" />
</ databaseSettings >
</ configuration >
NHibernate中的Session控制取决于SessionFactory,Spring.NET提供了LocalSessionFactoryObject类来统一管理SessionFactory。其中MappingAssemblies属性为实体程序集的名称,可以填写多个名称。HibernateProperties为NHibernate的配置,dialect属性为数据库的方言,因为是SQL server 2K数据库,所以使用NHibernate.Dialect.MsSql2000Dialect 。proxyfactory.factory_class属性为延迟加载的代理类驱动,在NHibernate 2.1版中必须配置。hbm2ddl.auto属性为反向建立映射表的配置,我们配置为update后,NHibernate会帮我们自动根据实体的结构生成数据库中的表。
接下来我们看一下业务处理层。
public interface IUserManager
{
void Delete(User entity);
User Get( object id);
object Save(User entity);
void Update(User entity);
IRepository<User> UserRepository { get; set; }
}
public class UserManager : IUserManager
{
public IRepository<User> UserRepository { get; set; }
public object Save(User entity)
{
return this.UserRepository.Save(entity);
}
public void Delete(User entity)
{
this.UserRepository.Delete(entity);
}
public User Get( object id)
{
return this.UserRepository.Get(id);
}
public void Update(User entity)
{
this.UserRepository.Update(entity);
}
}
public interface ICompanyManager
{
void Delete( object id);
Company Get( object id);
object Save(Company entity);
void Update(Company entity);
}
public class CompanyManager : ICompanyManager
{
public IRepository<Company> CompanyRepository { get; set; }
public object Save(Company entity)
{
return this.CompanyRepository.Save(entity);
}
public void Delete( object id)
{
this.CompanyRepository.Delete( this.Get(id));
}
public Company Get( object id)
{
return this.CompanyRepository.Get(id);
}
public void Update(Company entity)
{
Company company = this.Get(entity.CompanyID);
company.CompanyName = entity.CompanyName;
this.CompanyRepository.Update(company);
}
}
代码的编写我不仔细讲,我们主要学习一下相关的配置。
<?xml version="1.0" encoding="utf-8" ?>
< objects xmlns ="http://www.springframework.net" >
< object id ="transactionManager"
type ="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate21" >
< property name ="DbProvider" ref ="DbProvider" />
< property name ="SessionFactory" ref ="NHibernateSessionFactory" />
</ object >
< object id ="transactionInterceptor" type ="Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data" >
< property name ="TransactionManager" ref ="transactionManager" />
< property name ="TransactionAttributeSource" >
< object type ="Spring.Transaction.Interceptor.AttributesTransactionAttributeSource, Spring.Data" />
</ property >
</ object >
< object id ="BaseTransactionManager" type ="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data" abstract ="true" >
< property name ="PlatformTransactionManager" ref ="transactionManager" />
< property name ="TransactionAttributes" >
< name-values >
< add key ="Save*" value ="PROPAGATION_REQUIRED" />
< add key ="Set*" value ="PROPAGATION_REQUIRED" />
< add key ="Finish*" value ="PROPAGATION_REQUIRED" />
< add key ="Update*" value ="PROPAGATION_REQUIRED" />
< add key ="Delete*" value ="PROPAGATION_REQUIRED" />
< add key ="Add*" value ="PROPAGATION_REQUIRED" />
< add key ="Get*" value ="PROPAGATION_SUPPORTS,readOnly" />
< add key ="Find*" value ="PROPAGATION_SUPPORTS,readOnly" />
< add key ="Load*" value ="PROPAGATION_SUPPORTS,readOnly" />
< add key ="*" value ="PROPAGATION_REQUIRED" />
</ name-values >
</ property >
</ object >
< object id ="CompanyManager" parent ="BaseTransactionManager" >
< property name ="Target" >
< object type ="Manager.CompanyManager,Manager" >
< property name ="CompanyRepository" ref ="repository.company" />
</ object >
</ property >
</ object >
< object id ="UserManager" parent ="BaseTransactionManager" >
< property name ="Target" >
< object type ="Manager.UserManager,Manager" >
< property name ="UserRepository" ref ="repository.user" />
</ object >
</ property >
</ object >
</ objects >
我们在前几篇学过AOP拦截和事务代理。Spring.NET为NHibernate提供的事务代理是TransactionProxyFactoryObject。我们将改类的Target熟悉注入业务处理层的类,这样Spring.NET会为该类包装上事务。
最后我们写一个单元测试类,对业务层进行单元测试。
[TestFixture]
public class UserManagerTest
{
static log4net.ILog logger = log4net.LogManager.GetLogger( " Logger ");
[SetUp]
public void SetUp()
{
try
{
log4net.Config.XmlConfigurator.Configure();
IApplicationContext applicationContext = ContextRegistry.GetContext();
userManager = (IUserManager)applicationContext.GetObject( " UserManager ");
companyManager = (ICompanyManager)applicationContext.GetObject( " CompanyManager ");
}
catch (Exception ex)
{
logger.Error(ex);
throw ex;
}
}
private IUserManager userManager;
private ICompanyManager companyManager;
[Test]
public void Delete()
{
userManager.Delete(userManager.Get( 2));
}
[Test]
public void Get()
{
User user = userManager.Get( 1);
}
[Test]
public void Save()
{
User user = new User();
user.UserName = " 刘冬 ";
user.CurrentCompany = companyManager.Get( 1);
userManager.Save(user);
}
[Test]
public void Update()
{
User user = userManager.Get( 1);
user.UserName = " 刘冬冬 ";
userManager.Update(user);
}
}
[TestFixture]
public class CompanyManagerTest
{
static log4net.ILog logger = log4net.LogManager.GetLogger( " Logger ");
[SetUp]
public void SetUp()
{
try
{
log4net.Config.XmlConfigurator.Configure();
IApplicationContext applicationContext = ContextRegistry.GetContext();
companyManager = (ICompanyManager)applicationContext.GetObject( " CompanyManager ");
}
catch (Exception ex)
{
logger.Error(ex);
throw ex;
}
}
private ICompanyManager companyManager;
[Test]
public void Delete()
{
companyManager.Delete( 4);
}
[Test]
public void Get()
{
Company company = companyManager.Get( 1);
}
[Test]
public void Save()
{
Company company = new Company();
company.CompanyName = " 刘冬公司 ";
companyManager.Save(company);
}
[Test]
public void Update()
{
Company company = companyManager.Get( 1);
company.CompanyName = " 刘冬冬公司 ";
companyManager.Update(company);
}
}
配置文件:
<?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" />
< section name ="parsers" type ="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />
</ sectionGroup >
< section name ="databaseSettings" type ="System.Configuration.NameValueSectionHandler" />
< section name ="log4net" type ="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</ configSections >
< spring >
< parsers >
< parser type ="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
< parser type ="Spring.Transaction.Config.TxNamespaceParser, Spring.Data" />
</ parsers >
< context >
< resource uri ="assembly://Repository/Repository/Repository.xml" />
< resource uri ="assembly://Manager/Manager/Manager.xml" />
</ context >
</ spring >
< 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 ="INFO" />
</ logger >
</ log4net >
<!-- 数据库连接字符串 -->
< databaseSettings >
< add key ="db.datasource" value ="." />
< add key ="db.user" value ="sa" />
< add key ="db.password" value ="" />
< add key ="db.database" value ="SpringNet_Lesson18" />
</ databaseSettings >
</ configuration >
数据库我没有上传,因为空间的问题。我设置了自动建表的属性,可以用来自动创建数据库表结构。