(一) 领域模型 vs 数据集
在Martin Flower的《企业应用架构模式》一书中提到了企业应用的业务逻辑的组织主要分为以下三种模式:面向过程的事务脚本、面向对象的领域模型以及面向数据集的开发模式。
微软.Net Framework中提供了大量的数据感知组件使得开发者可以使用RAD的组件快速开发基于DataSet的企业应用来。开发者只要在界面上摆放一些数据感知组件如DataGrod,设定同数据源的连接,以及对应的表和字段,一个简单的程序就搞定了。面向数据集的开发模式非常适合于中小型的企业应用程序开发,但是对于复杂的应用程序来说,使用这种开发方式随着业务逻辑复杂度的增加就会慢慢显得力不从心了。
首先,使用RAD和数据感知组件,就意味数据表现层同数据库表的紧偶合,每使用一个数据感知组件,都相当于将数据的显示视图偶合到了数据库的某个表或者某个字段上,任何对数据模型的改变都会导致对所有绑定到改动的表或字段的数据感知组件的修改。
另外,使用弱类型数据集中读取数据时,通常是根据字段名来获取字段的对象的值,如DataReader["Spell"].ToString(),该方法是以字符串来获得数据集中的字段值,这时编译器是无法帮助我们发现这种组件的绑定是否全部被修正了,即便使用的字段名称错误了,编译器仍然会认为代码没有错误,这样需要改变的组件同库表之间的绑定很难被全部发现,经常会有所遗漏,而由改动引起的错误通常只能是在运行时才能被发现,这就意味着要花更多的时间才能发现错误。而且如果测试时,错误的代码没有被测试所覆盖的话,则错误可能要到客户使用时才会被发现,这时造成的后果可能已经无法挽回了。而使用面向对象的领域模型时,对业务域对象的操作都是通过对象的属性或者方法来进行的,如AObj.XXX方法,对对象属性名称或者方法进行了改变的话,编译器会帮助我们找到所有对该对象的不正确的操作,这意味着可以更早地发现和解决bug。虽然在.Net平台中提出了强类型数据集的概念,它可以实现表间关联,编辑校验的数据集属性,从某种程度上弥补了弱类型数据集的弱点,但是同完全面向对象的领域模型相比,强类型数据集只能实现基本数据类型的属性,并且只能实现数据库一级的属性值约束,比如无法实现枚举类型的属性。而领域模型则不存在这样的问题。
同时,使用数据感知组件,意味着同数据库的特有特性的紧偶合,比如为了减少代码量和提高效率,经常需要使用一些数据库平台相关的特殊sql,或者将一些复杂的sql写成平台相关的存储过程,这样当向不同数据库平台移植时,需要重新编写大量的业务逻辑。而面向对象的开发方式一般基于面向对象的查询语言,具有一致性,更加容易实现数据库平台无关性,实现系统方便地迁移。
另外,基于数据集的开发模式难于应用继承、设计模式等面向对象的开发方法,并且数据集对象不能包含自定义的操作。而领域模型则没有这样的障碍,开发的系统更加直观和容易理解。
(二)面向对象的数据库开发框架NHibernate
近年来,越来越多的人认识到使用面向对象的企业应用开发框架来进行系统的开发有着诸多的好处。在项目设计阶段,使用UML建模语言设计业务域对象模型,从模型出发,定义业务域对象,运行时对业务域对象的属性进行操作,直接将业务域对象保存到数据库,或者从数据库加载,消除对面向数据集的Sql的依赖,这就是通常所说的OR Mapping,对象-关系映射方法。
在Java平台上,OR Mapping的开源框架的No.1就是Hibernate,Hibernate是一个轻量级的OR Mapping解决方案,一经推出就取得了巨大的成功,在刚刚发布的EJB3.0的草案中就吸收了大量的Hibernate中的特性。从2004年三月开始,SourceForge上发布了.Net版本的NHibernate的Alpha版本,目前NHibernate的开发进展非常顺利,平均每一个半月就会发布一个新的版本。
NHibernate具有以下特性:
对象持续性:能够管理.Net类到数据库表的映射,以对象的方式存取数据,支持复杂对象、复合对象,支持对象之间的关联,比如继承,聚合,关联。OR Mapping的定义都是基于XML,具有很好的扩展性和通用性。可以支持现有的数据库定义,很好地保护用户投资。
支持对象查询:提供了面向对象的查询语言(HQL和条件查询),可以根据条件查询复合对象以及对象集合。
支持事务:创建还必须支持悲观锁的事务,并提供了乐观锁的并发支持。
性能优化:允许用户使用定制的Sql来提高查询的性能,提供了多种SQL自动策略开关,使得框架生成的Sql语句具有非常优化的性能。提供了灵活的Cache缓冲机制,以及延迟加载,批量更新的策略,保证一般应用的性能不会低于相应的数据集应用。
数据库平台无关性:使用OR Mapping技术实现了数据库平台无关性,可以随时切换开发及数据库发布平台,方便移植。
NHibernate的体系结构示意图:
图中的Session对应于应用程序同持久层的一次对话,其中保存有必需的持久化对象的缓存,可以通过标识符查找持久对象。持久层同底层数据库之间的操作是通过ADO.Net来实现的。
(二)Hello NHibernate
接下来我们将来创建一个非常简单的NHibernate的应用,它类似于一个留言簿的功能,可以将用户输入的信息保存到数据库中。
准备工作
首先从SourceForge上下载NHibernate最新版,本文基于NHibernate
接下来,创建一个在MS Sql Server2000上创建一个NHibernate的数据库。有了数据库,我们需要写app.config配置文件告诉NHibernate到什么地方去找数据库。下面是配置文件内容:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b
</configSections>
<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=" Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=NHibernate;Data Source=localhost"
/>
</nhibernate>
</configuration>
配置文件中的参数具有下面的含义:
参数
|
值
|
hibernate.show_sql
|
True表示向控制台输出运行中产生的Sql,用于调试目的
|
hibernate.connection.provider
|
表示使用指定的类来提供数据库连接缓冲池。
|
hibernate.dialect
|
表示NHibernate方言的类名,可以让NHibernate使用某些特定数据库平台的特性,目前NHibernate支持Sql Server, Oracle, Mysql, Firebird, Sybase, PostgreSql等数据库方言。
|
hibernate.connection.driver_class
|
Ado.Net的驱动类,支持SqlServer, Oracle, Mysql,OleDb, ODBC,Firebird等驱动。
|
hibernate.connection.connection_string
|
对应于Ado.Net的连接串。
|
业务域模型
根据需求,我们首先来设计业务域模型,模型非常简单,只有两个属性,其中Id唯一标识一个类的实例,相当于类的主键,Text表示用户输入的信息,示意图如下:
根据业务域模型,创建对应的.Net类的定义,代码如下:
public class Message
{
private int _id;
public int Id
{
get { return _id; }
set { _id = value; }
}
private string _Text;
public string Text
{
get { return _Text; }
set { _Text = value; }
}
public Message()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
public override string ToString()
{
return _Text;
}
}
Message类除了定义了必需的两个属性外,还重载了基类的ToString方法,这是为了便于在界面中进行显示。有了类定义,我们还需要创建存储Message对象的数据库表,下面就是同Message对象一一对应的数据库表Messages的DbSchema脚本:
CREATE TABLE [dbo].[messages] (
[MsgId] [int] IDENTITY (1, 1) NOT NULL ,
[Text] [varchar] (200) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
接下来,我们要编写OR Mapping 映射的配置文件Message.hbm.xml来指定Message对象同数据库表的映射关系,下面是创建的映射文件内容:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="HelloNHibernate.Message, HelloNHibernate" table="messages">
<id name="Id" column="MsgId" type="Int32">
<generator class="identity" />
</id>
<property name="Text" type="String" length="200"/>
</class>
</hibernate-mapping>
Xml文件中的Class元素的name属性指定了要进行映射的类的名称为HelloNHibernate.Message,同时这个类需要从HelloNHibernate assembly中进行加载, table属性指定了Message类在数据库中进行持久存储的库表名称为messages。而Id元素用于声明类标识属性对应于数据库表的主键字段的映射关系,其中name属性标识类的标识属性名称,column属性标识messages表的主键字段的名称,type是标识字段的数据类型,而内嵌的generator元素则指明主键字段唯一值的生成方法,这里identity表示NHibernate使用Sql Server数据库本身提供的自增加字段的特性来保证键值唯一。如果我们想自己给Id属性赋值来保证唯一性的话,可以设定class属性为assigned,这样NHibernate将不会自动生成键值。
定义完文件之后,别忘了在VS.Net Studio中将Message.hbm.xml的生成操作属性修改为嵌入的资源。还要注意的是,如果每次大家修改了hbm.xml文件,一定要使用“重新生成”命令来编译项目,否则VS.Net不会重新编译整个项目。
展示层设计
展示层的界面设计非常简单,在界面上放置一个ListBox用于显示消息列表,一个TextBox用于输入消息,三个Button分别用于查询消息列表、添加消息和删除消息,界面示意图如下:
实现业务逻辑
查询消息列表
在系统每次加载时,我们需要获取信息列表,下面就是加载信息列表的代码:
private void HelloForm_Load(object sender, System.EventArgs e)
{
QueryMessages();
}
private ISessionFactory factory;
private void QueryMessages()
{
//查询信息列表
Configuration cfg = new Configuration();
cfg.AddAssembly("HelloNHibernate");
factory = cfg.BuildSessionFactory();
ISession session = factory.OpenSession();
IList messages = session.CreateCriteria(typeof(Message)).List();
this.lbMsg.Items.Clear();
foreach(Message msg in messages)
this.lbMsg.Items.Add(msg);
session.Close();
}
在QueryMessages方法中,我们首先创建一个Configuration类,然后调用AddAssembly方法利用反射方法将装配件中定义的所有以.hbm.xml命名的Mapping文件加载到系统中,而BuildSessionFactory方法则利用app.config和hbm.xml配置文件中的定义创建一个SessionFactory,然后用SessionFactory工厂类创建一个Session会话类,通过调用会话类的条件查询方法CreateCriteria返回所有类型为 Message的对象列表。最后,边历列表将消息对象添加到列表框中,并关闭Session,释放ADO.Net的连接对象。
添加消息
添加消息稍微有一些不同的是,每次添加时我们都启动一个事务,保证添加消息操作的原子性,代码如下:
private void btnAdd_Click(object sender, System.EventArgs e)
{
//添加消息
if (this.tbMsg.Text.Trim()==String.Empty)
{
MessageBox.Show("消息输入框不能为空!");
return;
}
Message msg=new Message();
msg.Text=this.tbMsg.Text.Trim();
ISession session=factory.OpenSession();
ITransaction transaction = session.BeginTransaction();
session.Save(msg);
transaction.Commit();
session.Close();
this.lbMsg.Items.Add(msg);
}
运行后,添加几条记录,用Sql Server可以察看到数据库中的数据示意入下:
删除消息
删除消息的操作同添加比较类似,代码示意如下:
private void btnDel_Click(object sender, System.EventArgs e)
{
//删除消息
if (this.lbMsg.SelectedIndex==-1)
{
MessageBox.Show("请选中要删除的消息!");
return;
}
Message msg=(Message)lbMsg.Items[lbMsg.SelectedIndex];
ISession session=factory.OpenSession();
ITransaction transaction = session.BeginTransaction();
session.Delete(msg);
transaction.Commit();
session.Close();
this.lbMsg.Items.RemoveAt(lbMsg.SelectedIndex);
}
完成上面的程序后,大家会发现整个过程中我们没有写一条Sql语句就完成了添加、删除、查询数据的功能,所有的Sql语句都由NHibernate在后台为我们完成了,要想察看NHibernate生成的Sql语句的话,我们可以启动Sql Server的事件探查器来监视程序运行过程中的Sql,如下图示意:
总结
在本文中,我们演示了如何利用NHibernate,在不使用Sql的情况下实例化一个对象,修改属性,并将其保存到数据库中。在后续文章中,我还将同大家一起探索如何映射对象的关联,如一对多,多对多,继承、聚集,以及面向对象的条件查询和HQL语言等内容。