许多系统,特别是业务系统,它们的目的就是要管理一堆的信息。决策系统就是这样一个例子。它管理井位的信息,并使一些业务过程自动化。这个系统的用例大部分都是关于一些持久性存储的数据信息的访问。因此,开发该系统的很大一部分工作与数据存储的访问有关。对数据存储实现细节的一个改动就可能为整个项目带来巨大的影响。
使平台相关的持久性保持分离,不仅仅能够减少这种改变的影响,还能够在项目的开始阶段就有效地进行测试。因为这样做,在尚未选择持久性数据存储机制的时候就能够测试用例。
上图描述了<Handle Persistence>的用例描述。我们尽可能使其简化,只介绍系统中与数据存储访问相关的部分。
<Handle Persistence>的简要描述
扩展事件流:<Handle Persistence>
当需要从数据存储中访问数据时,在<执行事务>用例中的每一步都会执行扩展事件流。
备选事件流
A1处理数据存储的并发访问
如果要加入一个持久性机制,就必须把<Entity>实体类替换成一个能访问数据存储(例如关系型数据库)的类。这样的类被称为数据访问对象(DAO)。顾名思义,数据访问对象职责是使用一种特定的数据访问机制,来访问特定数据存储中的数据。DAO对调用者隐藏了所有关于数据存储的细节和机制,如果数据存储结构之后还需修改,那么就只需要修改DAO即可。
回顾一下,在最小化用例设计中<Entity>实体类使用的是静态数据结构,并且保存在内存中。但是在实际的系统中将使用真正的数据存储。因此,需要一个方法来决定需要哪个数据访问类,是最小<Entity>实体类,还是能访问真正关系型数据库的DAO?这个选择可以通过一个DAO工厂类来进行,这个类的作用是基于数据存储的类型返回一个正确的DAO类。
DAOFactory决定返回数据访问对象(DAO)实例类型的方法,通常是使用所执行的平台的环境变量。因此,更改环境变量,就可以配置使用哪一个数据访问类,不论是最小化用例设计的<Entity>类,还是访问真正的数据存储类,可能是SQL数据库、XML数据库或者其它数据库。
一般情况下,在访问<Entity>实例时,<Control>实例首先创建一个<Entity>实例,然后调用了<Entity>实例中的AccessData方法。为了引入DAOFactory模式,就要拦截创建<Entity>实例的调用,并用DAOFactory来替代。由DAOFactory决定是返回最小化用例设计<Entity>实例还是平台相关的DAO。
我们可以把<Entity>实例替换成<DataAccessObject>实例,但是还要设计这个<DataAccessObject>。现在,通常使用关系型数据库来实现持久性数据存储,开发人员会花费很多时间编写对象-关系映射(OR Mapping)的代码。因此,有许多可用的持久性框架并不奇怪,甚至还有自动生成访问关系型数据库代码的工具。但这些工具还只是以数据为中心视角,生成基本的CRUD方法。也有一些更高级的工具可以生成更多这样的代码,但还是需要处理用特定的数据维护问题。因此,总是需要补充一些额外的代码来完成这样的工作,于是可以把用例特定的操作代码放进用例持久性切片中。
我们可以把特定用例中的持久性处理放到实体持久切片中。实体持久切片可以当成一个模板,因此可以利用一些工具来生成基本的CRUD和其它方法。在决策系统中,我们产生了四个用例切片:最小化用例设计切片,实体-持久化模板切片,持久化切片以及用例持久化切片。
最小化用例设计切片。最小用例切片只包含用例的逻辑特性,不包含平台相关的持久性方面的内容。
实体-持久化切片。实体-持久化切片与之前讨论过的用例表示和用例分布式切片有着本质上的区别。每个实体都有一个实体-持久化切片。这是因为实体持久性处理是use case-generic的。
在决策系统中,我们将领域模型和数据库完全独立起来,让间接层完成领域对象和数据库表之间的映射。这个数据映射器处理数据库和领域模型之间所有的存取操作,并且允许双方都能独立变化。
如上图所示,Well类与Well表之间信息的移动是通过Well Mapper来完成的。Well对象是为查询Well表返回的每一行产生的一个实例。这有别于活动记录的形式,活动记录是让每个领域对象负责数据库的存取过程,即将领域逻辑也加入到类中。但这并不适合领域模型较为复杂的情况。诸如决策系统,随着领域模型的增大我们必须采用间接的方式,将SQL访问从领域逻辑里分离出来,并把它放在独立的访问块中。这样,每一个数据库表对应一个类。这些类为数据表建立了一个入口。应用程序的其它部分根本不需要了解任何与SQL有关的事情,而且很容易就能找到所有访问数据库的SQL。这样就使专攻数据库的开发者有了一个清晰的目标。事实上,并不是所有的开发者都充分理解SQL,应该把SQL的设立交给最擅长的角色数据库管理员,他们能够理解如何才能最好地调整和组织这些SQL以获取最佳的效能。在众多的O/RM开发工具中,我们选择了IBatisNet。一方面是因为决策系统的数据库是既已存在的,不方便自行改动。另一方面是因为它并非纯正的O/RM开发框架,并不是自行生成SQL语句,而是将SQL语句放置在XML语句中,由DBA来统一的书写和调整,增加了开发的灵活性。如下图所示:
所有对象与数据表之间的映射关系都是通过XML来描述的。Well Mapper则通过XML描述的映射关系完成对象与数据表之间的信息移动。
我们来看一下实际中的代码映射:
Well.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace DSS.Framework.DomainModel
{
/**//// <summary>
/// 井位信息
/// </summary>
[Serializable()]
public class Well
{
私有成员#region 私有成员
private int wellID;
private string name;
private string englishName;
private decimal x;
private decimal y;
private string type;
private string explorationType;
private string producationType;
private decimal welldepth;
private string state_Type;
private string exploration_Project_Code;
private string geography_Position;
private decimal designCoordinate_X;
private decimal designcoordinate_Y;
private string wellModel_Type;
private DateTime startDrill_Data;
private DateTime endDrill_Data;
private DateTime endWell_Data;
private decimal design_WellDepth;
private DateTime hand_WellData;
private string intention_Layer;
private string endDrill_Layer;
private string remark;
private int emphases_Well;
private string jb;
private int rwsID;
private decimal again_x;
private decimal again_y;
private int nd;
private string gzdymc;
private string jbgzmc;
private string wzcw;
#endregion
公共属性#region 公共属性
/**//// <summary>
/// 井序号
/// </summary>
public int WellID
{
get
{
return wellID;
}
set
{
wellID = value;
}
}
/**//// <summary>
/// 名称
/// </summary>
public string Name
{
get { return name; }
set { name = value; }
}
/**//// <summary>
/// 英文名称
/// </summary>
public string EnglishName
{
get { return englishName; }
set { englishName = value; }
}
/**//// <summary>
/// 坐标X
/// </summary>
public decimal X
{
get { return x; }
set { x = value; }
}
/**//// <summary>
/// 坐标Y
/// </summary>
public decimal Y
{
get { return y; }
set { y = value; }
}
public string Type
{
get { return type; }
set { type = value; }
}
public string ExplorationType
{
get { return explorationType; }
set { explorationType = value; }
}
public string ProducationType
{
get { return producationType; }
set { producationType = value; }
}
public decimal Welldepth
{
get { return welldepth; }
set { welldepth = value; }
}
//省略其它属性
#endregion
}
}
<?xml version="1.0" encoding="utf-8" ?>
<sqlMap namespace="LineItem" xmlns="http://ibatis.apache.org/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<alias>
<typeAlias alias="Well" type="DSS.Framework.DomainModel.Well,DSS.Framework" />
</alias>
<resultMaps>
<resultMap id="SelectAllResult" class="Well">
<result property="WellID" column="WellId" />
<!--省略其它属性与列的映射-->
</resultMap>
</resultMaps>
<statements>
<select id="Well_Select" resultMap="SelectAllResult">
select
*
from PUB_Well
</select>
</statements>
</sqlMap>
我们对关键的部分加以注释:
<typeAlias alias="Well" type="DSS.Framework.DomainModel.Well,DSS.Framework" />
程序集DSS.Framework.dll中的DSS.Framework.DomainModel.Well类,别名为Well。
<resultMap id="SelectAllResult" class="Well">
别名为Well的类,它与SQL的对应在<Statements>标识块中resultMap为” SelectAllResult”的部分。
<result property="WellID" column="WellId" />
Well对象的WellID属性,它的值对应着SQL语句结果中的WellId字段。
<select id="Well_Select" resultMap="SelectAllResult">
resultMap=” SelectAllResult”表示该SQL的结果对应着<resultMaps>块中, resultMap的id为SelectAllResult的类。
而” Well_Select”则是被Well Mapper类使用的。
public class WellMapper{
///省略
public IList FindAll(){
return Mapper().QueryForList("SelectAllResult", null);
}
//省略
}
如上图示,每产生一个领域模型对象,就要同时产生两个类:领域模型类(如Well)、领域模型映射器类(如WellMapper)。考虑到分布的问题,就会多产生一个接口,即领域模型映射器的接口(如IWellMapper)。而所做的操作也不过是CRUD而已。有鉴于此,我们将所有的领域模型映射器统一为一个类IBatisPersistence:
IBatisPersistence.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using DSS.Framework.Data;
using IBatisNet.DataMapper;
using IBatisNet.Common.Pagination;
namespace DSS.Server.Data
{
/**//// <summary>
/// IBaitsNet作为实现的持久化操作
/// </summary>
[Serializable]
public class IBatisPersistence:BaseSqlMapDao,IPersistence
{
public IBatisPersistence(SqlMapper sqlMap) : base(sqlMap) { }
IPersistence 成员#region IPersistence 成员
//public T Get<T>(int id) where T : class
//{
// return this.ExecuteQueryForObject(string.Format("Select{0}byId", typeof(T).Name), id) as T;
//}
//public T Get<T>(string id) where T : class
//{
// return this.ExecuteQueryForObject(string.Format("Select{0}byId", typeof(T).Name), id) as T;
//}
//public IList Get<T>(object obj) where T : class
//{
// return this.ExecuteQueryForList(string.Format("{0}_SelectbyObject", typeof(T).Name), obj);
//}
//public IList Select<T>() where T : class
//{
// return this.ExecuteQueryForList(string.Format("{0}_Select", typeof(T).Name), null);
//}
//public int Update<T>(T obj) where T : class
//{
// return (int)this.ExecuteUpdate(string.Format("{0}_Update", typeof(T).Name), obj);
//}
//public int Delete<T>(T obj) where T : class
//{
// return (int)this.ExecuteDelete(string.Format("{0}_Delete", typeof(T).Name), obj);
//}
//public void Insert<T>(T obj) where T : class
//{
// this.ExecuteInsert(string.Format("{0}_Insert", typeof(T).Name), obj);
//}
#endregion
IPersistence 成员#region IPersistence 成员
/**//// <summary>
/// 得到一个对象的实体
/// </summary>
/// <param name="type">对象的类型</param>
/// <param name="obj">获取该对象的查询依据</param>
/// <returns>对象</returns>
public object Get(Type type, object obj) {
return this.ExecuteQueryForObject(string.Format("{0}_Select", type.Name), obj);
}
/**//// <summary>
/// 获取一个对象集合
/// </summary>
/// <param name="type">对象的类型</param>
/// <returns>对象集合</returns>
public IList Select(Type type)
{
return this.ExecuteQueryForList(string.Format("{0}_Select", type.Name), null);
}
/**//// <summary>
/// 根据对象依据,获取一个对象集合
/// </summary>
/// <param name="type">对象的类型</param>
/// <param name="obj">查询依据</param>
/// <returns>对象集合</returns>
public IList Select(Type type, object obj) {
return this.ExecuteQueryForList(string.Format("{0}_SelectbyObject", type.Name), obj);
}
/**//// <summary>
/// 更新一个对象
/// </summary>
/// <param name="obj">对象</param>
/// <returns>受影响的行数</returns>
public int Update(object obj) {
return (int)this.ExecuteUpdate(string.Format("{0}_Update",obj.GetType().Name),obj);
}
/**//// <summary>
/// 删除一个对象
/// </summary>
/// <param name="obj">对象</param>
/// <returns>受影响的行数</returns>
public int Delete(object obj) {
return (int)this.ExecuteDelete(string.Format("{0}_Delete", obj.GetType().Name), obj);
}
/**//// <summary>
/// 插入一个对象
/// </summary>
/// <param name="obj">对象</param>
public void Insert(object obj) {
this.ExecuteInsert(string.Format("{0}_Insert", obj.GetType().Name), obj);
}
#endregion
}
}
事实上,IBatisPersistence最好的实现应该采用泛形来完成,但是因为目前的方法拦截器不支持泛型,所以采用Type类型来传参。
这样,提取IBatisPersistence的接口IPersistence。客户端的业务模块只需要引用这个接口。而IBatisPersistence做为基础服务直接部署在服务器端,这样节省了大量的代码工作量,同时也利用扩展和重构。