使用Bold开发数据库应用
介绍
下面我要介绍的Bold for Delphi就是是一套优秀的基于UML模型驱动的面向对象的数据库开发框架,包括了几十个组件组件,以及1000个以上的类,可以用来轻松地实现信息模型设计及基于信息模型的的应用程序。
基础概念介绍
为了使大家对Bold for Delphi整个框架的使用有一个大概的了解,下面将演示如何用UML设计一个简单的模型并用Bold来完成,并包括如何用Bold快速实现一个简单的操作界面。
自打我和我老婆认识以后,就染上了她的臭毛病,比较喜欢乱花钱,没有节制,结果搞的自己常常是挣的不如花的多,老要借外债。后来痛定思痛,决定要对每月收支情况做预算,严格控制费用支出,为此写了还写了好多的财务小程序,下面要讲的这个例子程序就是一个常见的家庭小账本程序,它可以用来统计家庭中的收支情况,软件的功能要求如下:
•可以定义家庭中的各个人员的信息 。
•可以输入收支情况,并同消费的人员关联起来。
•给出一定时期内消费的情况统计,作为未来家庭预算的依据。
建立信息模型
在产品的需求分析阶段,我们首先要建立数据库程序的信息模型,一般来说信息模型主要是指基于ER图的实体关系模型,这是因为我们使用的数据库大部分都是关系型数据库,虽然有些数据库,比如Oracle有面向对象的特性,但不是很完善,一般很少使用。而关系型数据库有一个很大的问题就是无法直观的体现面向对象的思想,关系型的ER模型能够清晰地描述业务域的静态的数据视图,但你无法从模型获取实体的操作及其相互之间的交互。同时,也很难在关系型数据库中简单地实现继承、重载、多态等等面向对象的技术,因此现代数据库开发方法所提倡的面向对象的编程思想无法简单、清晰、平滑地映射为关系型数据库中的表结构。
统一建模语言(UML)是一种以可视化的方式建立软件系统框架,并进行文档化的语言。UML语言是对当今软件工程领域成熟设计实践的一个总结,并且已经被实践证明是可以成功地描述大型的复杂系统的。目前国内很多的大型公司已经开始在软件开发过程中使用UML作为一种标准的信息模型设计语言了。 Bold for Delphi就是基于UML的,它内置了一套自己的UML建模工具,当然我们也可以使用Rose或者ModelMaker来进行UML设计。
面向对象的UML类图则可以说是对ER模型的一个扩展,它对实体之间的关系以及相互之间的作用也进行了描述,ER模型只是对要进行保存的数据进行的模型化,而类图则包括了全部的类实体的属性以及它们的操作和相互作用,它可以使我们对业务域问题有一个更精确的视图,通过使用各种类图技术可以更容易地、也更快速地建立正确的软件系统。
基于Bold for Delphi的数据库开发革命性的一点就是允许我们直接把基于UML的类图映射为关系型数据库的存储,而无须手工的通过代码进行转换。要注意一点的是,Bold同其它建模工具如Together,ModelMaker不同,它生成框架代码时只使用了UML中的类图。而Together等可以利用UML图中的类图、协作图等其它UML元素来生成代码框架,但是Together不负责生成对象模型对应的关系数据库模型。
类模型
下面的这个类图就是我们的账本程序的一个简单类图。
图中显示了两个类,人员信息类‘Person',以及账目信息类AcctItem。人员类和账目类之间的连线描述了两个类之间的关系,关系包括一个标题PayAssoc揭示了两者之间的关系是支付的关系,每个属性‘PayPerson'和‘Pay',以及关系多重度因子‘1'和‘0..n'表明每个人可以完成多个账目的收支,而每个账目至少要有一个关联的人员。同时类图还描述了下面一些业务规则:
•一个人的信息要有名称。
•账目信息中包含收支金额大小,以及发生日期。
上面的类图如果使用关系型数据库来实现的话,需要建立主从表,并将人员和账目之间的关联约束通过应用程序代码强制一些运行逻辑来完成,这时通常要通过补充详细的文档来描述需要强制的业务逻辑,如果没有详细的设计文档,实现代码时就很容易遗漏某些重要的商业规则,同时这些文档在整个的数据库开发的生命周期里面都需要人来手工地维护,难免会出现文档和模型不匹配的错误。而且文档的工作量比较大,而程序员数量又相对不足的话,程序员会觉得既要写代码又要写文档,无形中增加了很多工作量,难免会有抵触情绪,这些都会影响工作的效率。
对于这样的问题,Bold则通过精确描述信息模型,无须详细规则描述文档可以将模型自动的转变为实现代码,商业规则在整个数据库开发生命周期内由Bold的类来维护,减少了文档的工作量和出错的可能。
建立示例程序
首先,我们要安装Bold for Delphi,Bold的一个月评估版可以从www.boldsoft.com获取,同时D7的架构版内置了Bold,这里我就不详细介绍申请和安装的过程了,安装好后Bold会在IDE的组件面板中添加很多组件,接下来我们就开始建立使用Bold的Delphi程序了:
1、在Delphi中选File|New Application创建一个新的应用程序。
2、保存窗体文件为MainForm.pas保存工程文件为CMoney.dpr。
3、添加一个数据模块,设定数据模块的名字为DmMoney。
4、将数据模块保存为CDataModule.pas。
为了使用Bold来建立系统的信息模型,要进行下列操作:
1、从Bold Handles 组件页上选择BoldModal(命名为bmMoney)、BoldSystemTypeInfoHandle(命名为bsthMoney)和BoldSystemHandle(命名为bshMoney)到数据模块中。
2、设定bsthMoney的BoldModal属性为bmMoney。
3、设定bshMoney的BoldSystemTypeInfoHandle 属性为bsthMoney。
其中BoldModel组件将被用来保存模型,即类、类的关系、约束以及类型等,这些信息将在设计时作为字符串保存到Delphi的窗体和数据模块文件中,在运行时Bold将执行一些模型的中间转换过程,将模型转化为BoldSystemTypeInfoHandle控件所使用的格式,并选择实现可持续性的机制。
在设计时储存在BoldModel组件中的信息模型可以被看做元数据,就象数据库的库表和字段结构一样的信息,而BoldSystemTypeInfoHandle组件则保存BoldSystemHandle所需要的运行时信息。这些信息是对UML模型的一种运行时的表达,这个组件是其他Bold组件的信息源。BoldSystemHandle组件则被用来表达整个系统的业务域元素,可以理解为对象空间。通过对象空间我们可以在运行时获得设计时元数据表达的对象的运行实例。目前用到的三个控件已经可以很好的应用在不需要保存数据的环境中了,但账目记录这类数据库程序必须要保存用户输入的信息,因此还需要添加支持数据可持久性的控件,这里为了快速演示的需要,我们使用XML文件作为存储介质,接下来要添加XML可持续控件到数据模块中:
1、从Bold Persistence组件页上选择BoldPersistenceHandleFileXML控件(命名为bphxMoeny)添加到数据模块中。
2、设定组件的BoldModel属性为bmMoney控件。
3、设定bshMoney组件的PersistenceHandle属性为bphxMoeny组件。
现在组件关系示意图如下:
BoldPersistenceHandleFileXML组件将使我们的程序可以使用XML文件来保存和读取对象,这是一个使用很方便的控件,特别是在快速原型设计期间。因为在原型设计期间,模型经常会被改动,而重新生成数据库表是很费时间的,而XML文件可以使我们非常快的变更我们的模型设计。当模型基本稳定后,可以去掉这个控件,转而切换为其他使用关系型数据库进行存储的可持续性控件,这样的开发方式可以使我们不需要改动整个程序就能很容易地改变数据持续层的存储策略。也就是前面所说的“数据库平台无关设计”。
除了前面的一些基本的属性设置外,我们还要设定下列控件属性:
|
组件 |
属性 |
值 |
说明 |
|
bsthMoney |
UseGeneratedCode |
false |
是否使用bold生成类代码,这里暂时先不使用,稍后我们会进一步介绍 |
|
bphxMoeny |
FileName |
Data.xml |
指定保存数据的xml文件名 |
|
bshMoney |
AutoActivate |
true |
告诉Bold控件在程序运行后马上打开xml文件用于数据存储。 |
建立模型
下面的步骤是建立我们的模型,Bold for Delphi内置了一个树形的UML建模工具(应该说Bold美中不足的一点就是没有提供象Visio和Rose那样基于拖放的模型设计界面),我们可以双击BoldModel(bmMoney)组件调出模型设计工具,bold UML模型编辑器(见下图)包含了应用程序模型信息、数据类型信息和关系数据库映射信息。
模型编辑器支持下列实体类型:
•Model: 模型,全部业务域实体集合。
•Package: 包,整个模型的一个子集所包含的实体,可以将大模型分解为小模型来减少系统复杂度。
•Class:类,类似于Delphi的类的概念(Delphi的类可以从UML的类来生成),但包含Object Pascal无法直接描述的类的信息和相互关系。Bold框架通过关联类和特殊的列表类封装了一些额外的功能使得我们可以很容易的处理复杂的类关系。
•Attribute: 属性,类似于Delphi中的property概念,然而在Bold中,这些属性可以在模型中直接保存而无需我们编写属性的Get,Set方法。
•Operation: 操作,等价于Delphi中的类的过程和函数。
•Association: 关联,代表了类之间的关系,关联可以使用类来表达,关联也可以有操作和属性,在Bold中建立关联的复杂工作同样可以由框架来实现,我们无须编写代码来完成。
•Role: 角色,代表关联同类的连接。
•Data Type: 表示模型所支持的不同数据类型,它可以被扩展以支持用户自定义的数据类型。
下图是不同实体类型在模型编辑器中是如何标识的。
所有的实体类型都可以通过编辑器的右键菜单来创建和修改属性,同时我们选中实体节点后,实体和全局的选项会显示在右侧的编辑器中,其中重要的有:
•Name: 模型的名称
•Unit name: 指定Bold根据模型生成Delphi类的pascal单元的名称(前提是我们选择生成代码,这里也可以选择不生成代码)。
添加业务域对象
设定根节点的名称为MoneyModel,然后设定New_ModelRoot节点名称为MoneyModelRoot,同时别忘了设定MoneyModel的Model root class属性为MoneyModelRoot。后面的所有类都将从MoneyModelRoot类派生。
在右键菜单中选New Class命令,创建新的类Person和AcctItem,设定它们的SuperClass属性为MoneyModalRoot。
添加属性
在模型编辑器的树视图中选择AcctItem类,使用右键菜单中的Add Attribute来添加Amount、HappenDate和Name属性,设置所有属性为Persistent类型,表示这些属性的值要保存到数据库中,设定属性的数据类型为Currency。编辑界面示意如下:
添加关联
选中MoneyModel节点,然后在右键菜单中选择New Association命令创建PayAssoc关联,命名关联的两端角色为Pay和Money。每个角色的设置如下:
•Pay: 选择AcctItem作为角色的类,关闭Embed checkbox并选择0..*作为多重度因子。
•PayPerson: 选择Person作为角色的类,设定多重度为1..1。
编辑界面示意如下:
通过上面一系列的操作,我们就完成了整个的建模工作,它同前面的UML模型是完全等价的。
添加用户界面
要想使一个应用程序可以跑起来,我们还必须做一些界面工作,在CMoney.pas 单元中添加BoldAFPDefault和CDataModule到uses部分,BoldAFPDefault使得Bold for Delphi可以自动生成Bold控件可调用的商业对象的编辑界,CDataModule单元则使Bold组件可以获知数据模块中的模型,从Bold Handles组件面板上拖动2个BoldListHandle控件到窗体上,然后从Bold Controls组件面板上拖放2个BoldNavigator和2个BoldGrid组件分别用于显示人员和账目信息列表,另外还要添加2个标准的Label控件来添加界面描述,界面示意图如下:
其中BoldListHandle组件就相当于在数据库开发中的DataSource数据源,两者的主要的区别是BoldListHandle中的数据是对象实例,而DataSource中的数据是数据库记录。而BoldGrid和BoldNavigator则对应于数据感知组件中的DbGrid和DbNavigator组件,区别就是DbGrid中编辑的是数据库记录,而BoldGrid中编辑的是对象属性。
在数据库开发中,需要将数据源同数据感知组件绑定,同样为了编辑对象数据,也要将BoldListHandle同对象感知组件BoldNavigator和BoldGrid进行绑定,设定BoldNavigator和BoldGrid的BoldHandle为相应的数据源,设定后的结果如下:
在普通数据库开发中,除了要有DataSource外,还要有同DataSource绑定的数据集DataSet,同样的,BoldListHandle对应于DataSource,也要同DataMoudle中的BoldModel进行绑定。而Bold中的BoldSystemHandle组件就对应于TDataBase,包含了对象模型中所有对象实例。因此设定两个BoldListHandle组件的RootHandle属性为DmMoney.bshMoney。这还不算完,因为现在BoldListHandle是要分别获得人员和账目的相关信息,而要指定对象DataBase中的对象DataSet,在Bold中指定不同的对象集合,需要使用OCL语言,双击BoldListHandle的Expression属性,会弹出一个OCL语言编辑器,如下图示意:
前面提到了OCL语言是一种标准的对象查询语言,这里先不详述,后面会进一步的介绍,这里为了获得全部人员信息的列表的话,需要输入Person.allInstances表达式,同样获得账目列表信息的话,需要设定BoldListHanlde的Expression属性为AcctItem.allInstances。
设定好BoldHandle的属性后,右击BoldGrid然后选择Create Default Columns命令,会创建对应于对象元数据的字段。最后还要在添加DataModule的OnDestroy事件处理函数:
procedure TDmMoney.DataModuleDestroy(Sender: TObject);
begin
//更新数据库
bshMoney.System.UpdateDatabase;
end;
上面语句确保运行时录入的信息将被保存到XML文件中。最后运行程序,输入一些信息的效果图示意如下:
Bold同传统数据库开发的区别
两者之间最重要的不同就是元数据,Borland的控件感知的是数据库表、字段的结构定义元数据,而Bold控件感知的是类的定义元数据。两者另一个重要的区别就是数据的同步的处理方式:在传统的数据库程序中,我们想同步不同网格中的数据的话,可以使用下列方法:
A) 多个查询
三个网格控件每个连接到不同的SQL数据源来获取数据, 每次更新任意网格的数据时,重新运行这些查询来更新,这要求我们进行大量代码编写,并且要仔细考虑数据集的状态,比如当前数据集是否处于编辑模式等。
B)过滤网格
通过使用单一查询,并借助于某些更为强大的第三方的带有内置的过滤功能的网格控件我们可以稍微简单一些的实现这个功能,但依赖于SQL表达式的查询会产生一个只读的数据集,同时对第三方控件的依赖也是不好的问题。
而在Bold中,我们可以随意的修改任何对象的值,所有的变动会自动的同步更新,Bold for Delphi还支持各种方法来确保同步可以在网络上的不同机器间实现。由于篇幅限制,这种功能将不在本文中讨论。
内建的Bold对象编辑界面
双击BoldGrid中左侧的箭头,或者记录可以激活对象编辑对话框,对象编辑对话框可以同时启动多个,如下图所示意:
在BoldGrid中,我们可以使用ctrl+鼠标点击来多选对象,也可以点击网格左上角来选中所有的对象。但是默认的BoldGrid对象编辑器不支持人员和账目对象的关联的显示和编辑,但弹出的对象编辑器则支持这些关联,上图框起来的箭头,可以用来拖放对象,将哈巴狗拖动到PayPerson的位置,就可以建立支付人同账目对象之间的关联了。
还有一点要注意的是弹出式对象编辑器并不是同Grid同步绑定的,因此我们可以打开多个编辑器的同时还可以在网格内的记录间导航到不同的位置,两者可以做到互不影响游标,而传统的数据库程序无法做到这点,因为当游标改变时,会同步改变所有同DataSource绑定的数据感知组件中的内容。
退出系统后,可以看到Data.xml中记录了刚才输入的信息。
主从关系的实现
使用上面的Bold程序,再输入一个大尾巴兔兔的用户,你会发现有一个问题,就是大尾巴兔兔同志目前没有任何账目支出,但是点选到大尾巴兔兔对象上时,账目网格组件仍然是显示所有的账目信息,而没有实现主从关联。
为了实现主从关系,需要修改一下账目对象的BoldListHandle组件的属性,将blhAcct的RootHandle属性由原来的DmMoney.bshMoney改成blhPerson,表示父对象是人员,同时要将Expression属性改成Pay。另外为了能够在账目网格中显示同账目关联的人员信息,双击bgAcct网格,添加一个新的Column,设定它的BoldProperties.Expression为PayPerson.name,表示字段用来显示支付账目人的名字,见下图示意:
再次运行程序,可以看到这回实现了真正的主从关联,当点击哈巴狗时,只显示同哈巴狗关联的账目,同时,如果这时点击账目网格的添加命令的话,新建的对象的PayPerson自动设定为哈巴狗,如下图示意:
级联删除的实现
前面虽然我们实现了主从关系,但是将所有人员都删除后,你会发现data.xml文件中仍然有账目信息。这表明,删除人员时,没有将同人员绑定的账目信息删除,也就是说并没有实现级联删除。
为了更形象的察看这一现象,在界面添加一个BoldGrid、BoldNavigator和BoldListHandle来显示系统中所有的对象,设定BoldListHandle的RootHandle属性为DmMoney.bshMoney,设定Expression属性为MoneyModelRoot.allInstances表示显示系统中所有的对象实例。
然后向前面一样将BoldListHandle绑定到BoldGrid和BoldNavigator,并创建默认的Columns。再次运行后的效果如下图所示意:
可以看到全部对象列表中有两个字段,type字段表示系统中对象的类型,而AsString字段则显示对象的字符串表达,但是默认Person对象的AsString字段里面显示的是对象组件加类别字符串,而AcctItem对象的AsString显示的是对象的Amount属性,但是为了观察对象被删除的情况,我希望能够显示更有意义的字符串,如显示人员的名称,显示账目的名称及支付人。
因此需要修改模型中对象默认字符串的定义,双击数据模块中bmMoney,激活Bold UML Editor,选中Person对象,设定Default string rep为name,表示默认的AsString属性为人员的名称,见下图示意:
同样的,设定AcctItem的Default string rep为payPerson.name+'-'+name,表示AsString由支付人的名称及账目名称构成。
再次运行程序,显示效果示意如下:
这时,将哈巴狗从人员列表中删除后,界面示意图如下:
可以看到账目对象大白菜和化妆品仍然还在,只是支付人信息为空了。看来账目对象确实没有被级联删除。为了实现级联删除,再次打开Bold UML Editor,展开PayAssoc关联,选中Pay,然后设定Delete action为Cascade,表示使用级联删除,见下图示意:
再次运行程序,输入人员信息及人员账目信息后,再删除人员,会发现这回将绑定的账目信息也一同删除了。
总结
综上所述,使用Bold,我们可以在只写一行代码,不用设计任何数据库表的情况下,建立一个具有初步功能的应用程序,由此可见Bold的强大的快速开发数据库的能力。接下来,我将介绍一些更加高级的功能。